想在 Linux 系统里弄明白那些看不见的资源黑洞,也就是僵尸进程,得先看一下它们最后怎么“走完”。不管是普通进程还是线程,内核里都把它们当成 task_struct 这种数据结构。一个进程要是退出,它得走两步,先是变成 EXIT_DEAD,逻辑上算是完结了,就等老爸把资源收走。要是老爸不吭声,那它就会进入 EXIT_ZOMBIE 状态,这时候离彻底没了更近了,但还占着 PID 号和一点儿内存。这时候要是父进程还不调用 wait() 或者 waitpid(),子进程就一直卡在那儿不动了。 这种情况多了很要命。Linux 把所有进程都排成一棵树,树根是 init,每个节点都有个 PID 号。这东西数量有限,系统默认最多就是 32768 个(CPU 少的情况)或者 CPU 核心数乘 1024 个。要是僵尸进程一下子冒出来好多,PID 很快就被用光了,系统就没法再启动新的东西了。 到了容器时代,这事儿又多了一层限制。容器其实就是一组进程堆在一起跑。以前没限制的时候也是看宿主系统的 PID 上限,但现在有了 Cgroup 提供的 pids 控制组,管理员可以直接把 pids.max 文件调出来设个最高限额。这样超过了限制就不让创建新进程了。 要想回收僵尸进程有两种办法:wait() 和 waitpid()。wait() 看着简单但容易卡住。它会遍历所有子进程把僵尸全给收了,如果这会儿没有僵尸,这一调用就得一直等着。而 waitpid() 有个好功能叫 WNOHANG,让它在没僵尸的时候立马返回去干别的事。很多轻量级的 init 系统就是用这种方法每秒轮询一次来清理僵尸的。 想让自己的应用别被僵尸海淹没,可以这么干:定期扫描看看有没有僵尸(用 ps -o stat= -o pid= | grep -w Z | wc -l),要是有就赶紧报警;或者给服务绑定一个 SIGCHLD 处理函数,或者用 trap 命令捕捉子进程退出的信号自动回收 PID;还可以用 cgroup 的 pids.max 设个上限并留下审计日志。 总结下来僵尸进程就是潜伏在系统深处的资源黑洞。只要弄明白它是怎么回事、PID 有多少、怎么回收这些机制,再配上合理的监控和回收策略,就能把看不见的风险变成能看明白的指标——这样你的服务就能跑得更稳、更远了。