如何处理Linux中的僵尸进程defunct

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MOU_IT/article/details/87900268

1、什么是僵尸进程

    我们知道,每个Unix进程在进程表里都有一个进入点(entry),核心程序执行该进程时使用到的一切信息都存储在进入点。当用ps命令察看系统中的进程信息时,看到的就是进程表中的相关数据。当以fork()系统调用建立一个新的进程后,核心进程就会在进程表中给这个新进程分配一个进入点,然后将相关信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。当这个进程走完了自己的生命周期后,它会执行exit()系统调用,此时原来进程表中的数据会被该进程的退出码(exit code)、执行时所用的CPU时间等数据所取代,这些数据会一直保留到系统将它传递给它的父进程为止。由此可见,defunct进程的出现时间是在子进程终止后,但是父进程尚未读取这些数据之前。defunct进程是不能直接kill -9杀掉的,否则就不叫僵尸进程了。

     一个进程在调用exit()命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。

2、避免僵尸进程的办法

    1)父进程使用wait()或者waitpid()之类的函数等待子进程退出   

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

int main(int argc, char *argv[]){
  pid_t pid;
  pid = fork();
  if(pid<1){
    perroe("fail to fork");
    exit(1);
  }
  else if(pid==0){
    sleep(5);
    exit(0);
  }	
  else{
    sleep(30);
    wait(NULL);
    sleep(30);
  }
  return EXIT_SUCCESS;

}

    父进程创建子进程后30s调用wait()函数,等待子进程退出,回收子进程的资源,这也意味着子进程将会成为僵尸进程30s-5s=25s。 

    2)用两次fork(),而且使紧跟的子进程直接退出,使得孙子进程成为孤儿进程,从而init进程将负责清除这个孤儿进程。

    上述方法比较简洁,但是有个问题就是子进程如果处理的时间比较长的话,主进程会被挂起。比如: 

    socket()
    bind()
    listen()
    while(1){
        accept()
        if(fork()==0){ //进入子进程处理
            while(1){
                read()
                process()
                write()
            }
            close()
            exit
        }
        wait()   //如果这里父进程进行wait()操作,则很有可能再此处挂起,而如果不进行wait()操作,则此处又产生了僵尸进程。
    }

      对于这样的情况可以采取连续fork()两次的方法。简而言之,首先父进程首先创建子进程,子进程创建孙子进程,由孙子进程处理事务,而子进程再创建完孙子进程后,就退出。此时,孙子进程的父进程,也就是子进程退出了,因此孙子进程变为了一个孤儿进程,Linux进程处理孤儿的进程的方式,是init进程接管孤儿进程,而init进程的子进程不会成为僵尸进程。 所以上述的伪代码可以写为:

     pid_t pid;
     pid = fork();
     if(pid<1){
         perroe("fail to fork");
         exit(1);
     }
     else if(pid==0 ){
        if(fork()==0){
            /*孙子进程在这里处理事务*/
            process();
            close()
            exit()
        }
        else{
            /*子进程再这里退出,使得孙子进程成为init进程的儿子,从而避免僵尸进程的产生*/
            close()
            exit()          //<子进程在此退出
        }
    }
    else{
        /* 等待子进程的退出 */
        if(waitpid(pid, NULL, 0) != pid){
		    printf("waitpid error.\n");
		    exit(1);
        }
        //<爷爷进程进入下一轮的处理。
    }

    3)还有一种处理方法是使用信号处理函数来处理(参考):

#include     
#include     
#include     
#include     
#include     
#include     
int num_clients = 0;   
int dead_clients = 0;   
void sig_chld_handler(int sig) {   
    pid_t pid;   
    if (sig == SIGCHLD) {   
        pid = wait(NULL);   
       printf("A child dead, current child number: %d, id: %d/n", ++dead_clients, pid);   
    }   
}   
int main(int argc, char **argv) {   
    pid_t pid;   
    signal(SIGCHLD, sig_chld_handler);   
    for (int i = 0; i < 30; i++) {   
        if ((pid = fork()) == 0) {   
            exit(0);   
        } else if (pid > 0) {   
            printf("A child created, current child number: %d, id: %d/n", ++num_clients, pid);   
        }   
    }   
    sleep(10);   
    return 0;   
} 

      父进程首先注册一个信号处理函数signal(SIGCHLD, sig_chld_handler),然后每当子进程退出的时候父进程都会受到SIGCHLD信号,触发sig_chld_handler()函数,调用wait()函数等待子进程的退出。

猜你喜欢

转载自blog.csdn.net/MOU_IT/article/details/87900268