僵尸进程与孤儿进程#
一句话答案#
子进程退出后父进程没调用 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 的那种