僵死进程 产生,及避免方法
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程
是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退 出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信 号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。
============================================================
怎样来清除僵尸进程:
1.改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,尽管对的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
2.把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。
======================================================================
我怎样避免它们:
1 你需要却认父进程为每个子进程的终止调用‘wait()’(或者‘waitpid()’,‘wait3()’,等 等); 或者,在某些系统上,你可以指令系统你对子进程的退出状态没有兴趣。在SysV系统上,可以调用signal函数,设置SIGCLD信号为SIG_IGN,系统将不产生僵死进程, 节)另一种方法是*两次*‘fork()’,而且使紧跟的子进程直接退出,这样造成孙子进程变成孤儿进程(orphaned),从而init进程将负责清除它。为了忽略子进程状态,你需要做下面的步骤(查询你的系统手册页以知道这是否正
常工作):
复制代码-
- struct sigaction sa;
- sa.sa_handler = SIG_IGN;
- #ifdef SA_NOCLDWAIT
- sa.sa_flags = SA_NOCLDWAIT;
- #else
- sa.sa_flags = 0;
- #endif
- sigemptyset(&sa.sa_mask);
- sigaction(SIGCHLD, &sa, NULL);
|
如果这是成功的,那么‘wait()’函数集将不再正常工作;如果它们中任何一个被
调用,它们将等待直到*所有*子进程已经退出,然后返回失败,并且
‘errno==ECHILD’。
2 另一个技巧是捕获SIGCHLD信号,然后使信号处理程序调用‘waitpid()’或‘wait3()’。
3 让waitpid阻塞就可以了,这样会保证再子进程推出后父进程才结束。
4 忽略信号
int main(int argc, char **argv)
{
signal(SIGCHLD, SIG_IGN); //加入此句即可,忽略SIGCHLD信号
if (0 == fork()) {
printf("In the child process:%d\n", getpid());
} else {
printf("In the parent process:%d\n", getpid());
sleep(10);
exit(0);
}
printf("Pid:%d\n", getpid());
return 0;
}
5 fork两次在防止僵死方面来说,就是因为儿子进程先退出,孙子进程就被init接管了,实际上与最初的父进程脱离了关系,就不会僵死了。
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
{
fprintf(stderr,"Fork error!\n");
exit(-1);
}
else if (pid == 0) /* first child */
{
if ((pid = fork()) < 0)
{
fprintf(stderr,"Fork error!\n");
exit(-1);
}
else if (pid > 0)
exit(0); /* parent from second fork == first child */
/*
* We're the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* we're done, init will reap our status.
*/
sleep(2);
printf("Second child, parent pid = %d\n", getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
{
fprintf(stderr,"Waitpid error!\n");
exit(-1);
}
/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}
5
static void
sig_child()
{
waitpid(-1, NULL, 0);
}
[...]
signal(SIGCHLD, sig_child)
sig_child这个函数有问题。这个函数的作用是每当有一个进程退出时,便会调用一次sig_child,sig_child便会调用
waitpid处理一个已退出的子进程。但是假如程序正在sig_child里面,这时又有另外一个子进程退出了,这时该怎么处理呢?处理的方法应该是不
会有另外一个sig_child响应,于是这时便有2个子进程退出了,但是sig_child只能处理一个,于是便有一个退出的子进程成了僵死进程了。于是把sig_child改成:
static void
sig_child()
{
while (waitpid(-1, NULL, 0) > 0);
}
这样假如程序正在sig_child里面,这时又有另外n个子进程退出了,由于用了while,于是sig_child便会将所以退出的子进程“一网打尽”。