Linux内核调度器性能调优:CFS与实时调度策略 - 第4节

4.1 完全公平调度器的设计哲学

CFS(Completely Fair Scheduler)是 Linux 内核默认的调度器,其核心设计哲学是:在理想的多任务处理器上,每个进程应获得完全相等的 CPU 时间片。CFS 摒弃了传统固定时间片和优先级队列的设计,转而采用基于虚拟运行时间(vruntime)的公平分配模型。

核心思想:CFS 不直接分配时间片,而是通过维护一个按 vruntime 排序的红黑树,每次选择 vruntime 最小的进程运行,从而在宏观上实现“完全公平”。

4.1.1 从“时间片”到“权重比例”

传统调度器为每个进程分配固定时间片,优先级高的进程获得更长的时间片。CFS 则引入权重(weight)概念:每个进程的优先级(nice 值)对应一个权重,权重越高,进程在单位时间内获得的 CPU 时间比例越大。CFS 的目标是让所有进程的 vruntime 尽可能相等。

特性 传统 O(1) 调度器 CFS 调度器
时间分配 固定时间片 按权重比例分配
公平性 优先级决定时间片长度 vruntime 决定调度顺序
数据结构 多级优先级队列 红黑树(Red-Black Tree)
调度复杂度 O(1) O(log n)

4.2 虚拟运行时间(vruntime)

vruntime 是 CFS 的核心度量指标,它表示一个进程在理想 CPU 上运行的时间。实际运行时间会根据进程权重进行加权换算,公式如下:

vruntime += 实际运行时间 * (NICE_0_LOAD / 进程权重)

其中 NICE_0_LOAD 是 nice 值为 0 时的标准权重(1024)。权重越高的进程,其 vruntime 增长越慢,从而获得更多 CPU 时间。

4.2.1 vruntime 的关键特性

  • 单调递增:vruntime 只增不减,确保调度器始终选择“最饥饿”的进程。
  • 权重感知:高优先级(低 nice 值)进程的 vruntime 增长慢,低优先级进程增长快。
  • 睡眠补偿:进程睡眠时 vruntime 保持不变,唤醒后因其 vruntime 较小,会优先获得 CPU。
示例:假设两个进程 A(nice=0,权重=1024)和 B(nice=10,权重=110),同时运行 10ms 实际时间。A 的 vruntime 增加 10ms,B 的 vruntime 增加约 93ms。因此 B 的 vruntime 远大于 A,调度器会优先选择 A 运行。

4.3 红黑树(Red-Black Tree)数据结构

CFS 使用红黑树来组织所有可运行的进程,以 vruntime 作为键值。红黑树是一种自平衡二叉查找树,具有以下特性:

  • 节点颜色:每个节点为红色或黑色。
  • 根节点为黑色。
  • 红色节点的子节点必须为黑色(不能有连续红色节点)。
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

这些特性保证了红黑树的最坏情况查找、插入、删除操作时间复杂度均为 O(log n),其中 n 为可运行进程数。

4.3.1 红黑树在 CFS 中的角色

操作 红黑树行为 CFS 意义
选择下一个进程 查找最左节点(vruntime 最小) O(log n) 内找到最“饥饿”的进程
进程入队 按 vruntime 插入新节点 新唤醒或创建的进程加入调度
进程出队 删除对应节点 进程阻塞或退出时移除
更新 vruntime 删除旧节点,重新插入 进程运行后 vruntime 变化,重新排序
CFS 红黑树结构示意图 vruntime=50 vruntime=20 vruntime=80 vruntime=5 vruntime=35 vruntime=65 vruntime=95 调度选择逻辑: 1. 每次调度时,CFS 选择红黑树中最左节点(vruntime 最小)的进程运行。 2. 进程运行后,其 vruntime 增加,可能被重新插入到树中更靠右的位置。 3. 红黑树的自平衡特性保证所有操作在 O(log n) 时间内完成。

4.4 CFS 调度流程

CFS 的调度周期由 schedule() 函数触发,核心流程如下:

  1. 选择下一个进程:从红黑树中查找 vruntime 最小的节点(最左节点)。
  2. 上下文切换:保存当前进程状态,恢复目标进程状态。
  3. 运行进程:进程在 CPU 上执行,同时更新其 vruntime。
  4. 时钟中断处理:每次时钟中断检查当前进程是否应该被抢占。
  5. 重新入队:如果进程被抢占或主动让出 CPU,将其重新插入红黑树。

4.4.1 抢占决策

CFS 通过比较当前进程的 vruntime 与红黑树中最左节点的 vruntime 来决定是否抢占:

if (当前进程的 vruntime > 最左节点的 vruntime + 调度粒度)
    触发抢占,调度最左节点运行

调度粒度(sysctl_sched_min_granularity)默认为 0.75ms,防止过于频繁的上下文切换。

注意:CFS 的抢占是延迟抢占,即不会在任意时刻立即抢占,而是在时钟中断或进程主动让出 CPU 时检查。这保证了系统的稳定性和可预测性。

4.5 权重与优先级映射

Linux 的 nice 值范围是 -20 到 19,对应 40 个优先级级别。CFS 将 nice 值映射为权重,权重决定了进程的 CPU 时间分配比例。

nice 值 权重 相对 CPU 时间比例
-20 88761 约 86.7 倍于 nice=0
-10 9548 约 9.3 倍于 nice=0
0 1024 基准
10 110 约 0.11 倍于 nice=0
19 15 约 0.015 倍于 nice=0

权重表定义在 kernel/sched/core.c 中的 sched_prio_to_weight 数组,每个 nice 值对应一个权重值,相邻 nice 值之间的权重比例约为 1.25。

4.6 CFS 与实时调度策略的对比

CFS 是普通进程的默认调度器,而实时进程使用 SCHED_FIFO 或 SCHED_RR 策略。两者在调度行为上有本质区别:

特性 CFS(SCHED_NORMAL) SCHED_FIFO SCHED_RR
调度依据 vruntime 公平性 静态优先级 静态优先级 + 时间片轮转
抢占性 可被更高 vruntime 的进程抢占 仅被更高优先级实时进程抢占 时间片用完或更高优先级进程
时间片 动态计算 无限(直到主动让出) 固定时间片
适用场景 通用计算、交互式应用 硬实时、低延迟任务 软实时、需要公平轮转的实时任务
CFS 与实时调度策略对比 CFS (SCHED_NORMAL) 公平共享 CPU 时间 SCHED_FIFO 先入先出,无时间片 SCHED_RR 轮转调度,固定时间片 关键区别: • CFS 保证所有进程公平共享 CPU,适合通用场景。 • SCHED_FIFO 实时进程一旦运行,除非主动让出或被更高优先级实时进程抢占,否则一直运行。 • SCHED_RR 在 SCHED_FIFO 基础上增加时间片限制,同一优先级进程轮转运行。 • 实时进程优先级(0-99)始终高于普通进程(100-139),实时进程就绪时优先运行。

4.7 总结