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

没有进程可跑的时候,CPU 并没有【歇着】,它在跑空闲进程——一条等中断的指令。这么设计是为了让调度器永远有活可挑,省掉一堆【没活时怎么办】的特殊分支。

📝 本节和前面几节的关系

4.7 画完了状态机,这一节补一个容易被漏掉的状态:没人可跑的时候。它不在教材的状态机里,但在真实系统里一直存在。

一、没有可跑进程时,CPU 在干什么

它没有停。它执行一条【等中断】的指令(x86 上是 hlt,ARM 上是 wfi),CPU 进入低功耗状态,等下一个中断把它叫醒。 中断来了(定时器、网卡、磁盘),它就醒,看看有没有活儿,没有就继续等。 所以【空闲】不是【停止】,是【等】——而且这个【等】也在一个进程里。

二、为什么必须做成【一个进程】

因为调度器的主循环需要【永远有东西可挑】。 如果允许【就绪队列为空】,调度器就要写一堆特殊分支:空的时候怎么办、什么时候再看一眼、被谁叫醒。 把空闲做成一个进程(Linux 上是 0 号进程 swapper),主循环就永远能挑到一个——挑到它,就执行 hlt 等中断。 这是很典型的系统设计手法:把一个边界情况变成一个正常情况,代码就少一半。

三、两种说法的差别

【CPU 在跑 idle 进程】:从调度器角度,永远有活。好处是代码简单。 【CPU 闲着】:从人的角度,没干有用的事。好处是直观。 两种说法描述同一件事,只是站的位置不同。看 top 里的 id 那一列时,你在用后一种说法。 这件事有个实际后果:ps 里看到的进程数不包含 idle(它是内核线程,通常看不见),所以【进程数】与【谁在跑】不是一回事。

四、空闲不是浪费,是能力

一台机器的空闲率,代表它还有多少余量。空闲 0% 的机器不是健康的机器,是没余量的机器。 所以容量规划看的不是【峰值有多高】,是【峰值时还剩多少】——剩得少,下一次流量小高峰就会打满。 反过来,空闲率长期 90% 以上的机器,是在浪费钱——可以合并、可以缩容。

五、动手 Demo:空闲状态——没有进程可跑时,CPU 在干什么

放一个进程进去,让它跑 2 拍就等 30 拍,然后数一数时间线上有多少格是空的。 这些空格就是【idle 进程】在跑的时候。

ps = [Proc("A", 4, io_each=2, io_len=30)]
done, sw, tl, total = run(ps, quantum=4, ncpu=1, horizon=40)

idle = sum(1 for _t, names in tl if names[0] == "-")
print("观察", len(tl), "拍,空着", idle, "拍,占", idle / len(tl))

实跑输出:

--------------------------------------------------------------------
空闲状态——没有进程可跑时,CPU 在干什么
--------------------------------------------------------------------
① 一个进程,跑 2 拍就等 30 拍

      总观察时间                  33 拍
      CPU 空着的时间              29 拍
      空着占比                    88%

  **这时候 CPU 没有【歇着】——它在跑【空闲进程】。**
  **Linux 上就是 0 号进程(swapper)的 idle 循环**:
  执行一条【等中断】的指令,来中断就醒,看看有没有活儿,没有就继续等。

② 为什么要有【空闲进程】这个说法

  说法              物理事实           为什么这么讲
  ------------------------------------------------------------
  CPU 在跑 idle     执行等中断指令     调度器眼里永远有活可干
  CPU 闲着          同样在等中断       从人看的角度更直观

  **两种说法描述的是同一件事,只是站的位置不同。**
  **调度器要保证【永远有东西可挑】——所以空闲也必须是个进程。**
  **否则调度器就得写一堆【没有就绪进程时怎么办】的特殊分支。**

空着的那些拍占比很高——因为 30 拍的等待远长于 2 拍的计算。 这些拍在真机上就是 top 里的 id(idle 的百分比)。你在 top 上看到的 95% id,就是这张时间线上 95% 的空格。 【空闲】也要有人记账:不记账的话,top 里的百分比加起来就不是 100%。 记住这句:空闲 0% 不是健康,是没余量;空闲长期 90% 是浪费钱。

30 秒实验

  1. top 看 id 那一列,再对比 vmstat 1 的 id——两个数应该接近。
  2. cat /proc/stat | head -1 看 idle 字段的累计值,隔几秒再读一次,相减除以增量。
  3. ps -eLo pid,psr,stat,comm | grep -c " R" 数一下有几个进程真在跑。

常见误区

  1. 【CPU 不用的时候是关掉的】。它在执行等中断的指令,随时能被叫醒。真关掉再启动要几毫秒,那就没法用了。
  2. 【空闲率高的机器一定有问题】。可能它本来就没什么活。要看的是【峰值时还剩多少】。
  3. 【idle 进程占 CPU】。它占的是【时间】,不占算力——hlt 期间 CPU 基本不耗电(相对而言)。
自测三问(点开对答案) 1. 没有可跑进程时,CPU 在做什么? —— 在跑空闲进程,执行等中断指令(`hlt`/`wfi`),进低功耗,等中断叫醒。 2. 为什么非要把空闲做成一个进程? —— 让调度器永远有东西可挑,**省掉【就绪队列为空怎么办】的一堆特殊分支**。这是把边界情况变成正常情况的典型手法。 3. 空闲率该怎么看才对? —— 看【峰值时还剩多少】,而不是看平均值。**0% 说明没余量,长期 90% 说明在浪费钱。**

相关笔记