调度江湖风云录: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 是正确答案。
相关推荐
喧星Aries10 小时前
简单易懂,快表 详解
操作系统·内存·计算机组成原理
夏旭泽1 天前
操作系统-分布式同步
分布式·操作系统
望获linux3 天前
【实时Linux实战系列】实时系统的调试技术
linux·运维·服务器·网络·数据库·操作系统·嵌入式软件
DoraBigHead3 天前
进程的内存是怎么分的?深入解析内存管理的秘密工事
操作系统
望获linux3 天前
【Linux基础知识系列】第五十四篇 - 网络协议基础:TCP/IP
java·linux·服务器·开发语言·架构·操作系统·嵌入式软件
、BeYourself3 天前
死锁的避免
操作系统
CYRUS_STUDIO4 天前
深入 Android syscall 实现:内联汇编系统调用 + NDK 汇编构建
android·操作系统·汇编语言
LUCIAZZZ4 天前
高性能网络模式-Reactor和Preactor
java·服务器·开发语言·网络·操作系统·计算机系统
喧星Aries5 天前
内存的基础相关知识,什么是内存,内存管理
操作系统·内存·计算机组成原理