僵尸进程是子进程退出后父进程未调用wait/waitpid回收所致,核心在于父进程未履行回收义务,常见于忽略sigchld、信号处理不当、父进程死锁或崩溃等情况。

僵尸进程本身不运行,也不消耗资源,它只是子进程退出后、父进程还没“收尸”留下的一个空壳。关键不在子进程做了什么,而在于父进程有没有履行回收义务。
父进程没调用 wait 或 waitpid
这是最直接的原因。子进程调用 exit() 后,内核会保留它的 PID、退出码、运行时间等少量信息,等待父进程通过 wait() 或 waitpid() 主动读取并释放。如果父进程代码里压根没写这一步,或者写了但被注释掉、逻辑跳过,子进程就立刻变成 Z 状态。
- 常见于简单 fork 的 C 程序,只顾创建不管善后
- Shell 脚本中用 & 启动后台子进程,却忘了用 wait 等待或捕获退出状态
- 多线程程序中,主线程 fork 子进程后,其他线程误判为无需回收
父进程忽略或错误处理 SIGCHLD 信号
子进程终止时,内核会向父进程发送 SIGCHLD。默认行为是忽略该信号,所以父进程必须显式注册信号处理器,并在其中调用 waitpid(-1, &status, WNOHANG) 循环回收——否则一次只收一个,多个子进程退出就容易漏掉。
- 信号处理函数里只调用一次 waitpid(),而实际有多个子进程同时结束
- 信号被阻塞(如用 sigprocmask 屏蔽),导致回收逻辑迟迟不触发
- 使用 signal() 而非 sigaction() 注册,可能造成信号处理被重置或不可靠
父进程自身已失效或无法响应
即使逻辑正确,父进程若陷入死锁、无限循环、挂起(如被 ptrace 暂停)、或已崩溃,就彻底失去执行回收能力。此时子进程只能一直卡在僵尸状态,直到父进程重启或被 kill。
- 守护进程长期运行,某次更新引入资源竞争导致死锁
- 调试中用 gdb attach 后未继续执行,父进程停在断点上
- 父进程因内存不足被 OOM killer 终止,但子进程已在之前退出,来不及被 init 接管
父进程比子进程先退出的特殊情况
这种情况不会产生僵尸。父进程一退出,其所有子进程自动被 init(PID=1)接管。init 会定期调用 wait,所以这些孤儿子进程会被正常回收。真正危险的是:子进程先退出,而父进程还活着却没动作——init 不会插手“活父进程”的家务事。
- 父进程启动子进程后立即进入长耗时任务,期间子进程已结束
- 父进程忙于 I/O 或网络等待,信号处理被延迟甚至丢失
- 父子进程生命周期设计不合理,比如父进程预期子进程永远不退出,结果子进程异常终止











