添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

  • 研究经典面试题:孤儿进程和僵尸进程
  •