写在前面
1. 本文内容对应《 UNIX 环境高级编程》 ( 第 2 版 ) 》第 8 章。
2. 总结了进程终止方式和僵死进程的概念,以及使用 wait 函数获取子进程的终止状态。
3. 希望本文对您有所帮助,也欢迎您给我提意见和建议。
进程终止方式
进程有 5 种正常终止方式:
l 在 main 函数内执行 return 语句。
l 调用 exit 函数。此函数由 ISO C 定义,其操作包括调用各终止处理程序(用 atexit 函数注册),然后关闭所有标准 IO 流等。
l 调用 _exit 或 _Exit 函数,并不运行终止处理程序和信号处理程序。
l 进程的最后一个线程在其启动例程中执行返回语句。但是,该线程的返回值不会用作进程的返回值,进程以终止状态 0 返回。
l 进程的最后一个线程调用 pthread_exit 函数,进程终止状态总是 0 。
以及 3 种异常终止方式:
l 调用 abort 函数,产生 SIGABRT 信号。
l 接收一个信号并终止。
l 最后一个线程对取消请求做出响应。
僵死进程
一个已经终止,但是其父进程尚未对其进行善后处理(即获取终止子进程的有关信息,释放它仍占用的资源)的进程被称为僵死进程( zombie )。内核为每个终止子进程保存了一定量的信息,这些信息至少包括进程 ID ,进程的终止状态,以及该进程使用的 CPU 时间总量,所以当终止进程的父进程调用 wait 或 waitpid 时(也就是大家常说的收尸或是超度),可以得到这些信息。同时,内核释放终止进程所使用的所有存储区,关闭其所有打开文件,僵死进程不可能再借尸还魂。
如果父进程在子进程之前终止,那么子进程将得不到超度,永远残留在内存中(参考仙剑 3 的龙葵姑娘)。为了避免发生这样的悲剧,所有这些子进程将由 init 进程( ID 为 1 ,在自举结束时由内核调用)领养。其过程大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,则将该进程的父进程 ID 更改为 1 。无论何时,只要有一个子进程终止, init 进程就会调用一个 wait 函数取其终止状态,从而防止了在系统中存在很多僵死进程。
如果一个进程 fork 一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求的技巧是调用 fork 两次。让原先的子进程成为孙子进程,并让其父进程英年早逝,从而交由 init 进程领养。其祖父进程则不再负责孙子进程的超度工作。
wait
父进程调用 wait 完成子进程的超度。如果所有子进程都还在运行,则 wait 阻塞。如果任意一个子进程终止,正等待父进程获取其终止状态,则取得该子进程的终止状态,并返回该子进程 ID 。如果没有任何子进程,则 wait 立即出错返回。与 wait 不同, waitpid 可等待一个指定的进程,提供一个非阻塞版本,并且支持资源控制。 wait 得到的子进程终止状态可由 WIFEXITED 宏测试是否为正常终止,或由 WIFSIGNALED 宏测试是否为异常终止。正常终止时,可用 WEXITSTATUS 宏获取传递给 exit 参数的低 8 位。异常终止时,可由 WTERMSIG 宏获取使子进程终止的信号编号。
实验程序如下:
int main() { pid_t pid; int stat;
printf("parent pid=%d/n", getpid()); if((pid = fork()) < 0) { printf("fork first child error./n"); exit(1); } else if(pid == 0) { if((pid = fork()) < 0) { printf("fork second child error./n"); exit(1); } else if(pid > 0) { printf("first child pid=%d ppid=%d/n", getpid(), getppid()); abort(); } sleep(1); printf("second child pid=%d ppid=%d/n", getpid(), getppid()); exit(0); } sleep(2); if(waitpid(pid, &stat, 0) != pid) { printf("wait first child error./n"); exit(1); } if(WIFEXITED(stat)) { printf("wait %d success: normal termination, exit status=%d/n", pid, WEXITSTATUS(stat)); } else if(WIFSIGNALED(stat)) { printf("wait %d success: abnormal termination, signal number=%d/n", pid, WTERMSIG(stat)); } exit(0); } |
运行结果为:
pydeng@pydeng-laptop:~/apue.2e/mytest$ ./a.out parent pid=6262 first child pid=6263 ppid=6262 second child pid=6264 ppid=1 wait 6263 success: abnormal termination, signal number=6 |