僵死进程总结

    http://bbs.chinaunix.net/thread-820884-1-1.html

   什么是僵死进程呢?就是儿子死了,父亲不去收尸,儿子就变成僵尸了。

(1)父进程先结束,子进程后结束。父进程结束时,子进程被init收养,成为他的子进程。eg:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main()
{
    int p;
    if((p=fork())==0)
    {
       // sleep(2);
        printf("before adpot: my parent %d\n",getppid());
        sleep(10);
        printf("after adpot: my parent %d\n",getppid());
        sleep(10);
        exit(1);
    }else
    {
        sleep(3);
        printf("parent exit pid: %d\n",getpid());
    }
   // printf("ccc\n");
   // sleep(10);
    exit(0);
    return 0;
}

运行:

administrator@ubuntu:~/tel/my$ gcc asd.c -o asd
administrator@ubuntu:~/tel/my$ ./asd &
[1] 2557
administrator@ubuntu:~/tel/my$ before adpot: my parent 2557
parent exit pid: 2557
after adpot: my parent 1

[1]+  完成                  ./asd
父进程exit后,子进程的父进程id变为1,就是init啦。

(2)子进程先结束,父进程后结束,父进程没有收尸行为。子进程死后,变成僵死进程,等到父进程死时,子进程被init收养,进行收尸处理。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main()
{
    int p;
   
    if((p=fork())==0)
    {
        printf("child: %d\n", getpid());
        exit(1);
    }else
    {

        sleep(30);
        exit(0);
    }

    return 0;
}
运行:
administrator@ubuntu:~/tel/my$ ./asd &
[1] 2643
administrator@ubuntu:~/tel/my$ child: 2644

administrator@ubuntu:~/tel/my$ ps -ef|grep 2644
1000      2644  2643  0 10:45 pts/0    00:00:00 [asd] <defunct>   --->僵死进程!!
1000      2646  1999  0 10:45 pts/0    00:00:00 grep --color=auto 2644
(3)子进程先死,父进程一直不死。子进程会一直成为僵尸。【这种情况需要解决一下,前面两种情况,由于父进程都很快会退出,所以不会有问题。】这种情况,僵死进程会占用pid,这是最大的危害。解决办法:

    1)sinal(SIGCHLD,SIG_IGN);明确声明,子进程死后采用忽略对待。                     优点:简单,缺点:sv可行,posix不可行,因此通用行差。

    2)signal(SIGCHLD,func);编写信号处理程序,在func中调用wait系列函数。----最好别用sigal,用sigaction,另外,信号集的sa_flag选项使用SA_NOCLDSTOP,在处理程序中用waitpid(-1,NULL,WNOHANG)或 waitpid(-1,&somethg,WNOHANG)         优点:通用性好!支持POSIX

PS:如果用signal函数,那么不同系统表现不同。1.有的系统需要在处理函数中使用非阻塞的waitpid或者wait3一遍一遍循环检测是否有子进程需要收尸。在系统5中没有关系,多个子进程,每个死时都会调用处理函数,所以处理函数内部只要收尸一次即可。2.有的系统调用一次处理函数以后,对SIGCHLD的处理就复位了,所以在处理函数内部需要再次注册一下。

    3)直接wait。                优点:通用;缺点:父进程要等待

    4)fork两次。第一次fork产生子进程,子进程再fork产生孙子进程。子进程随后立刻exit,父进程则调用wait阻塞着,子进程exit后立刻收尸。于是,父进程wait后可以接着干活,孙子进程现在由init收养,也可以安心干活,不用考虑死后收养的问题,万一孙子进程比父进程先死,也可以正常收尸,不至于占用pid号。       优点:通用行很好。缺点:效率略低,毕竟fork两次。




一些demo:

wait用法:wait只能收一个进程。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main()
{
    int p;
   
    if((p=fork())==0)
    {
        printf("child1: %d\n", getpid());
        exit(1);
    }else
    {
        if((p=fork())==0)
        {
            printf("child2: %d\n", getpid());
            exit(1);   
        }else
        {
            wait();
            sleep(60);
            exit(0);
        }

    }

    return 0;
}
administrator@ubuntu:~/tel/my$ ./asd &
[1] 2926
administrator@ubuntu:~/tel/my$ child1: 2928
child2: 2927

administrator@ubuntu:~/tel/my$ ps -ef|grep 2927
1000      2930  1999  0 13:46 pts/0    00:00:00 grep --color=auto 2927
administrator@ubuntu:~/tel/my$ ps -ef|grep 2928
1000      2928  2926  0 13:46 pts/0    00:00:00 [asd] <defunct>
1000      2932  1999  0 13:46 pts/0    00:00:00 grep --color=auto 2928
administrator@ubuntu:~/tel/my$ ps -ef|grep 2928
1000      2928  2926  0 13:46 pts/0    00:00:00 [asd] <defunct>
1000      2934  1999  0 13:47 pts/0    00:00:00 grep --color=auto 2928
administrator@ubuntu:~/tel/my$ 
两个子进程,一个2927,一个2928,2927被收尸了,2928则变成僵尸了。

system V早期的signal函数的缺点:

     signa注册一次以后,马上就被取消(reset)了,想要一直使用指定的函数作为信号处理函数,必须在处理函数内部再调用一次signal,这样,并不是原子操作,这个时间差里,可能同样的信号又产生一次了,那么那个信号就丢掉了,没有被信号处理程序捕获到。

【实例1】丢失信号示例:父进程前后fork出两个子进程,随后pause两次以等待。结果是:信号处理函数只执行一次,同时系统中留了一个僵尸进程。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void shoushi(int a)
{
    signal(SIGCHLD, shoushi);
    if(a==SIGCHLD)
    {
        printf("aaa\n");
        wait();
    }
    
}

