调度(下) — 按比例分 CPU,与多核带来的新麻烦
05 调度(下) — 按比例分 CPU,与多核带来的新麻烦
1. 这一章讲什么
MLFQ 优化的还是「快慢」;还有一条完全不同的路线:保证份额—— A 该拿 75%、B 该拿 25%,不管谁长谁短。本章前半讲这条线上的三个算法: 彩票调度(随机)、步幅调度(确定)、Linux 的 CFS(现代默认)。 后半把舞台搬到多核:CPU 从 1 个变 N 个,单机调度的每个老答案都要重写。
在全书链条里的位置:这是 CPU 虚拟化部分的收尾。对话章里教授点出本部分的性格: 操作系统相当偏执——「paranoia rules the day」——想确保自己始终掌控机器1。
2. 顶层全景
份额路线:
彩票调度 ── 每片时间开奖,票多者中,概率上逼近份额
步幅调度 ── 步幅 = 大数/票数,谁 pass 小谁先跑,周期末份额精确
CFS ── 谁虚拟运行时间(vruntime)少谁先跑,公平内建于数据结构
│
多核路线:
SQMS(单队列)── 简单但锁争用严重 + 进程在 CPU 间乱跳
MQMS(每 CPU 一队列)── 可扩展且亲和,但会负载不均
└──▶ 迁移 / 工作窃取:把作业搬去闲的 CPU图说:左半回答「怎么公平地分」,右半回答「分的时候发现有好几个发令员怎么办」。
3. 核心原理
3.1 主线例子:75/25 的两种实现
**彩票调度。**A 持 75 张票,B 持 25 张,共 100 张。每个时间片,
调度器从 0–99 抽一个中奖号:A 拿 0–74,B 拿 75–99,
中奖号落在谁区间谁上机。书里的真实模拟输出:连续 20 次开奖号码是
63 85 70 39 76 17 29 41 36 39 10 99 68 83 63 62 43 0 49 12——
B 只中 4 次(20%),而不是期望的 25%2。概率上的正确,不是保证上的正确;
跑得越久,越接近期望。实现惊人地简单:一个随机数、一条进程链表、
一个累加器——从头遍历,累计票数第一次超过中奖号时停,停在哪就是谁3。
**步幅调度(stride scheduling)。**同一个例子改成确定性:取一个大数 10000,步幅 = 10000 ÷ 票数,得 A=100、B=200、C(假设有,250 票)=40。 每个进程有累计值 pass,初始全 0:每次选 pass 最小的跑,跑完 pass += 步幅。 书里给的轨迹(节选)4:
Pass(A)=0 Pass(B)=0 Pass(C)=0 → 跑 A(随意选,同低)
Pass(A)=100 Pass(B)=0 Pass(C)=0 → 跑 B
Pass(A)=100 Pass(B)=200 Pass(C)=0 → 跑 C
Pass(A)=100 Pass(B)=200 Pass(C)=40 → 跑 C
Pass(A)=100 Pass(B)=200 Pass(C)=80 → 跑 C
Pass(A)=200 Pass(B)=200 Pass(C)=120 → 跑 C …一个周期跑完:C 跑 5 次、A 2 次、B 1 次——正好按 250:100:50 分配5。 那为什么还需要彩票?因为步幅调度有全局状态:新进程中途插入时, pass 该设成多少?设 0 它就独占 CPU。彩票调度没有这个问题, 新进程拿着票进来、更新总票数就完事6。
3.2 Linux CFS:把「公平」做成默认答案
Linux 的完全公平调度器(CFS)放弃了「固定时间片 + 优先级」的传统结构。 它的计数器叫虚拟运行时间(vruntime):进程跑多少物理时间, vruntime 就涨多少;调度决策永远是挑 vruntime 最小的跑7。
- 时间片是动态的:
sched latency(典型 48ms)÷ 在跑进程数 n。 n=4 时每人 12ms8。 - 防止 n 太大时片太碎,设了 min granularity(典型 6ms): 时间片永不小于它——n=10 时理论值 4.8ms,实际给 6ms,公平性略损换效率9。
- 优先级用经典的 nice 值(-20 到 +19,默认 0)映射成权重表 (nice 0 → 1024,nice -5 → 3121,…),时间片按权重比例切; vruntime 的增速则反比于权重——nice -5 的进程 vruntime 涨得慢, 于是更常被选中。书里的例子:A(nice -5)时间片约 36ms,B(nice 0)约 12ms, A 的 vruntime 以 B 的三分之一速度积累10。
性能动机:Google 数据中心的测量显示,即使重度优化后, 调度仍吃掉约 5% 的 CPU——所以现代调度器必须把「做决定本身」 做得飞快11(补充,不在书里:CFS 用红黑树按 vruntime 排序, 取最小是 O(log n),这是它能压低开销的关键实现手段)。
3.3 彩票的三件小工具
书里还给了三个操纵票数的机制,每个对应一种真实需求12:
| 机制 | 做法 | 场景 |
|---|---|---|
| 货币(currency) | 用户在自己的作业间用任意「本币」发票,系统按份额折算成全局票 | 用户 A 拿 100 票,给自己两个作业各发 500「A 币」,各折成 50 全局票 |
| 转让(transfer) | 进程临时把票交给另一个进程 | 客户-服务器:客户把票转给正在为自己干活的服务器,办完再转回 |
| 通胀(inflation) | 进程临时给自己加票 | 只适用于互信环境;竞争者之间这么干就是抢机器 |
图说:三张表说明「票」这个抽象的真正价值不在调度,而在它是 表达「份额所有权」的通用货币——后来的虚拟机内存份额管理也在用12。
3.4 多核:三个物理事实,推翻单机答案
事实一:缓存一致性(coherence)。程序在 CPU 1 上把地址 A 的值 从 D 改成 D′,新值只写在 CPU 1 的缓存里;调度器把它挪到 CPU 2, 重读 A 拿到旧值 D——错了。硬件用总线嗅探之类协议兜底, 保住「共享内存」的假象13。代价:跨 CPU 共享数据天生更贵。
**事实二:同步绕不开。**即使硬件保证一致性,「从链表头删一个节点」 这样的三步操作在两个 CPU 同时执行仍会出事(两个线程的 tmp 都记住 同一个旧头,双双 free 同一块内存)。要正确就得加锁; 而锁的争用随 CPU 数增长急剧恶化14。
**事实三:缓存亲和(affinity)。**进程在 CPU 1 上攒了一缓存的热数据, 下次还在 CPU 1 跑就快;每次换 CPU 就全部冷启动15。
两个调度架构的正反面16:
| SQMS 单队列 | MQMS 每 CPU 一队列 | |
|---|---|---|
| 扩展性 | 差:全系统抢一把队列锁 | 好:队列数随 CPU 数走 |
| 缓存亲和 | 差:进程在 CPU 间弹跳(书中 A–E 五作业四 CPU 的例子,作业被轮流搬家) | 好:作业钉在本 CPU |
| 负载均衡 | 天然(大家都从一个队列拿) | 差:C 先跑完后 A 拿 2 份、B 和 D 各 1 份;A、C 都走后 CPU 0 直接闲置 |
图说:MQMS 的最后一行「CPU 0 闲置」是本章的戏眼——解法只有一个: 迁移(migration),把作业搬去闲的 CPU;更麻烦的情形 (A 独守 CPU 0,B/D 挤 CPU 1)需要持续迁移,隔几段时间片换一批, 用实现复杂度换公平17。Linux 的做法(多级调度域、负载均衡)书里给了引子, 细节交给参考文献18。
4. 作者的判断与证据
书里给了证据的判断:
- 彩票的公平性有模拟数据(F 值随作业长度收敛到 1)——公平是 「跑久了才到位」的,这个收敛性有图2。
- 步幅调度的精确性有完整的手算轨迹45。
- CFS 的动机引 Google 数据中心的 5% 实测11。
作者的判断(立场):
- 随机性被作者大力推荐:没有最坏情况、状态极简、决策极快—— 「当你必须做决定时,随机常常是一种稳健而简单的办法」19。 同时承认它的代价:短时间尺度上比例不准,这正是步幅调度存在的原因。
- 「票数怎么分」被明确标为开放问题——用户知道得最多么? 这只把问题推回去了,没有答案20。
5. 边界与局限
- CFS 一节写于它较早期的形态(红黑树、组调度等后来演进未覆盖; 补充,不在书里:此后 CFS 增加了组调度、EEVDF 取代部分启发式,即经验规则)。
- 多核部分对缓存一致性「跳过所有细节」——需要体系结构课补; NUMA(非一致访存)只字未提,而它在今天的多路服务器上是主导因素。
- 工作窃取(work stealing)作为经典解法在本章没有出现(它出现在 本章参考文献之外的并发文献里),迁移一节讲的 OS 级方案较重。
6. 可带走的
- 想保证份额,只有两条路:随机逼近(彩票:简单、无全局状态、短期不准) 或确定计数(步幅/CFS:精确,但有全局状态/排序结构的负担)。
- CFS 的心法一句话:谁欠的运行时间少,谁先跑;欠多少用 vruntime 记。
- 优先级不必做成「抢占级别」,可以做成记账速度——涨得慢的自然常跑。 这是对「权重」最优雅的实现方式之一。
- 多核调度的三件套矛盾:锁争用(单队列)、负载不均(多队列)、 缓存亲和(两边都想要)——任何方案都是三者的折中。
- 「随机没有最坏情况」是彩票调度给整个系统的启示:遇到要在 坏例上崩的传统算法时,先问一句随机化能不能救。
- 份额型调度都有同一个没答案的问题:初始份额怎么分。 遇到「保证公平」的需求,先追问「按什么分」。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 彩票的 75/25 例子与随机输出 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:55(搜「85 70 39」) |
| 随机性的三个优点 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:33(搜「USE RANDOMNESS」) |
| 票 = 份额所有权的通用机制 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:71(搜「TICKETS TO REPRESENT」) |
| 货币/转让/通胀三机制 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:84(搜「ticket currency」) |
| 抽奖实现代码与 300 号例子 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:150(搜「300」) |
| 公平性随作业长度收敛 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:180(搜「Fairness Study」) |
| 步幅与 pass 定义 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:228(搜「stride」) |
| 步幅调度轨迹 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:259(搜「Who Runs」) |
| 彩票 vs 步幅:全局状态 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:283(搜「no global state」) |
| CFS 与 Google 5% | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:292(搜「Completely Fair」) |
| vruntime 挑最小 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:319(搜「virtual runtime」) |
| sched latency / min granularity | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:331(搜「sched latency」) |
| nice 值与权重表 | 9. Scheduling: Proportional Share | text/11-ch09-9-scheduling-proportional-share.txt:368(搜「Niceness」) |
| 缓存一致性问题(D 与 D′) | 10. Multiprocessor Scheduling (Advanced) | text/12-ch10-10-multiprocessor-scheduling-advanced.txt:104(搜「Oops」) |
| 同步:链表删除的双 free | 10. Multiprocessor Scheduling (Advanced) | text/12-ch10-10-multiprocessor-scheduling-advanced.txt:151(搜「List_Pop」) |
| 缓存亲和 | 10. Multiprocessor Scheduling (Advanced) | text/12-ch10-10-multiprocessor-scheduling-advanced.txt:178(搜「cache affinity」) |
| SQMS 的锁与弹跳 | 10. Multiprocessor Scheduling (Advanced) | text/12-ch10-10-multiprocessor-scheduling-advanced.txt:199(搜「single queue」) |
| MQMS 与负载不均(CPU 0 闲置) | 10. Multiprocessor Scheduling (Advanced) | text/12-ch10-10-multiprocessor-scheduling-advanced.txt:325(搜「CPU 0 is idle」) |
| 迁移与持续迁移 | 10. Multiprocessor Scheduling (Advanced) | text/12-ch10-10-multiprocessor-scheduling-advanced.txt:339(搜「migration」) |
| 「偏执统治这一天」 | 11. Summary Dialogue on CPU Virtualization | text/13-ch11-11-summary-dialogue-on-cpu-virtualization.txt:26(搜「Paranoia rules the day」) |
Footnotes
-
出处:「11. Summary Dialogue on CPU Virtualization」第 18–26 段(
text/13-ch11-11-summary-dialogue-on-cpu-virtualization.txt:26,搜「Paranoia rules the day」)。学生总结:操作系统想当神经质,既要程序高效跑,又要随时能说「且慢」。 ↩ -
出处:「9. Scheduling: Proportional Share」第 54–65 段(
text/11-ch09-9-scheduling-proportional-share.txt:55,搜「85 70 39」)。原文记录 20 次开奖与由此产生的调度,B 实得 20% 而非 25%。 ↩ ↩2 -
出处:「9. Scheduling: Proportional Share」第 136–160 段(
text/11-ch09-9-scheduling-proportional-share.txt:148,搜「pick a random number」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 247–270 段(
text/11-ch09-9-scheduling-proportional-share.txt:259,搜「Who Runs」)。 ↩ ↩2 -
出处:「9. Scheduling: Proportional Share」第 277–280 段(
text/11-ch09-9-scheduling-proportional-share.txt:278,搜「exactly in proportion」)。 ↩ ↩2 -
出处:「9. Scheduling: Proportional Share」第 281–290 段(
text/11-ch09-9-scheduling-proportional-share.txt:283,搜「no global state」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 315–323 段(
text/11-ch09-9-scheduling-proportional-share.txt:319,搜「virtual runtime」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 331–345 段(
text/11-ch09-9-scheduling-proportional-share.txt:331,搜「sched latency」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 349–360 段(
text/11-ch09-9-scheduling-proportional-share.txt:349,搜「min granularity」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 394–408 段(
text/11-ch09-9-scheduling-proportional-share.txt:395,搜「most precious job」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 299–304 段(
text/11-ch09-9-scheduling-proportional-share.txt:302,搜「5%」)。 ↩ ↩2 -
出处:「9. Scheduling: Proportional Share」第 81–114 段(
text/11-ch09-9-scheduling-proportional-share.txt:84,搜「ticket currency」)。 ↩ ↩2 -
出处:「10. Multiprocessor Scheduling (Advanced)」第 91–121 段(
text/12-ch10-10-multiprocessor-scheduling-advanced.txt:105,搜「cache coherence」)。 ↩ -
出处:「10. Multiprocessor Scheduling (Advanced)」第 123–176 段(
text/12-ch10-10-multiprocessor-scheduling-advanced.txt:151,搜「List_Pop」)。 ↩ -
出处:「10. Multiprocessor Scheduling (Advanced)」第 178–190 段(
text/12-ch10-10-multiprocessor-scheduling-advanced.txt:178,搜「cache affinity」)。 ↩ -
出处:「10. Multiprocessor Scheduling (Advanced)」第 195–262 段(
text/12-ch10-10-multiprocessor-scheduling-advanced.txt:199,搜「single queue」)与第 264–298 段(text/12-ch10-10-multiprocessor-scheduling-advanced.txt:266,搜「multiple queues」)。 ↩ -
出处:「10. Multiprocessor Scheduling (Advanced)」第 299–359 段(
text/12-ch10-10-multiprocessor-scheduling-advanced.txt:339,搜「migration」)。 ↩ -
出处:「10. Multiprocessor Scheduling (Advanced)」第 360 段之后(
text/12-ch10-10-multiprocessor-scheduling-advanced.txt:386,搜「Linux」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 33–49 段(
text/11-ch09-9-scheduling-proportional-share.txt:33,搜「USE RANDOMNESS」)。 ↩ -
出处:「9. Scheduling: Proportional Share」第 212–220 段(
text/11-ch09-9-scheduling-proportional-share.txt:212,搜「Assign Tickets」)。 ↩