linux后台开发中避免僵尸进程的方法总结

一、什么是僵死进程?
一般情况下,程序调用exit(包括_exit和_Exit,它们的区别这里不做解释),它的绝大多数内存和相关的资源已经被内核释放掉,但是在进程表中这个进程项(entry)还保留着(进程ID,退出状态,占用的资源等等),你可能会问,为什么这么麻烦,直接释放完资源不就行了吗?这是因为有时它的父进程想了解它的退出状态 。在子进程退出但还未被其父进程“收尸”之前,该子进程就是僵死进程,或者僵尸进程 。如果父进程先于子进程去世,那么子进程将被init进程收养,这个时候init就是这个子进程的父进程 。
所以一旦出现父进程长期运行,而又没有显示调用wait或者waitpid,同时也没有处理SIGCHLD信号,这个时候init进程就没有办法来替子进程收尸,这个时候,子进程就真的成了“僵尸”了 。
二、僵死进程与孤儿进程的区别?
回答这个问题很简单,就是爸爸(父进程)和儿子(子进程)谁先死的问题!
如果当儿子还在世的时候,爸爸去世了,那么儿子就成孤儿了,这个时候儿子就会被init收养,换句话说,init进程充当了儿子的爸爸,所以等到儿子去世的时候,就由init进程来为其收尸 。
如果当爸爸还活着的时候,儿子死了,这个时候如果爸爸不给儿子收尸,那么儿子就会变成僵尸进程 。
三、僵死进程的危害?

  1. 僵死进程的PID还占据着,意味着海量的子进程会占据满进程表项,会使后来的进程无法fork.
  2. 僵死进程的内核栈无法被释放掉(1K 或者 2K大小),为啥会留着它的内核栈,因为在栈的最低端,有着thread_info结构,它包含着 struct_task 结构,这里面包含着一些退出信息 。
四、避免僵死进程的方法
网上搜了下,总结有三种方方法:
① 程序中显示的调用signal(SIGCHLD, SIG_IGN)来忽略SIGCHLD信号,这样子进程结束后,由内核来wai和释放资源
② fork两次,第一次fork的子进程在fork完成后直接退出,这样第二次fork得到的子进程就没有爸爸了,它会自动被老祖宗init收养,init会负责释放它的资源,这样就不会有“僵尸”产生了
③ 对子进程进行wait,释放它们的资源,但是父进程一般没工夫在那里守着,等着子进程的退出,所以,一般使用信号的方式来处理,在收到SIGCHLD信号的时候,在信号处理函数中调用wait操作来释放他们的资源 。
五、对每个避免僵死进程方法的解析与总结
首先我们让我们来看一个生成僵尸进程的程序zombie.c如下:
#include <stdio.h>  #include <stdlib.h>  #include <unistd.h>    int main(int argc, const char *argv[])  {      int i;      pid_t pid;        for (i = 0; i < 10; i++) {          if ((pid = fork()) == 0)    /* child */              _exit(0);      }      sleep(10);        exit(EXIT_SUCCESS);  }  运行程序,在10s睡眠期间使用ps查看进程,你会发现有10个标记为“defunct”的僵尸进程:
linux后台开发中避免僵尸进程的方法总结

文章插图
 
接下来看第一种方法,程序avoid_zombie1.c如下:
#include <stdio.h>  #include <stdlib.h>  #include <signal.h>  #include <unistd.h>  #include <errno.h>    int main(int argc, const char *argv[])  {      pid_t pid;        if (SIG_ERR == signal(SIGCHLD, SIG_IGN)) {          perror("signal error");          _exit(EXIT_FAILURE);      }        while (1) {          if ((pid = fork()) == 0)    /* child */              _exit(0);      }        exit(EXIT_SUCCESS);  }  


推荐阅读