Linux僵尸进程

来源:互联网 发布:软件文档编写指南 编辑:程序博客网 时间:2024/06/03 08:17
    我们知道,在Unix进程模型中,进程是按照父进程产生子进程,子进程产生子子进程这样的方式创建出完成各项相互协作功能的进程的。当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用。如果父进程没有这么做的话,此时,子进程虽然已经退出了,但是在系统进程表中还为它保留了一些退出状态的信息,如果父进程一直不取得这些退出信息的话,这些进程表项就将一直被占用,此时,这些占着茅坑不拉屎的子进程就成为“僵尸进程”(zombie)。系统进程表是一项有限资源,如果系统进程表被僵死进程耗尽的话,系统就可能无法创建新的进程。
    那么,孤儿进程又是怎么回事呢?孤儿进程是指这样一类进程:在进程还未退出之前,它的父进程就已经退出了,一个没有了父进程的子进程就是一个孤儿进程(orphan)。既然所有进程都必须在退出之后被wait()或waitpid()以释放其遗留在系统中的一些资源,那么应该由谁来处理孤儿进程的善后事宜呢?这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程“凄凉地”结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。
    这样来看,孤儿进程并不会有什么危害,真正会对系统构成威胁的是僵尸进程。那么,什么情况下僵尸进程会威胁系统的稳定呢?设想有这样一个父进程:它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵尸进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。严格地来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经“僵死”的孤儿进程就能瞑目而去了。

总结:

僵尸进程:一个子进程在其父进程还没有调用wait()或waitpid()的情况下退出。这个子进程就是僵尸进程。

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

害处:

僵尸进程:占用系统资源,如果很多,则会严重影响服务器的性能

孤儿进程:不会占用系统资源

只要老爹不等wait(sys/wait.h)儿子,儿子都将成为孤魂野鬼zombie,unix中默认老爹总是想看儿子死后的状态(以便报仇)  
   if   老爹比儿子先再见  
        儿子将被init进程收养,最后的结果是zombie儿子彻底再见,系统资源释放  
  else    
   {  
         儿子的zombie将一直存在,系统资源占用...  
         if   老爹dead    
            儿子将被init(id   =   1)收养,最后的结果是zombie儿子彻底再见,系统资源释放  
         else   

           类似的儿子zombie越来越多,系统就等死了!!!  
   }  

如何防止僵尸进程?
首先明白如何产生僵尸进程:
1、子进程结束后向父进程发出SIGCHLD信号,父进程默认忽略了它
2、父进程没有调用wait()或waitpid()函数来等待子进程的结束

因此,防止的方法有:
第一种方法:  捕捉SIGCHLD信号,并在信号处理函数里面调用wait函数

int main(int argc, char **argv)
{
          ...
        Signal(SIGCHLD, sig_chld);
         for(;}
         ...
}

void sig_chld(int signo)
{
        pid_t        pid;
        int        stat;

        while ( (pid = waitpid(-1, &stat, WNOHANG)) >; 0)
                printf("child %d terminated/n", pid);
        return;
}

第二种方法:两次fork():

在《Unix 环境高级编程》里关于这个在8.6节有非常清楚的说明。如果一个进程要fork一个子进程,但不要求它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求的诀窍是调用fork两次。程序8 - 5实现了这一点。在第二个子进程中调用s l e e p以保证在打印父进程I D时第一个子进程已终止。在f o r k之后,父、子进程都可继续执行——我们无法预知哪一个会先执行。如果不使第二个子进程睡眠,则在f o r k之后,它可能比其父进程先执行,于是它打印的父进程I D将是创建它的父进程,而不是i n i t进程(进程ID 1)。

#include <sys/types.h>#include <sys/wait.h>#include "ourhdr.h"intmain(void){        pid_t        pid;        if ( (pid = fork()) < 0)                err_sys("fork error");        else if (pid == 0) {                /* first child */                if ( (pid = fork()) < 0)                        err_sys("fork error");                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 */                err_sys("waitpid error");        /* We're the parent (the original process); we continue executing,           knowing that we're not the parent of the second child. */        exit(0);}



原创粉丝点击