在Linux内核里,不管是普通进程还是线程,都统一看作一个 task_struct 结构。一个进程结束,并不是直接没了,得先变成EXIT_DEAD状态,等着父进程来回收资源;如果还不回收,就会变成EXIT_ZOMBIE状态。到了这个状态,它还占着PID号和一小部分内存。要是父进程一直不调用wait()或者waitpid(),这些子进程就成了僵尸,僵在那里不走。 僵尸进程一多,会把系统拖垮。Linux把所有进程连在一起像棵树,根是init进程。PID的总数是有限的,由 /proc/sys/kernel/pid_max 来定。如果CPU数量不超过32,默认最大就是32768;要是CPU更多,那就默认是CPU数量乘以1024。一旦僵尸太多,PID很快就用完了,系统就没法再创建新进程了,服务自然就瘫痪。 容器时代也有了新的问题。容器其实就是一组进程组。要是没做限制,一个节点上能开的进程数也受pid_max控制。为了防止容器爆满导致系统崩溃,Cgroup提供了pids控制组,一般放在 /sys/fs/cgroup/pids 里。里面有个叫pids.max的文件,管理员可以自己设个数,超过这个数就不让创建新进程。 回收僵尸有两种办法。第一种是用wait(),父进程调用它后,内核会把所有的僵尸子进程全找出来一次性回收掉。不过如果当前没有僵尸,父进程就会一直卡住。第二种是用waitpid(),这个可以加个参数WNOHANG让它变成非阻塞的。如果没有僵尸它就立马返回,不会让父进程也僵在那里不动。很多轻量级的init系统比如tini就用这种方法每秒去清理一次。 想让你的应用离僵尸远点得这么做:定期用 ps -o stat= -o pid= | grep -w Z | wc -l 扫描一下,如果数不是零就赶紧报警;可以给关键服务绑定SIGCHLD信号处理函数,子进程退出时自动回收PID;或者用trap捕捉父脚本里的子进程退出信号;还可以用cgroup里的pids.max来做配额限制。 总的来说,僵尸进程看着可怕其实就是系统深处的资源黑洞。只要搞懂它是怎么回事、怎么收、PID数量怎么算,再配上监控和回收策略,就能把这种看不见的风险变成能看到的指标——这样服务就能跑得更稳、更远了。