调度江湖风云录:CFS、实时调度与公平性的权谋艺术

------ 小dora 操作系统学习笔记Vol.7 · 高阶 CPU 调度篇


"dora:我的程序卡顿,但又没崩,调度器是不是偏心眼?"

操作系统翻开 /proc/sched_debug: "你虚拟运行时间太长了,等会儿再说。"


🧠 一、调度器的"终极目标"是啥?

在初学者眼中,调度器(Scheduler)似乎就是排排坐、吃果果------谁排在前面,谁就先执行。但现代操作系统可不是"幼儿园排队",调度策略早已进化出多套机制,以适应不同的工作负载、服务质量需求和资源公平性。

一个优秀的调度器必须应对以下挑战:

  1. 多任务并发:可能同时运行数千个进程/线程,要求公平高效。
  2. 任务性质多样:有的 CPU 密集(如视频编解码),有的 IO 密集(如数据库服务)。
  3. 响应时间 vs 吞吐量:实时系统要快,服务器系统要多。
  4. 多核系统调度协调:避免所有任务扎堆一个核心。

于是,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 是正确答案。
相关推荐
千里镜宵烛2 小时前
深入理解 Linux 线程:从概念到虚拟地址空间的全面解析
开发语言·c++·操作系统·线程
OpenAnolis小助手1 天前
朗空量子与 Anolis OS 完成适配,龙蜥获得抗量子安全能力
安全·开源·操作系统·龙蜥社区·龙蜥生态
墨夏2 天前
跨平台开发下的策略模式
设计模式·操作系统
fakerth3 天前
OpenHarmony介绍
操作系统·openharmony
程序员老刘4 天前
操作系统“卡脖子”到底是个啥?
android·开源·操作系统
有信仰4 天前
操作系统——虚拟内存和物理内存
操作系统
望获linux9 天前
【实时Linux实战系列】实时数据流处理框架分析
linux·运维·前端·数据库·chrome·操作系统·wpf
unfetteredman9 天前
Mac查看端口使用信息
操作系统·mac
闪电麦坤9510 天前
操作系统:RPC 中可能遇到的问题(Issues in RPC)
rpc·操作系统
闪电麦坤9510 天前
操作系统:远程过程调用( Remote Procedure Call,RPC)
rpc·操作系统