第 6 章 · 03 进程状态机与僵尸进程 本节摘要:前两节你看到了 / 输出里那列 —— 、 、 、 、 几个字母来回出现。本节要把这些字母背后的「进程状态机」讲透。一个进程从诞生到消亡,会在几个状态之间流转:运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)。理解这个状态机,你才能解释「为什么 后进程还在」「为什么有的进程杀不掉(D 状态)」。本节的另一个重点是「僵尸进程」——它怎么产生的、为什么危险、怎么清理。这是生产运维的高频问题,也是面试的经典考点。 内容来源:综合 Linux 进程知识整理,套用体系化模板。 学习目标 阅读完本节,你应当能够: 画出进程的完整状态机:运行(R)、就绪、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)。
本节摘要:前两节你看到了
ps/top输出里那列STAT——R、S、D、Z、T几个字母来回出现。本节要把这些字母背后的「进程状态机」讲透。一个进程从诞生到消亡,会在几个状态之间流转:运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)。理解这个状态机,你才能解释「为什么kill后进程还在」「为什么有的进程杀不掉(D 状态)」。本节的另一个重点是「僵尸进程」——它怎么产生的、为什么危险、怎么清理。这是生产运维的高频问题,也是面试的经典考点。
内容来源:综合 Linux 进程知识整理,套用体系化模板。
阅读完本节,你应当能够:
kill -CHLD 或修复父进程来清理僵尸,理解为什么不能直接 kill 僵尸本身。很多人对进程的认知是二元的:「在跑」或「没在跑」。但真实的进程生命周期要复杂得多——它会在多个状态间流转,每个状态对应一种「正在等待什么」。
内核给每个进程维护一个状态。你在 ps/top 的 STAT 列看到的就是这个状态。理解状态机,能帮你回答这些排查中常遇到的疑问:
kill 了进程,为什么 ps 里还能看到它?」——它变成了僵尸,父进程没收尸。kill -9 都杀不掉,为什么?」——D 是不可中断睡眠,内核在等 IO,信号暂时处理不了。R(running/ready 运行或就绪):正在 CPU 上跑,或在就绪队列等 CPU。ps 里看到 R 说明它正忙。注意「R」包含了「正在跑」和「准备跑」两种,因为从用户视角两者无法区分。
S(interruptible sleep 可中断睡眠):进程在等某个事件(等输入、等网络、等定时器、等子进程)。这是大多数进程的常态——你看到 bash 的 STAT 是 Ss,意思是它在 S(睡眠等你敲键盘)。这种睡眠「可中断」——发信号(如 kill)能把它唤醒。
D(uninterruptible sleep 不可中断睡眠):进程在等一个内核态的 IO 操作(通常是磁盘 IO),且这个等待「不可中断」——连 kill -9 都杀不掉。这听起来反直觉,但设计如此:某些内核操作一旦开始就不能中途打断(否则会破坏数据结构)。D 状态通常很短暂;如果一个进程长期卡在 D,通常是磁盘或驱动出了问题。
⚠️ 注意:D 状态的进程
kill -9也无效。这不是 bug,是设计。唯一的解法是等它自己从 IO 返回,或解决底层 IO 问题(比如磁盘坏了就换磁盘)。
T(stopped/traced 停止):进程被暂停。常见来源:你按了 Ctrl+Z(发 SIGSTOP)、或调试器(gdb)attach 了它。fg/bg 或 kill -CONT 能让它恢复。
Z(zombie 僵尸):这是本节的重点,单独讲。
进程调用 exit() 退出时,内核会释放它占的内存、文件等资源,但保留它的进程表项(含 PID、退出码、资源统计),等父进程来「收尸」(调用 wait/waitpid 取走退出信息)。在父进程收尸之前,这个「已死但表项还在」的进程就是僵尸(Z)。
子进程 exit() ──> 内核释放资源, 但保留进程表项(含退出码) │ ▼ 变成僵尸 Z │ 父进程 wait() ──> 取走退出码, 表项被回收, 彻底消失
如果父进程「不收尸」(忘了调用 wait,或自己卡住了),子进程就一直停留在僵尸状态。
僵尸本身几乎不占 CPU 和内存(资源已释放),但它占一个进程表项(一个 PID)。系统 PID 数量有限(默认约 32768,可调),僵尸堆积会耗尽 PID,导致无法启动新进程——「fork: Resource temporarily unavailable」。
⚠️ 注意:不能直接
kill僵尸本身——它已经死了,kill对它无效。要清僵尸,得让它的父进程去收尸。
三种解法:
wait(如果是你写的程序,这是正解)。kill -CHLD 父PID,提醒它「你有子进程退出了,去收尸」。很多程序的 SIGCHLD 处理会触发 wait。init(PID 1)「领养」,init 会自动收尸,僵尸即消失。ps -o ppid= -p <僵尸PID> # 查僵尸的父进程 PID kill -CHLD <父PID> # 提醒父进程收尸 # 不行就: kill <父PID> # 杀父进程, 僵尸被 init 领养并清理
僵尸占的资源极少(CPU 为 0,内存几乎为 0)。它的危害是「占 PID 名额」。不要因为「僵尸」这名字吓人就觉得它在吃内存——它的真正风险是 PID 耗尽。
D 状态是内核等 IO,信号处理不了。反复 kill -9 没用,只会让你更焦虑。正确的做法是查它在等什么 IO:cat /proc/<PID>/stack 看内核栈,定位卡在哪;或查磁盘健康(dmesg、iostat)。
杀父进程能让僵尸消失,但父进程管理的其他工作也会中断。生产环境慎用——先确认父进程是什么、能不能重启。如果是某个服务,优先重启服务(systemctl restart)而非裸 kill。
kill -9 无效(设计如此,非 bug),要查底层 IO 问题。下一节讲信号与 kill——这是控制进程(终止、重载、暂停)的核心机制,也解释了为什么 D 状态杀不掉、SIGTERM 与 SIGKILL 的区别。