int main()
{
    int p;
    signal(SIGCHLD, shoushi);
    if((p=fork())==0)
    {
        printf("child1: %d\n", getpid());
        exit(1);
    }else
    {
        if((p=fork())==0)
        {
    //        sleep(3);
            printf("child2: %d\n", getpid());
            exit(1);   
        }else
        {
            pause();
            pause();
            exit(0);
        }

    }

    return 0;
}

运行:

administrator@ubuntu:~/tel/my$ gcc asd.c -o asd
administrator@ubuntu:~/tel/my$ ./asd &
[1] 3424
administrator@ubuntu:~/tel/my$ child1: 3425
child2: 3426
aaa

administrator@ubuntu:~/tel/my$ jobs
[1]+  运行中               ./asd &
administrator@ubuntu:~/tel/my$ ps -ef |grep 3425
1000      3428  1999  0 15:02 pts/0    00:00:00 grep --color=auto 3425
administrator@ubuntu:~/tel/my$ ps -ef |grep 3426
1000      3426  3424  0 15:02 pts/0    00:00:00 [asd] <defunct>
1000      3430  1999  0 15:02 pts/0    00:00:00 grep --color=auto 3426
administrator@ubuntu:~/tel/my$ 
所以,第二个子进程在那个时间差之内exit了,没有被捕获到,而且此时父进程的第二个pause尚未运行,因此结果就是:第二个子进程变为僵尸,父进程的第二个pause一直阻塞。

【实例2】成功捕获两个信号。实例1中,将sleep(3)的注释去掉,于是第一个子进程有充分的时间死掉,父进程从容转到信号处理函数,完成处理,并用signal再次设置信号处理函数,父进程进入第二个pause阻塞,然后第二个子进程死掉,将父进程的第二个pause打断,前往信号处理函数:

administrator@ubuntu:~/tel/my$ gcc asd.c -o asd
administrator@ubuntu:~/tel/my$ ./asd &
[1] 3442
administrator@ubuntu:~/tel/my$ child1: 3443
aaa

administrator@ubuntu:~/tel/my$ child2: 3444
aaa

[1]+  完成                  ./asd
【实例3】信号处理函数中,不再重新注册,此时,信号处理函数只能用一次,用过就完了。第二个子进程不会调用信号处理函数,而会变成僵尸。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void shoushi(int a)
{
    //signal(SIGCHLD, shoushi);
    if(a==SIGCHLD)
    {
        printf("aaa\n");
        wait();
    }
    
}

int main()
{
    int p;
    signal(SIGCHLD, shoushi);
    if((p=fork())==0)
    {
        printf("child1: %d\n", getpid());
        exit(1);
    }else
    {
        if((p=fork())==0)
        {
            sleep(3);
            printf("child2: %d\n", getpid());
            exit(1);   
        }else
        {
            pause();
            pause();
            exit(0);
        }

    }

    return 0;
}

。。。。没有成功,两个子进程都调用了信号处理函数。

不管怎么样,需要考虑的问题是:(1)信号丢失。多个子进程可能在差不多的时间结束掉,来不及处理完一个以后再让第二个子进程结束。这种情况,就需要在信号处理函数中完成:(a)如果有多个进程终止了的话,一并处理掉。这需要一个循环,但是,假如已经处理完了,已经没有了,难道就让父进程一直阻塞在这里吗?当然不,所以信号处理函数需要搞定的第二件事就是:如果没有待处理的子进程,那么退出。办法是用无阻塞的waitpid:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void shoushi(int a)
{
    //signal(SIGCHLD, shoushi);
    if(a==SIGCHLD)
    {
        pid_t pid;
        while( (pid=waitpid(-1,NULL,WNOHANG)) >0)
            printf("deal with dead body: %d\n", pid);
    }
    
}

int main()
{
    int p;
    signal(SIGCHLD, shoushi);
    if((p=fork())==0)
    {
        printf("child1: %d\n", getpid());
        exit(1);
    }else
    {
        if((p=fork())==0)
        {
            //sleep(3);
            printf("child2: %d\n", getpid());
            exit(1);   
        }else
        {
            pause();
           // pause();
            exit(0);
        }

    }

    return 0;
}
运行:

administrator@ubuntu:~/tel/my$ gcc asd.c -o asd
administrator@ubuntu:~/tel/my$ ./asd
child2: 3516
child1: 3515
deal with dead body: 3515
deal with dead body: 3516
administrator@ubuntu:~/tel/my$ 
结果是正确的。当然,条件是两个子进程都是直接就exit的,要是有延时,就会分别在不同时间发出一个信号,那样只有一个pause就不够了。
总结:

wait的用法:(1)没有子进程时返回-1;(2)有子进程已经结束但是没有收尸,那么替他收尸,返回他的pid,通过参数返回信息;(3)如果有子进程,但是都没死,那么wait阻塞,直到第一个死掉的进程到来。

waitpid:(1)没有子进程时返回-1;(2)waitpid(-1,NULL,WNOHANG)等待第一个终止的进程,如果没有进程终止,那么返回-1,不然处理掉,返回该进程的pid。



sigaction有一点比较好,就是在信号处理函数中时,相同的信号是默认阻塞的!所以不用担心信号丢失。但是阻塞的时候如何接二连三出来好几个信号,最后只留下一个,因此信号处理函数还是应该用循环,否则对于SIGCHLD信号会丢失处理导致僵尸进程的遗留。

正确的处理方法:sigaction+waitpid+循环


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值