Linux进程管理知识整理(1)
Linux进程管理知识整理(1)
1、进程有哪些状态?什么是进程的可中断等待状态?进程退出后为什么要等待调度器删除其task_struct结构?进程的退出状态有哪些?
TASK_RUNNING可运行状态)
TASK_INTERRUPTIBLE可中断等待状态)
TASK_UNINTERRUPTIBLE不可中断等待状态)
TASK_STOPPED进程被其它进程设置为暂停状态)
TASK_TRACED进程被调试器设置为暂停状态)
TASK_DEAD退出状态)
进程由于所需资源得不到满足,从而进入等待队列,但是该状态能够被信号中断。比如当一个正在运行的进程因进行磁盘I/O操作而进入可中断等待状态时,在I/O操作完成之前,用户可以向该进程发送SIGKILL,从而使该进程提前结束等待状态,进入可运行态,以便响应SIGKILL,执行进程退出代码,从而结束该进程。
当进程退出时例如调用exit或者从main函数返回),需要向父进程发送信号,父进程进行信号处理时,需要获取子进程的信息,因此这时不能删除子进程的task_struct。另外每个进程都有一个内核态的堆栈,当进程调用exit()时,在切换到另外一个进程前,总是要使用内核态堆栈,因此当进程调用exit()时,完成必要的处理后,就把state设置为TASK_DEAD,并切换到其他进程。当顺利地切换到其他进程后,由于该进程的状态设置为TASK_DEAD,因此这个进程不会被调度,之后当调度器检查到状态为TASK_DEAD的进程时,就会删除这个进程的task_struct结构,这样这个进程就彻底的消失了。
EXIT_ZOMBIE僵死进程):父进程等待子进程结束时发送的SIGCHLD信号默认情况下,创建进程都会设置在进程退出的时候向父进程发送信号的标志,除非创建的是轻权进程),此时子进程已退出,并且SIGCHLD信号已经发送,但是父进程还没有被调度运行;EXIT_DEAD僵死撤销状态):父进程对子进程的退出信号“没兴趣”,或者在子进程退出时,父进程通过waitpid()调用等待子进程的SIGCHLD信号。
2、僵尸进程
1) 怎么产生僵尸进程
一个进程在调用exit命令结束自己的时候,其实它并没有真正的被销毁,只是进程不能被调度并处于EXIT_ZOMBIE状态,它占用的所有内存就是内核栈、thread_info结构和task_struct结构。此时进程存在的唯一目的就是向它的父进程提供信息,如果它的父进程没有调用wait或waitpid等待子进程结束,又没有显示地忽略该信号,那么它就一直保持EXIT_ZOMBIE状态。
2) 怎么查看僵尸进程
利用命令ps,看到有标记为Z的进程就是僵尸进程。
3) 怎么清理僵尸进程
- 父进程可以调用waitpid、wait函数来等待子进程结束
- 把父进程杀掉,父进程死后,僵尸进程成为“孤儿进程”,过继给init进程,init进程始终负责清理僵尸进程,它产生的所有僵尸进程也跟着消失。
3、PID管理
在Linux系统中用pid结构体来标识一个进程,通过pidmap位图来管理所有的进程号(即pid:与前面的pid结构体不是同一个意思),目的就是要更快的找到目标进程。用pid结构体来表示进程的优点:比直接用数字pid_t更容易管理(进程退出时pid回收再分配效率高),比直接用task_struct标识进程占用空间小。
pid结构体如下所示:
- struct pid
- {
- atomic_t count;
- int nr; /*存放pid数值*/
- struct hlist_node pid_chain; /*把该pid链到哈希表中*/
- struct hlist_head tasks[PIDTYPE_MAX];
- struct rcu_head rcu;
- };
因为对于32位系统来说,默认最大的pid为32768,由于pidmap位图中每一位表示这个pid是否可用,共需要32768位,正好一个物理页的大小(4*1024*8)。
pidmap结构体如下所示:
- struct pidmap {
- /*
- *这个变量用来统计这个结构体对应的一页物理内存中有多少个位
- *是0的,即空闲pid的数量
- */
- atomic_t nr_free;
- void *page; /*这个就是指向存放这个位图的内存页的指针*/
- };
下面首先来看Linux内核启动之初在start_kernel函数中对pidmap位图的初始化函数pidmap_init如下所示:
- void __init pidmap_init(void)
- {
- /*申请一页物理内存,并初始化为0*/
- init_pid_ns.pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);
- /*将第0位设置为1,表示当前进程使用pid为0,即现在就是0号进程*/
- set_bit(0, init_pid_ns.pidmap[0].page);
- /*同时更新nr_free统计空闲pid的值*/
- atomic_dec(&init_pid_ns.pidmap[0].nr_free);
- pid_cachep = KMEM_CACHE(pid, SLAB_PANIC);
- }
再来看Linux内核启动之初在start_kernel函数中对pid hash表的初始化函数pidhash_init如下所示:
- void __init pidhash_init(void)
- {
- int i, pidhash_size;
- /*
- *nr_kernel_pages表示内核内存总页数,就是系统DMA和NORMAL内
- *存页区的实际物理内存总页数
- *megabytes:统计的是内核内存有多少MB
- */
- unsigned long megabytes = nr_kernel_pages >> (20 - PAGE_SHIFT);
- /*从下面两行代码可以看出pidhash_shift是在4~12之间的*/
- pidhash_shift = max(4, fls(megabytes * 4));
- pidhash_shift = min(12, pidhash_shift);
- pidhash_size = 1 << pidhash_shift;
- printk("PID hash table entries: %d (order: %d, %Zd bytes)\n",
- pidhash_size, pidhash_shift,
- pidhash_size * sizeof(struct hlist_head));
- /*
- *由alloc_bootmem可知pid_hash是在低端物理内存申请的,由于
- *pidhash_init函数是在mem_init函数执行之前被调用的,所以这里申请
- *的内存是不会被回收的
- */
- pid_hash = alloc_bootmem(pidhash_size * sizeof(*(pid_hash)));
- if (!pid_hash)
- panic("Could not alloc pidhash!\n");
- for (i = 0; i < pidhash_size; i++)
- /*初始化每个表的每个表项的链表*/
- INIT_HLIST_HEAD(&pid_hash[i]);
- }
总结:内核维护两个数据结构来维护进程号pid,一个是哈希表pid_hash,还有一个位图pidmap。在do_fork()中每调用一次alloc_pid(),首先会通过调用alloc_pidmap()修改相应的位图,该函数的主要思想是:last记录上次分配的pid,此次分配的pid为last+1,如果pid超出最大值,那么就循环回到最初值(RESERVED_PIDS),然后测试pidmap上该pid所对应的bit是否为0,直到找到为止。其次通过hlist_add_head_rcu函数在pid_hash表中增加一项。
评论暂时关闭