第 6 章 · 03 进程状态机与僵尸进程


文档摘要

第 6 章 · 03 进程状态机与僵尸进程 本节摘要:前两节你看到了 / 输出里那列 —— 、 、 、 、 几个字母来回出现。本节要把这些字母背后的「进程状态机」讲透。一个进程从诞生到消亡,会在几个状态之间流转:运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)。理解这个状态机,你才能解释「为什么 后进程还在」「为什么有的进程杀不掉(D 状态)」。本节的另一个重点是「僵尸进程」——它怎么产生的、为什么危险、怎么清理。这是生产运维的高频问题,也是面试的经典考点。 内容来源:综合 Linux 进程知识整理,套用体系化模板。 学习目标 阅读完本节,你应当能够: 画出进程的完整状态机:运行(R)、就绪、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)。

第 6 章 · 03 进程状态机与僵尸进程

本节摘要:前两节你看到了 ps/top 输出里那列 STAT——RSDZT 几个字母来回出现。本节要把这些字母背后的「进程状态机」讲透。一个进程从诞生到消亡,会在几个状态之间流转:运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)。理解这个状态机,你才能解释「为什么 kill 后进程还在」「为什么有的进程杀不掉(D 状态)」。本节的另一个重点是「僵尸进程」——它怎么产生的、为什么危险、怎么清理。这是生产运维的高频问题,也是面试的经典考点。

内容来源:综合 Linux 进程知识整理,套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 画出进程的完整状态机:运行(R)、就绪、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)。
  2. 解释每个状态意味着什么、在什么场景下出现。
  3. 说清僵尸进程的产生原因:子进程结束但父进程没回收。
  4. 解释为什么 D 状态的进程「杀不掉」(不可中断睡眠,通常在等内核 IO)。
  5. kill -CHLD 或修复父进程来清理僵尸,理解为什么不能直接 kill 僵尸本身。

一、设计动机:进程不是「活着」或「死了」那么简单

很多人对进程的认知是二元的:「在跑」或「没在跑」。但真实的进程生命周期要复杂得多——它会在多个状态间流转,每个状态对应一种「正在等待什么」。

内核给每个进程维护一个状态。你在 ps/topSTAT 列看到的就是这个状态。理解状态机,能帮你回答这些排查中常遇到的疑问:

  • kill 了进程,为什么 ps 里还能看到它?」——它变成了僵尸,父进程没收尸。
  • 「这个进程 STAT 是 D,kill -9 都杀不掉,为什么?」——D 是不可中断睡眠,内核在等 IO,信号暂时处理不了。
  • 「系统里堆了几百个僵尸,要紧吗?」——要紧,每个僵尸占一个 PID,堆积会耗尽进程表。

二、进程状态机详解

各状态详解

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/bgkill -CONT 能让它恢复。

Z(zombie 僵尸):这是本节的重点,单独讲。

三、僵尸进程:产生、危害、清理

僵尸是怎么产生的

进程调用 exit() 退出时,内核会释放它占的内存、文件等资源,但保留它的进程表项(含 PID、退出码、资源统计),等父进程来「收尸」(调用 wait/waitpid 取走退出信息)。在父进程收尸之前,这个「已死但表项还在」的进程就是僵尸(Z)。

子进程 exit() ──> 内核释放资源, 但保留进程表项(含退出码) │ ▼ 变成僵尸 Z │ 父进程 wait() ──> 取走退出码, 表项被回收, 彻底消失

如果父进程「不收尸」(忘了调用 wait,或自己卡住了),子进程就一直停留在僵尸状态。

僵尸的危害

僵尸本身几乎不占 CPU 和内存(资源已释放),但它占一个进程表项(一个 PID)。系统 PID 数量有限(默认约 32768,可调),僵尸堆积会耗尽 PID,导致无法启动新进程——「fork: Resource temporarily unavailable」。

怎么清理僵尸

⚠️ 注意:不能直接 kill 僵尸本身——它已经死了,kill 对它无效。要清僵尸,得让它的父进程去收尸。

三种解法:

  1. 修复父进程:让父进程调用 wait(如果是你写的程序,这是正解)。
  2. 给父进程发 SIGCHLD:kill -CHLD 父PID,提醒它「你有子进程退出了,去收尸」。很多程序的 SIGCHLD 处理会触发 wait。
  3. 杀掉父进程:如果父进程是个失控的程序,杀掉它。父进程死后,僵尸会被 init(PID 1)「领养」,init 会自动收尸,僵尸即消失。
ps -o ppid= -p <僵尸PID> # 查僵尸的父进程 PID kill -CHLD <父PID> # 提醒父进程收尸 # 不行就: kill <父PID> # 杀父进程, 僵尸被 init 领养并清理

四、踩坑与排错

坑 1:误以为僵尸占很多资源

僵尸占的资源极少(CPU 为 0,内存几乎为 0)。它的危害是「占 PID 名额」。不要因为「僵尸」这名字吓人就觉得它在吃内存——它的真正风险是 PID 耗尽。

坑 2:D 状态进程 kill -9 无效就反复重试

D 状态是内核等 IO,信号处理不了。反复 kill -9 没用,只会让你更焦虑。正确的做法是查它在等什么 IO:cat /proc/<PID>/stack 看内核栈,定位卡在哪;或查磁盘健康(dmesgiostat)。

坑 3:杀父进程清僵尸的副作用

杀父进程能让僵尸消失,但父进程管理的其他工作也会中断。生产环境慎用——先确认父进程是什么、能不能重启。如果是某个服务,优先重启服务(systemctl restart)而非裸 kill。

本节要点回顾

  1. 进程状态机:R(运行/就绪)、S(可中断睡眠,常态)、D(不可中断睡眠,等内核 IO,杀不掉)、T(停止)、Z(僵尸)。
  2. S 是大多数进程的常态(等输入/网络/定时器);R 说明正忙
  3. D 状态 kill -9 无效(设计如此,非 bug),要查底层 IO 问题。
  4. 僵尸是「子进程退出但父进程没 wait 收尸」,占资源少但占 PID,堆积会耗尽进程表。
  5. 清僵尸不能 kill 僵尸本身(它已死),要修父进程、给父进程发 SIGCHLD,或杀父进程让 init 领养。

下一节讲信号与 kill——这是控制进程(终止、重载、暂停)的核心机制,也解释了为什么 D 状态杀不掉、SIGTERM 与 SIGKILL 的区别。


发布者: 作者: 灏天文库 转发
评论区 (0)
U