很老的话题了,最近用到了,所以记下来。
所谓僵尸进程,即Zombie进程。当一个进程已经结束,但是系统没有把它的进程的数据结构完全释放,此时用ps 察看它的状态是defunt。 僵尸进程占据进程表的空间,而且不能被kill掉因为它已经死了,所以在开发多进程尤其是守护进程时注意要避免产生僵尸进程。
僵尸进程的产生原因是什么呢?
当UNIX系统中1个进程结束,init即1号进程会检查该进程有没有子进程,如果有,init就会接管这些子进程,把这些子进程的ppid改成1。
同时init会用信号SIGCHLD通知该进程的父进程。如果父进程用wait()或者waitpid()来获取子进程的状态的话,那么进程的相关状态数据返给父进程之后,该进程自身将被销毁。
那么考虑一种情况,如果父进程没有使用wait或waitpid呢? 此时因为子进程的状态数据没有被取走,所以该子进程即变成了僵尸进程。
解决方法通常有3种
1)2次fork
父进程
pid_t pid;
switch ( pid = fork() ) {
case -1: break;
case 0:
execl("xxx", "xxx", (char*)0,(char*)0);
exit(-1);
default:
waitpid(pid, NULL, 0); //we wait to avoid zombie
break;
子进程 xxx
pid_t pid;
switch ( pid = fork() ) {
case -1: break;
case 0:
/* 做子进程自己的动作 */
sleep(1); // sleep 1 second to let the parent go firstly
exit(-1);
default:
break;
}
可以看到,子进程中又fork了一次,产生的孙进程做实际的操作,而子进程迅速结束返回,这样父进程可以快速回收。而孙进程将被init进程接管。
2)捕获SIGCHLD,然后waitpid
使用sigaction()为SIGCHLD安装一个handler,然后在handler里使用waitpid
static void sig_handler(int signo) {
int stat;
while(waitpid(-1,&stat,WNOHANG)>0)
;
return;
}
为什么用while是因为可能有多个子进程,而多个SIGCHLD并不会排队。
不要用signal()函数来设handler,因为那不可靠。
当然如果父进程可以阻塞执行,等待子进程完成,那么不用安装信号,直接waitpid即可。通常不会这么做。
3)设置SIGCHLD的动作为 SA_NOCLDWAIT
使用sigaction()对SIGCHLD的flag设置 SA_NOCLDWAIT,通知系统不产生僵尸。
该方法没有试过。
阅读(1341) | 评论(0) | 转发(0) |