面试知识库
进阶

僵尸进程与孤儿进程#

一句话答案#

子进程退出后父进程没调用 wait 回收,残留的进程描述符就是僵尸进程(占用 PID 不可被 kill);父进程先于子进程退出,子进程被 init/systemd(PID 1)收养就是孤儿进程(无害,会被自动回收)。

核心要点

僵尸进程(Zombie):

  • 子进程已退出,但父进程没 wait/waitpid 读取退出状态 → task_struct 残留,状态 Z
  • 危害:占用 PID 表项,大量堆积会耗尽 PID 导致无法创建新进程
  • kill -9 无效(进程已死),只能让父进程 wait 或干掉父进程

孤儿进程(Orphan):

  • 父进程先退出,子进程被 PID 1(init/systemd)收养
  • 无害:init 会循环 wait 回收,不会变僵尸

回收机制: 父进程 wait()/waitpid(),或注册 SIGCHLD 信号处理函数异步回收。

面试回答(2分钟版)

这两个概念都和父子进程的退出回收有关。在 Unix 里子进程退出后并不会立刻消失,内核会保留它的进程描述符 task_struct 和退出状态码,等父进程通过 wait 或 waitpid 来读取,读完才真正释放。如果父进程一直不调用 wait,这个已经退出但没被回收的子进程就成了僵尸进程,状态显示为 Z。僵尸进程本身不占内存和 CPU,但它占着一个 PID 表项,如果程序里不断 fork 子进程又不回收,僵尸会越堆越多,最终把系统 PID 耗尽,导致谁都没法再创建新进程,这是线上事故的常见根因。注意僵尸进程用 kill -9 杀不掉,因为它已经死了,正确做法是让父进程去 wait,或者把父进程杀掉——父进程一死,僵尸子进程就被 init 收养并回收。孤儿进程正好相反,是父进程先于子进程退出,这时子进程会被 1 号进程也就是 init 或 systemd 收养,由它来负责 wait 回收,所以孤儿进程是无害的。实际编程里避免僵尸有两种常用手段:一是父进程主动 waitpid 回收子进程退出状态;二是注册 SIGCHLD 信号处理函数,在子进程退出时异步调用 wait,或者直接把 SIGCHLD 设为 SIG_IGN 让内核自动回收。守护进程常用 fork 两次的技巧,让真正的工作进程变成孤儿被 init 接管,避免僵尸。

追问与易错

追问方向:

  • “僵尸进程怎么清理?”→ 找到它的父进程 PID(ps -ef 看 PPID),让父进程 wait;父进程不配合就 kill 父进程,僵尸被 init 收养后清除。kill -9 僵尸PID 没用
  • “怎么从代码层面避免僵尸?”→ ① 父进程 waitpid 回收;② 注册 SIGCHLD 处理函数里 wait;③ signal(SIGCHLD, SIG_IGN) 让内核自动回收;④ fork 两次让子进程变孤儿交给 init
  • “孤儿进程有危害吗?”→ 基本无害,会被 PID 1 自动回收。守护进程(daemon)正是利用这点脱离控制终端
  • “怎么排查 PID 耗尽?”→ cat /proc/sys/kernel/pid_max 看上限,ps aux | grep defunct 数僵尸数量,定位狂 fork 不回收的父进程

易错点:

  • ❌ 僵尸进程很占资源——只占 PID 表项和少量内核结构,不占内存/CPU,危害在”占 PID + 堆积”
  • kill -9 能清僵尸——进程已死,信号无意义,得处理父进程
  • ❌ 孤儿进程会变僵尸——恰恰相反,被 init 收养后能正常回收,僵尸才是没人 wait 的那种