Unix允许进程查询内核以获得其父进程的PID,或者其任何于进程的执行状态。例如,进程可以创建一个子进程来执行特定的任务,然后调用诸如wait()这样的一些库函数检查子进程是否终止。如果子进程已经终止,那么,它的终止代号将告诉父进程这个任务是否已成功地完成。
为了遵循这些设计选择,不允许Unix内核在进程一终止后就丢弃包含在进程描述符字段中的数据。只有父进程发出了与被终止的进程相关的wait()类系统调用之后,才允许这样做。这就是引入僵死状态的原因:尽管从技术上来说进程已死,但必须保存它的描述符,直到父进程得到通知。
如果父进程在子进程结束之前结束会发生什么情况呢?在这种情况下,系统中会到处是僵死的进程,而且它们的进程描述符永久占据着RAM。如前所述,必须强迫所有的孤儿进程成为init进程的子进程来解决这个问题。这样,init进程在用wait()类系统调用检查其合法的子进程终止时,就会撤消僵死的进程。
release_task()函数从僵死进程的描述符中分离出最后的数据结构;对僵死进程的处理有两种可能方式;如果父进程不需要接收来自子进程的信号,就调用do_exit();如果已经给父进程发送了一个信号,就调用wait4()或waitpid()系统调用。在后一种情况下,函数还将回收进程描述符所占用的内存空间,而在前一种情况下,内存的回收将由进程调度程序来完成。该函数执行下述步骤:
-
递减终止进程拥有者的进程个数。这个值存放在本章前面提到的user_struct结构中。
-
如果进程正在被跟踪,函数将它从调试程序的ptrace_children链表中删除,并让该进程重新属于初始的父进程。
-
调用__exit_signal()删除所有的挂起信号并释放进程的signal_struct描述符。如果该描述符不再被其它的轻量级进程使用,函数进一步删除这个数据结构。此外,函数调用exit_itimers()从进程中剥离掉所有的POSIX时间间隔定时器。
-
调用__exit_sighand()删除信号处理函数。
-
调用__unhash_process(),该函数依次执行下面的操作:
a.变量nr_threads减。
b.两次调用detach_pid(),分别从PIDTYPE_PID和PIDTYPE_TGID类型的PID散列表中删除进程描述符。
c.如果进程是线程组的领头进程,那么再调用两次detach_pid(),从PIDTYPE_PGID和PIDTYPE_SID类型的散列表中删除进程描述符。
d.用宏REMOVE_LINKS从进程链表中解除进程描述符的链接。
-
如果进程不是线程的领头进程,领头进程处于僵死状态,而且进程是线程组的最后一个成员,则该函数向领头进程的父进程发送一个信号,通知它进程已终止。
-
调用sched_exit()函数来调整父进程的时间片。
-
调用put_task_struct()递减进程描述符的使用计数器,如果计数器变为0,则函数终止所有残留的对进程的引用。
a.递减进程所有都的user_struct数据结构的使用计数器,如果使用计数器变成0,就释放该数据结构。
b.释放进程描述符以及thread_info描述符和内核堆栈所占用的内存区域。