------ 小dora 操作系统学习笔记Vol.7 · 高阶 CPU 调度篇
"dora:我的程序卡顿,但又没崩,调度器是不是偏心眼?"
操作系统翻开 /proc/sched_debug: "你虚拟运行时间太长了,等会儿再说。"
🧠 一、调度器的"终极目标"是啥?
在初学者眼中,调度器(Scheduler)似乎就是排排坐、吃果果------谁排在前面,谁就先执行。但现代操作系统可不是"幼儿园排队",调度策略早已进化出多套机制,以适应不同的工作负载、服务质量需求和资源公平性。
一个优秀的调度器必须应对以下挑战:
- 多任务并发:可能同时运行数千个进程/线程,要求公平高效。
- 任务性质多样:有的 CPU 密集(如视频编解码),有的 IO 密集(如数据库服务)。
- 响应时间 vs 吞吐量:实时系统要快,服务器系统要多。
- 多核系统调度协调:避免所有任务扎堆一个核心。
于是,Linux 内核开发者设计了被誉为"最具工程美感"的调度器之一:CFS(Completely Fair Scheduler) 。
🌲 二、CFS 完全公平调度器:红黑树上的权力游戏
CFS 的调度哲学非常直接,却充满算法优雅:
"谁在历史上被冷落得最久,就优先上台发言。"
✅ 2.1 虚拟运行时间 vruntime
为实现"公平",CFS 为每个任务维护一个名为 vruntime
的值,即虚拟运行时间。
-
vruntime 越小,说明该任务最近获得的 CPU 时间越少,优先级越高。
-
每当一个任务运行时,其 vruntime 就以一定速度增长。
-
这个增长速率不仅与实际运行时间相关,还与任务的 nice 值(优先级) 有关。
- nice 值低(优先级高),vruntime 增长慢。
- nice 值高(优先级低),vruntime 增长快。
📌 巧记口诀:
"vruntime 越小越可怜,红黑树头第一个先上班。"
✅ 2.2 红黑树调度机制
CFS 使用一个红黑树(rb-tree)来存储所有"就绪态"进程的 vruntime。
- 红黑树具有自平衡性质,可以在 O(logN) 时间内插入、删除、查找。
- 树的最左节点就是 vruntime 最小的任务 → 它将成为下一个调度对象。
- 每次调度器触发调度时,只需取树根节点,即最"饿"的进程。
这种方式避免了传统调度策略中频繁排序、遍历链表等开销。
⚖️ 三、调度周期与时间片如何分配?
CFS 的另一个创新点是:它不采用固定时间片(如 RR 的每个任务给 10ms) ,而是通过计算每个任务应获得的"公平份额"。
✅ 3.1 调度周期与权重计算
CFS 设定一个调度周期(例如 20ms),然后按照任务的调度权重(weight)来分配时间:
ini
slice_i = period × weight_i / total_weight
- period:CFS 的全局调度周期。
- weight_i:第 i 个任务的权重(由 nice 值决定)。
- total_weight:所有活跃任务的权重总和。
✅ 3.2 nice 值与权重映射表
nice | weight |
---|---|
-20 | 88761 |
0 | 1024 |
+19 | 15 |
- nice 越低,代表用户希望该任务获得更高 CPU 优先级,权重更大。
- 所以,同样是 20ms 周期,nice=-10 的进程可能获得 8ms,nice=10 的只获得 1ms。
📌 巧记口诀:
"nice 不 nice,时间少一拍。"
✅ 3.3 时间片不是固定的?为何?
- 固定时间片不能体现任务间的"优先级公平性"。
- CFS 动态计算 slice,保证各任务"被调度的权利"接近其权重占比。
- 避免了高优先级任务被低优先级任务饿死的风险。
🧵 四、进阶:实时调度策略(Real-Time Scheduling)
除了 CFS 外,Linux 还实现了三种实时调度策略,分别用于硬实时/软实时场景。它们与 CFS 不同,不关心公平,而是强调 确定性。
1️⃣ SCHED_FIFO:先进先出,绝不让位
- 一旦某个高优实时任务获得 CPU,将一直运行到阻塞/退出。
- 不会因为时间片用尽而切换。
- 只有更高优先级任务才可打断它。
→ 非常适合处理控制系统、实时音频等对延迟极为敏感的场景。
2️⃣ SCHED_RR:实时时间片轮转
- 同优先级下多个实时任务轮转调度。
- 每个任务有时间片,时间片用完被强制切换。
- 保证"不会被饿死",但调度响应仍然快速。
3️⃣ SCHED_DEADLINE:硬实时神器
基于 Earliest Deadline First (EDF) 算法,适用于高精度任务调度。
- 用户需要指定:运行时间、周期、截止时间。
- 调度器确保任务在截止时间前完成运行。
📌 示例命令:
bash
chrt -f -p 80 <pid> # 将进程切为 SCHED_FIFO,优先级 80
📌 优先级范围:
- 实时策略:1--99(数字越大优先级越高)
- CFS(普通进程):nice -20 到 +19
🧪 五、实战小测
🔍 题目 1
假设调度周期为 24ms,系统中有两个任务:
- A,nice=0(weight=1024)
- B,nice=10(weight=110)
计算时间片:
计算总权重:
ini
ini
复制编辑
total_weight = 1024 + 110 = 1134
计算各自时间片:
ini
ini
复制编辑
slice_A = 24ms * 1024 / 1134 ≈ 21.67ms
slice_B = 24ms * 110 / 1134 ≈ 2.33ms
结论:任务 A 时间片远大于 B,说明它能获得更多 CPU 时间,这符合 nice 值较低任务优先的调度原则。
🔍 题目 2
哪个任务更容易被调度:
- 任务 A:vruntime = 100ms
- 任务 B:vruntime = 75ms
解读:CFS 会优先调度 vruntime 较小的任务,因为它代表"被冷落的时间越久",等待时间越长。
所以任务 B(vruntime=75ms)比 A(100ms)更容易被调度。
🔍 题目 3
以下关于 SCHED_FIFO 的说法哪个是正确的?
A. FIFO 任务有时间片限制
B. FIFO 任务一旦运行,永不切换除非阻塞
C. FIFO 和 CFS 使用相同优先级机制
D. FIFO 会自动调整 nice 值
答案:B
- SCHED_FIFO 是一种无时间片的实时调度策略,任务一旦获得 CPU,就一直运行,直到主动阻塞、退出或被更高优先级任务抢占。
- 它与 CFS 机制不同,不会自动调整 nice 值。
- 因此 B 是正确答案。