init(PID=1)收不掉僵尸进程,因其可能未安装SIGCHLD处理函数,或虽安装但未循环调用waitpid(-1, &status, WNOHANG)回收所有退出子进程,尤其在busybox旧版本或自定义init中常见。

为什么 init(PID=1)收不掉僵尸进程
Linux 中,子进程退出后若父进程未调用 wait() 或 waitpid() 获取其退出状态,该子进程会变成僵尸进程(Z 状态)。正常情况下,当父进程退出,内核会将孤儿进程的父进程设为 init(PID=1),由它负责回收——但前提是 init 正确响应 SIGCHLD 并调用 wait 类系统调用。
问题在于:如果 init 进程本身没有安装 SIGCHLD 信号处理函数,或安装了但未在 handler 中调用 waitpid(-1, &status, WNOHANG) 循环收割,就会漏掉部分僵尸进程。尤其在使用自定义 init(如 busybox init、systemd 替代品、或容器中精简 init)时,这个逻辑极易出错。
busybox init 的 SIGCHLD 处理缺陷
老版本 busybox(如 1.30.x 之前)的 init 默认不注册 SIGCHLD handler;即使注册了,也只调用一次 waitpid(),无法处理并发退出的多个子进程——导致仅回收第一个,其余滞留为僵尸。
- 现象:
ps aux | grep 'Z'持续出现,且父 PID 均为1 - 验证:运行
strace -p 1 -e trace=signal,waitpid,观察是否收到SIGCHLD、是否调用waitpid,以及返回值是否为-1(ECHILD)或正数(成功回收一个) - 修复方式取决于 busybox 版本:
– ≥1.31.0:默认启用循环 wait,需确认编译时开启CONFIG_FEATURE_INIT_SCTTY和CONFIG_FEATURE_INIT_SYSLOG不影响主逻辑
– <1.31.0:必须打补丁或升级,无安全 workaround
systemd 作为 init 时的 ReapZombie 行为
systemd 理论上会自动收割僵尸,但它依赖 notify 机制和 cgroup v1/v2 的进程生命周期跟踪。若 systemd 启动参数中禁用了子进程监控(如 systemd.legacy_systemd_cgroup_controller=0),或容器环境未正确挂载 cgroupfs,可能导致僵尸“不可见”于 systemd 的回收路径。
- 检查点:
cat /proc/1/cmdline | tr '\0' ' '确认是 systemd;再查systemctl show --property=ReapZombie(应为yes) - 关键限制:systemd 只对它“知道”的子进程(即通过
fork()+exec且未脱离 cgroup 的进程)负责;用clone()创建的线程、或手动setpgid(0,0)脱离会话的进程,可能逃逸回收 - 临时缓解:
echo 1 > /proc/sys/kernel/child_subreaper可设当前 shell 为 subreaper,但仅对后续 fork 生效,不能清理已有僵尸
如何定位是 init 信号处理 bug 而非应用层泄漏
先排除用户进程自身未 wait 的情况,再聚焦 PID=1。核心判断依据是:僵尸的 PPID 确实为 1,且持续存在超过数秒。
- 确认僵尸归属:
ps -o pid,ppid,stat,comm -C '[""]' | awk '$3 ~ /Z/ && $2 == 1' - 检查 init 是否在收:
grep -i 'sigchld\|wait' /proc/1/status(看 SigBlk/SigCgt 字段);更直接的是cat /proc/1/status | grep -E '^(SigQ|SigPnd)',若SigQ中有未决SIGCHLD,说明信号被阻塞或未处理 - 不要依赖
kill -s SIGCHLD 1测试——POSIX 规定 PID=1 忽略所有未显式捕获的信号,包括SIGCHLD,除非它真的装了 handler
真正棘手的不是 init 收不掉,而是 init 根本没机会收到信号:比如内核调度延迟、cgroup 事件丢失、或 init 自身卡在不可中断睡眠(D 状态)。这时候得看 /proc/1/stack 和 dmesg 中是否有相关警告。










