🐧 第四章 进程调度器
📄 一句话概括

Linux 用五个字母描述进程:R 在跑或想跑、S 在等、D 在等硬件、T 被停住、Z 已死待收。关键不是背字母,是知道【状态就是它挂在哪个队列】。

📝 本节和前面几节的关系

4.5 讲换人要花钱,那什么时候该换?答案是【状态变了的时候】。这一节先把状态这几个名字立住。

一、五个字母,各是什么

R(Running/Runnable):正在 CPU 上跑,或者在就绪队列里排队。它想跑,也随时能跑。 S(Interruptible Sleep):在等一个事件(IO 完成、信号、定时器)。能被信号叫醒,所以叫可中断。 D(Uninterruptible Sleep):在等硬件,连 kill -9 都杀不掉,只能等它自己回来。 T(Stopped):被 SIGSTOP 或调试器停住。它没死,只是不动。 Z(Zombie):已经结束,但父进程还没读它的退出码。只剩一条记录。

二、状态不是标签,是队列

这句话是理解调度的关键:内核里没有【状态变量】这种浪漫的东西,它就是几个链表。 就绪队列:所有 R 状态、等着被挑的进程。调度器只从这个队列里挑人。 等待队列:每个可能发生的事件都有一条(等磁盘、等网络、等锁、等定时器)。事件发生时,内核把对应的进程从等待队列挪到就绪队列——这就叫【唤醒】。 所以【状态转换】本质是【换队列】。而【唤醒】只是给了排队资格,不是给了 CPU。

三、为什么 S 状态的进程最多

因为程序大部分时间都在等。等磁盘、等网络、等用户输入、等定时器。 一份 ps aux 里九成的进程是 S,这很正常,不是故障。 反过来,如果一台机器上 R 状态的进程特别多(比如是核数的好几倍),说明 CPU 真的不够用——大家在排队。 这个观察在排查时很好用:ps -eo stat | sort | uniq -c 一眼看出分布。

四、D 状态为什么可怕

因为它【不可中断】。S 状态你能用信号叫醒,D 状态连内核都不响应信号——它在等一个必须等完的硬件操作。 常见成因:磁盘坏道、NFS 服务器失联、某些驱动 bug。 后果:进程杀不死、卸载文件系统卸不掉、关机卡住。只能等硬件回来或者硬重启。 所以看到一堆 D,先查存储和网络文件系统,别急着调调度器。

五、动手 Demo:进程的状态——五个名字就够描述一切

这一节把五个状态码与【它挂在哪个队列】对上,再补一张【谁来动它】的表。 看清一件事:状态不是标签,是位置。

# Linux 的进程状态码(ps 的 STAT 列首字母)
STATES = {
    "R": "运行中/就绪——正在跑,或排队等着跑",
    "S": "可中断睡眠——在等事件,能被信号叫醒",
    "D": "不可中断睡眠——在等硬件,kill -9 也杀不掉",
    "T": "停止——被 SIGSTOP 或调试器停住",
    "Z": "僵尸——已结束,等父进程读退出码",
}
for k, v in STATES.items():
    print(k, v)

实跑输出:

--------------------------------------------------------------------
进程的状态——五个名字就够描述一切
--------------------------------------------------------------------
① Linux 上真实的状态码

  状态             ps 里显示     含义
  ----------------------------------------------------------
  运行中/就绪     R             正在跑,或排队等着跑
  可中断睡眠       S             在等事件(IO 完成、信号)
  不可中断睡眠     D             在等硬件,连 kill 都杀不掉
  停止             T             被 SIGSTOP 或调试器停住
  僵尸             Z             已结束,等父进程读退出码

  **R 与 S 的区别最值得记**:R 是【想跑】,S 是【在等】。
  **一台机器上绝大多数进程都是 S**——因为大部分时间它们都在等。

② 状态不是标签,是【它在哪个队列里】

  状态     挂在哪                   谁来动它
  -----------------------------------------------------
  R        就绪队列 / CPU 上        调度器
  S        等待队列(某个事件)     事件发生的内核代码
  Z        进程表里的一条记录       父进程 wait

  **所以【状态转换】本质是【换队列】。** 
  **从等待队列挪到就绪队列,就叫【被唤醒】。**

R 与 S 的区别最值得记:R 是【想跑】,S 是【在等】。一台机器上绝大多数进程都是 S。 Z 与 T 的区别也很实用:Z 是已经死了、只是没被收尸;T 是活着但不动(可以被 SIGCONT 恢复)。 这五个字母你会在 ps、top、/proc/<pid>/status 里反复见到,而且它们是排查问题的第一手线索——比看 CPU 占用率更早暴露问题。

📋 在真 Linux 上数一遍:ps -eo stat | cut -c1 | sort | uniq -c | sort -rn,你会看到 S 占绝大多数,R 只有几个。

30 秒实验

  1. ps -eo stat,pid,comm | head -20,逐个猜它们是 R 还是 S,再想想为什么。
  2. sleep 300 & 然后 ps -o stat,cmd -p $!——看后台的 sleep 是 S 还是 R。
  3. cat /proc/self/status | grep State——self 指向正在读它的那个进程。

常见误区

  1. 【状态是内核给进程贴的标签】。它是【进程挂在哪个队列】的直接体现。调度器只从就绪队列里挑人。
  2. 【D 状态是卡死了】。它在等一个真实的硬件操作。杀掉它没用,得解决硬件或那个 IO。
  3. 【S 状态的进程是在偷懒】。它在等。等的时间也是用户等的时间,一点没省。
自测三问(点开对答案) 1. 五个状态码各是什么?哪个最常见? —— R 运行/就绪、S 可中断睡眠、D 不可中断睡眠、T 停止、Z 僵尸。**S 最常见**,因为程序大部分时间在等。 2. 为什么说【状态就是它挂在哪个队列】? —— 内核用几个链表管理进程:就绪队列装 R,等待队列装 S/D。**唤醒就是换队列**,而换队列不等于拿到 CPU。 3. D 状态为什么连 kill -9 都杀不掉? —— 它在等不可中断的硬件操作,**内核不在这里检查信号**。只能等操作完成或硬重启。

相关笔记