僵尸进程

In UNIX System terminology, a process that has terminated,but whose parent has not yet waited for it, is called a zombie. 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程。

 

简介

一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是 使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。

在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill -9也不能杀死僵尸进程。[补救办法杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。]

 

危害

由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束. 那么会不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的状态信息呢?  不会。因为UNⅨ提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。此即为僵尸进程的危害,应当避免。

 

避免

⒈父进程通过wait和waitpid等函数等待子进程结束,但这会导致父进程挂起。
⒉ 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。
⒊ 如果父进程不关心子进程什么时候结束,那么可以用 signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
⒋ 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。
 
fork两次代码示例:
#include "apue.h"
#include <sys/wait.h>
int main(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 soonas our         
real parent calls exit() in the statement above. Here
's where
we'd continue executing,knowing that whenwe're done,init will
reap our status.*/ sleep(2); printf("second child,parent pid = %d ",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); }

 

常见疑问

子进程结束后为什么要进入僵尸状态?

因为父进程可能要取得子进程的退出状态等信息。

僵尸状态是每个子进程必经的状态吗?

是的, 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理,这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

如何查看僵尸进程?

$ ps -el 其中,有标记为Z的进程就是僵尸进程 S代表休眠状态;D代表不可中断的休眠状态;R代表运行状态;Z代表僵死状态;T代表停止或跟踪状态

也可以用 ps -ef | grep defunct进行快速查看。(另外使用top命令查看时有一栏为S,如果状态为Z说明它就是僵尸进程)

 

僵尸进程示例代码:

#include <sys/types.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
    pid_t pid;     
    pid = fork();     
    if(pid < 0) 
        exit(1); 
    else if(pid == 0) 
    { 
        printf("I'm child\n"); 
        exit(0);      
    }     
    else 
    {        
         while(1)
         {
              ;      //do something
         } 
    } 
} 

 

本文转载自:http://baike.baidu.com/link?url=n-uJvoGGF6f89oOpJi4NZwLU8AXGMIK-CGPhW5z7XMxtGywhEYloXr1eXPW7j6Id

posted on 2014-01-01 22:49  运动和行动  阅读(381)  评论(0编辑  收藏  举报