僵尸进程(Zombie)
如同生命体一般,进程也有开始和结束。
进程退出时会进行内核清理,释放进程所有的资源,这些资源包括内存资源、文件资源、信号量资源、共享内存资源,或者引用计数减一,或者彻底释放。
不过,进程的退出其实并没有将所有的资源完全释放,仍保留了少量的资源,比如进程的PID依然被占用着,不可被系统分配。此时的进程不可运行,事实上也没有地址空间让其运行(因为大部分资源已经被回收),进程进入僵尸状态。
为什么进程退出之后不将所有的资源释放,反而保留了少量资源, 进入僵尸状态呢?
看看僵尸进程依然占有的系统资源,我们就能获得答案。
僵尸进程依然保留的资源有进程控制块task_struct、内核栈等。这些资源不释放是为了提供一些重要的信息,比如进程为何退出,是收到信号退出还是正常退出,进程退出码是多少,进程一共消耗了多少系统CPU时间,多少用户CPU时间,收到了多少信号,发生了多少次上下文切换,最大内存驻留集是多少,产生多少缺页中 断?等等。这些信息总结了进程的一生。
如果没有这个僵尸状态,进程的这些信息也会随之流逝,系统也将再也没有机会获知该进程的相关信息了。
因此进程退出后,会保留少量的资源,等待父进程前来收集这些信息。一旦父进程收集了这些信息之后(通过调用下面提到的 wait/waitpid等函数),这些残存的资源完成了它的使命,就可以释放了,进程就脱离僵尸状态,彻底消失了。
如何产生一个僵尸进程?
制造一个僵尸进程是一件很容易的事情,只要父进程调用fork创建子进程,子进程退出后,父进程如果不调用wait或waitpid来获取子进程的退出信息,子进程就会沦为僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
/* 如果出错 */
printf("error occurred!\n");
}
else if(pid == 0)
{
/* 子进程 */
exit(0);
}
else
{
/* 父进程 */
/* 休眠 300秒 */
sleep(300);
/* 获取僵尸进程的退出信息 */
wait(NULL);
}
return 0;
}
上面的例子中父进程休眠300秒后才会调用wait来获取子进程的退出信息。而子进程退出之后就会变成僵尸状态,成为一个僵尸进程。直到父进程来获取退出信息,才彻底被释放。
清除僵尸进程有以下两种方法:
1、父进程调用wait函数,为子进程“收尸”。
2、父进程退出,init进程会为子进程“收尸”。
一般而言,系统不希望大量进程长期处于僵尸状态,因为会浪费系统资源。除了少量的内存资源外,比较重要的是进程ID。僵尸进程并没有将自己的进程ID归还给系统,而是依然占有这个进程ID, 因此系统不能将该ID分配给其他进程。因此不建议使用方法二清除僵尸进程。
如何防范/避免僵尸进程的产生呢?
1、如果不关心子进程的退出状态,就应该将父进程对SIGCHLD的
处理函数设置为SIG_IGN,或者在调用sigaction函数时设置SA_
NOCLDWAIT标志位。
2、如果父进程关心子进程的退出信息,则应该在流程上妥善设
计,能够及时地调用wait/waitpid,避免僵尸状态。
本文探讨了僵尸进程的形成原因,如子进程退出后保留的资源及其作用,并提供了制造僵尸进程的示例。讲解了如何通过wait函数清理僵尸进程,以及如何防范僵尸进程的产生,强调了及时获取子进程退出信息的重要性。

被折叠的 条评论
为什么被折叠?



