linux 僵死进程及其处理方法

一、什么是僵死进程?

一般情况下,程序调用exit(包括_exit和_Exit,它们的区别这里不做解释),它的绝大多数内存和相关的资源已经被内核释放掉,但是在进程表中这个进程项(entry)还保留着(进程ID,退出状态,占用的资源等等),你可能会问,为什么这么麻烦,直接释放完资源不就行了吗?这是因为有时它的父进程想了解它的退出状态。在子进程退出但还未被其父进程“收尸”之前,该子进程就是僵死进程,或者僵尸进程。如果父进程先于子进程去世,那么子进程将被init进程收养,这个时候init就是这个子进程的父进程。

所以一旦出现父进程长期运行,而又没有显示调用wait或者waitpid,同时也没有处理SIGCHLD信号,这个时候init进程就没有办法来替子进程收尸,这个时候,子进程就真的成了“僵尸”了。

僵尸例子代码如下:

#include <string>
#include <iostream>
#include <algorithm>
#include <cstring> 
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <stdio.h>
#include<unistd.h> 
#include <sys/types.h>
int main(int argc,char **argv){
 pid_t chldpid=0;
 int i;
 printf("main enter");
 for(i=0;i<10;i++){
 if((chldpid=fork())==0) { 
     printf("child exit \n"); 
<span style="white-space:pre">	</span> exit(0); 
     }
  else { 
<span style="white-space:pre">	</span>printf("child process generted=%d\n",chldpid); 
    }
  } 
  i=100; 
  while(i>0) { 
   sleep(10); 
  } 
  return 0;
}


二、僵死进程与孤儿进程的区别?

回答这个问题很简单,就是爸爸(父进程)和儿子(子进程)谁先死的问题!

如果当儿子还在世的时候,爸爸去世了,那么儿子就成孤儿了,这个时候儿子就会被init收养,换句话说,init进程充当了儿子的爸爸,所以等到儿子去世的时候,就由init进程来为其收尸。

如果当爸爸还活着的时候,儿子死了,这个时候如果爸爸不给儿子收尸,那么儿子就会变成僵尸进程。

三、僵死进程的危害?

  1. 僵死进程的PID还占据着,意味着海量的子进程会占据满进程表项,会使后来的进程无法fork.
  2. 僵死进程的内核栈无法被释放掉(1K 或者 2K大小),为啥会留着它的内核栈,因为在栈的最低端,有着thread_info结构,它包含着 struct_task 结构,这里面包含着一些退出信息。

四、避免僵死进程的方法

网上搜了下,总结有三种方方法:

      1. 程序中显示的调用signal(SIGCHLD, SIG_IGN)来忽略SIGCHLD信号,这样子进程结束后,由内核来wai和释放资源。

      2. 对子进程的退出捕获它们的退出信号SIGCHLD。这样就可以释放它们的资源。

       父进程一般没工夫在那里守着,等着子进程的退出,所以,一般使用信号的方式来处理,在收到SIGCHLD信号的时候,在信号处理函数中调用

      waitpid() 操作来释放他们的资源。推荐的代码例子如下: 

void sig_chld(int signo) 
{ 
       pid_t   pid; 
       int     stat; 
        
       while((pid = waitpid(-1, &stat, WNOHANG)) > 0){ 
               printf("child %d terminated\n", pid); 
       } 
        return; 
} 

然后在main()中,在fork语句的前面的某个地方设置子进程退出的信号处理函数,如下所示:

if(signal(SIGCHLD, sig_chld) == SIG_ERR)
    {
        fprintf(stderr, "signal error : %s\n", strerror(errno));
        return 1;
    }

注意,正如跟其它信号一样,SIGCHLD也是不可靠的,是不会被LINUX入队列的。也就是如果一个child process退出,

则触发一个SIGCHLD而进入上面的中断处理程序,但该函数还没有处理结束,又有一个child process退出再次触发一个SIGCHLD,后面的

SIGCHLD就会处在pending状态,如果不巧的是,还有更多的child process退出而此时中断处理程序还没有结束,则后面的这些SIGCHLD就会

被linux给丢弃了而不是进信号队列。

但是如上面代码写的一样,只要用while()去处理,则该中断处理程序会给现有的所有尚未处理的死亡子进程收尸,从而避免僵尸zombie出现。

参见<<Unix网络编程卷1>>

需要知道三点:

   1. 当某个信号的信号处理函数被调用时,该信号会被操作系统阻塞(默认sa_flags不设置SA_NODEFER标志)。

   2. 当某个信号的信号处理函数被调用时,该信号阻塞时(如果不设置WNOHANG),该信号又多次发生,那么操作系统并不将它们排队,而是只保留第一次的,后续的被抛弃。还有一点我们必须清楚的是

   3. wait系列函数与信号SIGCHLD是没有任何关系的,即wait系列函数并不是信号SIGCHLD驱动的。

注:这里不能用wait()来处理多个signalchld信号!

但是这里为什么放在信号处理函数中处理呢?这样做的原因是:子进程什么时候结束对父进程来说完全是个异步事件,而信号机制就是用来处理异步事件的,所以当子进程结束时,在SIGCHLD信号处理函数中调用wait来收尸可以保证以最快的速度收回其残余信息,不至于造成系统中积累了大量的僵尸进程。

也可以这样来理解:系统把所有的僵尸进程串在一起形成一个僵尸进程链表,而while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)就是来清空这个链表的,直到waitpid()返回0,表明已经没有僵尸进程了,或者返回-1,表明出错(当错误码errno为ECHILD的时候同样表明已经不存在僵尸进程了)。

了解了以上知识点,就能理解为什么while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)能够回收所有的僵尸进程了。

http://blog.csdn.net/guzhouke19910920/article/details/7645034

我们可以在上面的信号处理函数中加入相应的打印信息:

static int num1 = 0
static int num2 = 0;
void avoid_zombies_handler(int signo)
{
    pid_t pid;
    int exit_status;
    int saved_errno = errno;

    printf("num1 = %d\n", ++num1);
    while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0) {
        printf("num2 = %d\n", ++num2);
    }

    errno = saved_errno;
}

打印的结果你会发现,当num1递增1的时候,即每调用一次信号处理函数,num2一般会递增很多,即while循环了很多次,所以尽管有的SIGCHLD信号被丢弃了,我们也不用担心会遗漏系统中的僵尸进程。退出while循环时,证明此时系统中已经没有僵尸进程了,所以退出信号处理函数后,如果有阻塞的SIGCHLD信号,那么会再次触发该信号处理函数,这样我们就不用担心了,万无一失。我们不防做个最坏的打算,即之前的信号全部被丢弃了,只有最后一次的SIGCHLD信号被捕获,从而触发了信号处理函数,这样我们也不用担心,因为while循环会一次性收回全部的僵尸进程信息,只是这次循环的次数要多得多罢了,当然这只是假设,一般系统不会出现这样的情况。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值