僵尸进程不占cpu内存但占pid,主因是父进程未调用wait();用strace -p -e trace=wait,waitpid,wait4,waitid -f跟踪父进程wait调用,并结合signal追踪定位sigchld处理缺陷。

僵尸进程本身不占用CPU或内存资源,但会持续占用进程表项(PID)和少量内核结构,若大量堆积,可能导致系统无法创建新进程。根本原因在于子进程已终止,但父进程尚未调用 wait() 或 waitpid() 获取其退出状态。要确认问题是否出在父进程“忘记收尸”,strace 是最直接的动态观测工具。
明确目标:盯住父进程的 wait 系统调用
僵尸进程的生命周期卡在“已终止、未被 wait”这一步。因此排查重点不是僵尸进程自身(它已不运行),而是它的父进程——看它是否调用、何时调用、为何阻塞或跳过 wait 系列系统调用。
- 使用
ps -o pid,ppid,stat,comm快速定位僵尸进程(STAT 列含Z)及其父进程 PID - 对父进程 PID 执行
strace -p <ppid> -e trace=wait,waitpid,wait4,waitid -f</ppid>,聚焦等待相关系统调用 -
-f参数很重要:若父进程是多线程或多子进程模型(如服务主进程 fork 出工作进程),需跟踪所有子线程/子进程的系统调用
常见 strace 输出模式与含义
观察 strace 实时输出,以下几种情况最具诊断价值:
-
wait4(-1, NULL, WNOHANG, NULL) = 0:父进程在非阻塞模式下轮询,但当前无子进程可回收 —— 属正常行为,需结合频率判断是否过于频繁或完全缺失 -
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = <pid></pid>:成功回收某子进程,返回其 PID 和退出状态 —— 表明 wait 逻辑存在且有效 - 长时间无任何 wait 相关输出:父进程可能压根没实现 wait 逻辑(如简单 fork 后不处理)、或逻辑被条件分支跳过(例如只在特定信号下 wait)
-
wait4(-1, 0x..., 0, NULL) = -1 EINTR (Interrupted system call):被信号中断,通常会重试;若频繁出现且无后续重试,可能信号处理函数覆盖了默认行为
配合信号追踪,识别 wait 触发时机
很多服务采用“信号驱动 wait”模式:子进程终止时向父进程发送 SIGCHLD,父进程在信号处理函数中调用 wait。若信号处理函数注册失败、被覆盖或未正确安装,wait 就永远不会执行。
- 追加
-e trace=signal,sigreturn到 strace 命令中,观察SIGCHLD是否送达父进程 - 检查输出中是否有
sigaction(SIGCHLD, {...}, ...)或signal(SIGCHLD, ...)调用,确认处理函数已注册 - 若看到
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, ...} ---但之后无wait4,说明信号处理函数内部未调用 wait 或逻辑有缺陷
验证修复后的 wait 行为
修改代码(如补全 signal handler 中的 wait 循环)或调整配置后,不要仅依赖 ps 查看僵尸是否消失——那只是结果。应再次用 strace 观察父进程是否开始稳定调用 wait4 并成功回收:
- 理想输出:每次子进程退出后,数毫秒内出现一条带具体 PID 和状态的
wait4成功返回 - 注意避免“单次 wait 只收一个”陷阱:正确做法是在
SIGCHLD处理函数中用while ((pid = waitpid(-1, &status, WNOHANG)) > 0)循环回收,否则多个子进程退出时只收走第一个,其余变僵尸 - 若修复后 strace 仍无 wait 调用,检查是否加载了旧二进制、进程未重启、或信号被
sigprocmask屏蔽
不复杂但容易忽略:僵尸不是病灶,是症状;strace 看父进程的 wait,才是揪出病因的最快路径。











