40

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

 5 years ago
source link: https://monkeysayhi.github.io/2018/12/05/浅谈Linux僵尸进程与孤儿进程/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在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进程会自动接手这个子进程,还是能被清除掉的。但是,如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是系统中为什么有时候会有很多的僵尸进程。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK