UNIX 进程揭秘( 四 )


sean 2934 28850 21:43:05 pts/10:00 ./die2
sean 2935 29340- ?0:00
sunbox$ ps -ef | grep 2934
[1]Exit 199./die2
die2 使用 & 操作符在后台运行,然后显示一个进程清单,并且仅显示正在运行的进程及其子进程 。PID 2934 是父进程,PID 2935 是派生 (fork) 并立即终止的进程 。尽管子进程提前退出,但它仍然在进程表中作为失效 (defunct) 进程存在,或称为僵死 (zombie) 进程 。当父进程在 60 秒以后终止时,两个进程都消失了 。
当子进程终止时,会使用一个名为 SIGCHLD 的信号来通知其父进程 。该通知的确切机制现在对您并不重要 。重要的是父进程必须以某种方式确认子进程的终止 。子进程从终止时起就一直处于僵死状态,直到父进程确认该信号为止 。僵死进程不运行或消耗 CPU 周期;它只是占用进程表空间 。当父进程终止时,内核最终能够回收未确认的子进程以及父进程 。这意味着可消除僵死进程的唯一方法是终止父进程 。处理僵死进程的最好方法是首先确保它们不会发生 。清单 8 中的代码实现了一个处理传入的 SIGCHLD 信号的信号处理程序 。
清单 8. 实际操作中的信号处理程序
#include
#include
#include
#include
void sighandler(int sig) {
printf("In signal handler for signal %dn", sig);
/* wait() is the key to acknowledging the SIGCHLD */
wait(0);
}
int main(void) {
int i;
/* Assign a signal handler to SIGCHLD */
sigset(SIGCHLD, &sighandler);
if (!fork()) {
/* Child */
_exit(0);
}
sleep(60);
}
sunbox$ gcc dIE3.c -o die3
sunbox$ ./die3 &
[1] 3116
sunbox$ In signal handler for signal 18
ps -ef | grep 3116
sean 3116 28850 22:37:26 pts/10:00 ./die3
由于使用了 sigset 函数(它向信号处理程序分配一个函数指针),清单 8 比前一个示例稍微复杂一点,。每当进程接收到某个已处理的信号时,就会调用通过 sigset 分配的函数 。对于 SIGCHLD 信号,应用程序必须调用 wait(3c) 函数,以等待子进程退出 。由于该进程已经退出,这相当于向内核确认了子进程的终止 。实际上,父进程所做的工作可能不只是确认该信息 。它还可能需要清理子进程的数据 。
在执行 die3 以后,代码检查了进程清单,并干净地执行子进程 。然后使用值 18 (SIGCHLD) 来调用信号处理程序,确认子进程的退出,并且父进程返回到 sleep(60) 。
总结
Unix 进程是在某个进程调用 fork 时创建的,fork 将正在运行的可执行进程一分为二 。然后该进程可以执行 exec 系列中的某个系统调用,从而将当前运行的映像替换为新的映像 。
当父进程终止时,其所有子进程将由 PID 为 1 的 init 接纳 。如果子进程在父进程之前终止,则会向父进程发送一个信号,然后子进程转变为僵死状态,直到该信号得到确认,或父进程被终止 。
【UNIX 进程揭秘】现在您已了解了进程是如何创建和销毁的,您已经为处理运行您系统的进程作了更好的准备,尤其是大量使用多进程的系统,例如 Apache 。如果您需要执行某些故障排除,能够跟踪某个特定进程的进程树还允许您将任何应用程序追溯到创建它的进程 。

推荐阅读