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

合法的转换只有五条:就绪→运行、运行→就绪、运行→阻塞、阻塞→就绪、运行→僵尸。最有用的一条是【阻塞→运行不合法】——醒来不等于立刻跑,只是有了排队资格。

📝 本节和前面几节的关系

4.6 立住了五个状态,这一节把状态之间的路画出来,并且重点讲【哪些路不通、为什么不痛】——不合法的那几条才是理解的试金石。

一、五条合法的路

就绪 → 运行:被调度器选中。这是唯一一条【进入 CPU】的路。 运行 → 就绪:时间片用完,或者被更高优先级的抢占。这是分时的核心动作。 运行 → 阻塞:自己主动让出(等 IO、等锁、sleep)。注意是主动的。 阻塞 → 就绪:等的事件发生了。注意终点是【就绪】不是【运行】。 运行 → 僵尸:自己退出或被信号杀死。这条路没有回头。

二、三条不合法,比合法的更有信息量

阻塞 → 运行:不合法。醒来只是【有了资格】,还得排队。这条最容易被想错——很多人以为事件一发生进程就接着跑。 就绪 → 阻塞:不合法。没在跑,没资格去等 IO——IO 请求是【跑起来之后】才发出去的。 僵尸 → 就绪:不合法。死不能复生。僵尸的唯一出路是被 wait 掉。 把这三条记牢,你看调度日志、看 ps 状态跳变时,就不会做出错误推断。

三、为什么【醒来不能直接跑】

因为 CPU 只有一个(或几个),而醒来的可能有很多个。 如果事件一发生就让它跑,那就是【谁醒谁插队】——调度器就失去了对【谁先跑】的控制权。 插队的后果:一个爱睡爱醒的进程能反复抢占 CPU,把老实干活的饿死。 所以必须经过就绪队列,让调度器统一决定顺序。代价是【唤醒延迟】——醒了还得等排到。

四、真实系统里多出来的路

运行 → 停止(SIGSTOP/调试器),停止 → 就绪(SIGCONT)。 阻塞 → 被唤醒 → 又被抢:事件来了,但 CPU 忙着,它白醒一场回队尾。 不可中断睡眠(D):这条路连信号都进不去,是唯一一条【外力无效】的路。 这些额外路径的存在,说明状态机是【内核实现】而不是【教材抽象】——教材画五条,是因为这五条足以解释绝大部分现象。

五、动手 Demo:状态转换——哪些转换合法

先把五条合法的路列出来,再把三条不合法的一条条说明为什么。 这一节的产出是一张【能贴在墙上的表】。

legal = {
    ("就绪", "运行"): "被调度器选中",
    ("运行", "就绪"): "时间片用完 / 被抢占",
    ("运行", "阻塞"): "等 IO、等锁、sleep",
    ("阻塞", "就绪"): "等待的事件发生了",
    ("运行", "僵尸"): "自己退出或被杀死",
}

bad = [("阻塞", "运行"), ("就绪", "阻塞"), ("僵尸", "就绪")]

实跑输出:

--------------------------------------------------------------------
状态转换——哪些转换合法
--------------------------------------------------------------------
① 合法的转换就这五条

  从       到       触发原因
  --------------------------------------
  就绪     运行     被调度器选中
  运行     就绪     时间片用完 / 被抢占
  运行     阻塞     等 IO、等锁、sleep
  阻塞     就绪     等待的事件发生了
  运行     僵尸     自己退出或被杀死

② 不合法的转换,才是这一节的重点

  从       到       为什么不合法
  -------------------------------------------------
  阻塞     运行     必须先进就绪队列排队,不能插队
  就绪     阻塞     没在跑,没法去等 IO
  僵尸     就绪     死不能复生

  **【阻塞 → 运行】不合法这一条,是理解调度的关键**:
  **醒来不等于立刻跑**,它只是【有了排队资格】——
  **什么时候真跑,由调度器说了算。**

五条合法转换里,只有一条是【进入 CPU】的——所以一个进程能跑多久、什么时候能跑,完全由【就绪 → 运行】这一步说了算。 三条非法转换里,【阻塞 → 运行】最重要:它把【唤醒】与【被调度】分开,这正是调度器能控制顺位的前提。 注意【运行 → 阻塞】是主动的:进程自己调 read、自己 sleep。主动让出与被动抢占,是两回事——前者是它知道要等,后者是别人要跑。

📋 在真 Linux 上看状态跳变:watch -n 0.2 'ps -o stat,cmd -p <pid>',或者 cat /proc/<pid>/status | grep State 反复读。一个在做 IO 的进程,你会看到 S 与 R 来回跳。

30 秒实验

  1. 起一个 while true; do echo x; sleep 1; done,用 watch ps 看它的状态在 R/S 之间跳。
  2. kill -STOP <pid> 然后 ps -o stat 看它变 T;再 kill -CONT 看它回 S。
  3. cat /proc/<pid>/wchan——它告诉你这个进程在等什么(内核函数名)。

常见误区

  1. 【IO 完成了进程就接着跑】。它只是从等待队列挪到就绪队列。什么时候真跑,得排队。
  2. 【被抢占和主动让出是一回事】。主动让出(等 IO)是自己知道要等;抢占是时间片到了或者别人优先级更高。两者对缓存的影响不同。
  3. 【僵尸能被 kill 掉】。僵尸已经死了,没有任何代码在跑,信号无处可送。只能靠父进程 wait。
自测三问(点开对答案) 1. 五条合法转换是什么?哪一条是【唯一进入 CPU】的路? —— 就绪→运行、运行→就绪、运行→阻塞、阻塞→就绪、运行→僵尸。**只有【就绪→运行】能进 CPU。** 2. 为什么【阻塞→运行】不合法?这条的意义是什么? —— 醒来只是有了排队资格。**这条保证调度器掌握【谁先跑】的决定权**,否则爱醒的进程能反复插队。 3. 【运行→阻塞】是谁发起的?和【运行→就绪】有什么区别? —— 前者由进程自己发起(调 read/sleep,主动让出);后者是时间片用完或被抢占(被动)。

相关笔记