浅谈Linux僵尸进程与孤儿进程

在Linux中,进程退出后,分配的绝大部分资源将被回收,除了task_struct结构及少数资源外。此时的进程已经“死亡”,但task_struct结构还保存在进程列表中,半死不活,故称为“僵尸进程”

在回收僵尸进程之前,如果父进程退出了,则僵尸进程变为“孤儿进程”,进而被init进程接管、回收。

僵尸进程的状态为EXIT_ZOMBIE,缩写Z,ps命令也会打印僵尸进程,但无法使用kill杀死。

为什么需要僵尸进程(保留task_struct)?

之所以保留task_struct,是因为task_struct里面保存了进程的pid、退出码、以及一些统计信息,父进程很可能会关心这些信息。比如$?变量就保存了最近一个退出的前台进程的退出码,这个退出码就来自于僵尸进程的task_struct结构。

为什么要处理僵尸进程

僵尸进程的task_struct中保存了进程的pid、退出码等。尤其是pid,如果僵尸进程过多,最终耗尽了pid,那么将无法创建新的进程。

如何处理僵尸进程?

父进程可以通过wait系列的系统调用(如wait4、waitpid等,以下用wait指代)来等待某个或某些子进程的退出,并获取它的退出信息,然后顺便回收子进程的“尸体”(如task_struct),然后子进程转入EXIT_DEAD状态(X),等待被操作系统彻底回收。

子进程退出后,父进程未调用wait回收尸体前,子进程将保持僵尸状态。

方案1:父进程调用wait

很自然的,如果父进程主动调用wait,也就消灭了僵尸进程。

但wait调用是阻塞的,如果调用wait时子进程还没有退出,将阻塞住父进程,影响性能。

方案2:kill父进程(产生“孤儿进程”)

如果父进程回收僵尸进程前就退出了,则僵尸进程变为“孤儿进程”。通常会将“孤儿进程”委托给init进程(pid等于1),init进程将在一个死循环中等待其子进程(包括这些僵尸进程)的退出事件,并调用wait回收子进程的尸体。

因此,找到僵尸进程的父进程,kill掉,也是一个没有办法时的办法。

方案3:通过信号机制异步回收

编写程序时,子进程退出前向父进程发送SIGCHLD信号,父进程收到SIGCHLD信号后(通过signal(SIGCHLD, sig_child)绑定信号处理器),调用wait回收子进程的尸体。

与方案1相比,方案3不需要阻塞父进程,是最理想的方式。

为什么会出现少数僵尸进程一直不被回收

在实际工作中,总会碰到少数僵尸进程一直不被回收。

显然,如果父进程没有绑定SIGCHLD信号处理函数调用wait或waitpid等待子进程结束,那么僵尸进程就会一直存在。如果这时候父进程结束了,那么init进程会自动接手这个子进程,还是能被清除掉的。但是,如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是系统中为什么有时候会有很多的僵尸进程。


参考:

扫描微信关注我
微信公众号二维码
本文链接:浅谈Linux僵尸进程与孤儿进程
作者:猴子007
出处:https://monkeysayhi.github.io
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名及链接。
我是猴子007,<br>一只非常特殊的动物,<br>可以从事程序的开发、维护,<br>经常因寻找香蕉或母猿而无心工作。