真实系统比五状态机复杂:停止、恢复、白醒一场、不可中断睡眠。最值得记的是【白醒一场】——事件来了但 CPU 忙着,它醒了又得回队尾。这就是【唤醒延迟】,也是优先级存在的理由。
4.8 讲【没人跑的时候】,这一节讲【有人跑但轮不到你】的那些边角情况。这些情况不在教材的五状态机里,但在真机上天天发生。
一、多出来的四条路
运行 → 停止:SIGSTOP 或调试器。进程还在内存里,只是不动了。
停止 → 就绪:SIGCONT。回到队尾重新排队,不是接着上次的位置。
阻塞 → 被唤醒 → 又被抢:事件来了,但 CPU 被占着,它白醒一场。
阻塞(D):等硬件,连信号都不响应,是唯一【外力无效】的状态。
二、白醒一场是怎么回事
事件发生时,内核把进程从等待队列挪到就绪队列——这一步叫唤醒。 但如果这时 CPU 上有别的进程在跑(时间片没完),它只能等着。 等到轮上它的时候,可能又过了好几拍——这段时间就是【唤醒延迟】。 实测:一个每跑 1 拍就等 2 拍的进程,跟一个长任务抢 CPU,它的完成时刻会明显晚于【理论上它需要的拍数】。 这就是为什么【优先级】必须存在:有些活儿等不起,得允许它插到前面。
三、暂停与恢复的代价
SIGSTOP 之后进程的内存还在、文件描述符还在、状态都还在,只是不参与调度。
恢复时要重新排队,不保证立刻跑。而且它的缓存早就凉了。
这个机制的实际用途:调试器断点、Ctrl+Z 挂起、以及容器里的【暂停】功能(freezer cgroup)——暂停一整个容器,就是把里面所有进程都塞进 T 状态。
四、这些边角为什么值得学
因为真机上出问题,八成出在边角上。 【服务没响应但进程还在】——多半在 D 状态等 IO; 【进程杀了没死】——多半也是 D; 【响应忽快忽慢】——多半是唤醒延迟,被别的进程挤了。 五状态机告诉你【正常怎么走】,这些边角告诉你【不正常时长什么样】。
五、动手 Demo:各种各样的状态转换——真实系统比五个状态复杂
让一个短任务(每跑 1 拍等 2 拍)跟一个长任务抢 CPU,看短任务的完成时刻比它自己需要的拍数晚了多少。 晚的那部分,就是唤醒延迟。
ps = [Proc("A", 3, io_each=1, io_len=2), Proc("B", 30)]
done, sw, _tl, total = run(ps, quantum=8, ncpu=1)
print("A 占 CPU", ps[0].ran, "拍")
print("A 等", ps[0].waited, "拍")
print("A 完成于", ps[0].done_at, "拍")
实跑输出:
--------------------------------------------------------------------
各种各样的状态转换——真实系统比五个状态复杂
--------------------------------------------------------------------
① 真实系统里还会多出这几条路
多出来的转换 触发原因 实际后果
---------------------------------------------------------------------
运行 → 停止 SIGSTOP/调试器 ps 里变 T,不占 CPU
停止 → 就绪 SIGCONT 回到队列尾排队
阻塞 → 被唤醒 → 又被抢 事件来了但 CPU 忙 白醒一场,回队尾
不可中断睡眠 D 等硬件/某些 IO 连 kill -9 都杀不掉
**最后那条是运维的噩梦**:`D` 状态的进程你杀不死它,
只能等硬件回来或者重启。**它不是卡住,是在等一个必须等的东西。**
② 为什么会有【白醒一场】
A 每跑 1 拍就等 2 拍,B 一直跑
A 真正占 CPU 3 拍
A 等的时间 4 拍
A 完成的时刻 5 拍
**A 醒了之后不一定马上轮得到它**——B 的时间片还没完。
**所以【唤醒延迟】是延迟的一部分**,而且它取决于别人在干什么。
**这就是为什么【优先级】这种东西必须存在**:
**有些活儿等不起,得允许它插到队前面去。**
A 只需要 3 拍 CPU,但它完成得很晚——因为它每次醒来都要等 B 让出时间片。 这就是【响应时间】与【CPU 时间】的差别:用户感受到的是前者,而前者取决于别人在干什么。 所以【同样的程序,机器忙的时候慢】不是错觉——它的 CPU 时间没变,唤醒延迟变长了。 这条推论很有用:排查【为什么这个接口偶尔很慢】时,先看它是不是被同机的其他负载挤了。
30 秒实验
kill -STOP <pid>之后ps -o stat看 T,再kill -CONT,看它回到 S 还是 R。cat /proc/<pid>/status | grep -E "State|voluntary"——两个切换计数:主动让出与被动抢占。- 在容器里试
docker pause <容器>,然后ps看里面进程的状态(有 docker 的话)。
常见误区
- 【进程被暂停后内存会被释放】。不会。它只是不参与调度,内存、fd、状态全都留着。
- 【唤醒之后马上就能跑】。要排队。这就是唤醒延迟的来源。
- 【D 状态是内核 bug】。大多数时候是真实的硬件等待(坏盘、NFS 失联)。先查存储,再怀疑内核。
自测三问(点开对答案)
1. 什么是【白醒一场】?它的后果是什么? —— 事件来了、进程被唤醒进就绪队列,但 CPU 被占着,**只能等**。后果是**唤醒延迟**,它让响应时间取决于别人在干什么。 2. `SIGSTOP` 与 `SIGCONT` 之后,进程的哪些东西还在? —— 内存、文件描述符、全部状态都在,**只是不参与调度**;恢复后回队尾重新排队。 3. 为什么说【真机上的问题八成出在边角上】? —— 因为五状态机描述的是正常路径;**服务无响应、杀不死、响应忽快忽慢**,都对应 D 状态、唤醒延迟这些边角。- ☐ 4.10 吞吐量与延迟
相关笔记
- 状态转换 — 五条正路
- 吞吐量与延迟 — 唤醒延迟就是延迟的一部分
- 变更优先级 — 抢救等不起的活儿
- 🐧 Linux是怎样工作的 · 索引