1. CFS 调度器概述

1.1 CFS 的设计哲学

完全公平调度器(Completely Fair Scheduler, CFS)是 Linux 内核 2.6.23 版本引入的默认调度器,由 Ingo Molnár 设计实现。其核心设计哲学可以概括为:“模拟理想、精确的多任务 CPU”。

CFS 摒弃了传统调度器基于固定时间片和优先级队列的“时间片轮转”模型,转而采用基于“虚拟运行时间”(vruntime)的模型。它试图让每个可运行的任务都能获得处理器时间的“公平份额”,而不是简单地给每个任务分配一个固定的时间片。

核心思想: 如果系统中有 N 个可运行的任务,CFS 的理想目标是让每个任务都获得 1/N 的处理器时间。当任务数量变化时,CFS 会动态调整每个任务的运行时间,以维持这种公平性。

1.2 核心目标

CFS 的设计围绕以下几个关键目标展开:

  • 公平性(Fairness): 确保所有可运行的任务在统计意义上获得与其权重成比例的 CPU 时间。
  • 交互性(Interactivity): 对 I/O 密集型任务(如键盘输入、鼠标移动)给予快速响应,避免系统“卡顿”。
  • 低延迟(Low Latency): 减少调度延迟,特别是对于需要快速响应的任务。
  • 可扩展性(Scalability): 在多核、多处理器系统上高效运行,支持大量任务。
  • 复杂度可控(Complexity Control): 调度操作(如选择下一个运行的任务)的时间复杂度为 O(log N),其中 N 是任务数量。

1.3 CFS 与 O(1) 调度器的区别

O(1) 调度器是 Linux 2.6.0 至 2.6.22 版本的默认调度器。CFS 在多个方面对其进行了根本性的改进。下表详细对比了两者的核心差异:

对比维度 O(1) 调度器 CFS 调度器
核心模型 基于固定时间片 + 优先级数组(active/expired) 基于虚拟运行时间(vruntime)的公平队列模型
数据结构 每个优先级一个链表(140个优先级队列) 红黑树(Red-Black Tree),以 vruntime 为键值
时间复杂度 O(1) 常数时间(选择任务和入队) O(log N)(红黑树插入、删除、查找最左节点)
时间片分配 静态、固定的时间片(由优先级决定) 动态、按权重比例分配(目标延迟 / 任务数)
交互性处理 启发式“交互性”评分,复杂且易出错 通过睡眠时间补偿(增加 vruntime)自然实现
优先级映射 nice 值直接映射到时间片长度 nice 值映射为权重(weight),影响 vruntime 增长速率
负载均衡 基于负载统计的周期性均衡 基于“调度域”和“负载权重”的精细均衡
面试要点: 面试官常问“为什么 CFS 要放弃 O(1) 调度器?” 核心原因在于 O(1) 调度器的交互性启发式算法过于复杂且不可靠,而 CFS 通过 vruntime 模型优雅地解决了这个问题。此外,O(1) 调度器在多核扩展性和大型服务器场景下表现不佳。

1.4 CFS 调度器核心架构图

下图展示了 CFS 调度器的核心组件及其工作流程。每个 CPU 都有一个自己的运行队列(cfs_rq),其中包含一棵红黑树。调度器通过选择红黑树中最左侧的节点(即 vruntime 最小的任务)来获得下一个运行的任务。

CFS 调度器核心架构图 任务创建/唤醒 (fork/wake_up) 入队操作 (enqueue_task_fair) 每个 CPU 的 cfs_rq 红黑树 (以 vruntime 为键) A B C D 最左节点 调度选择 (pick_next_task_fair) 上下文切换 (context_switch) 任务运行 (执行用户代码) 时钟中断 (tick) 更新 vruntime (update_curr) 说明:每个 CPU 维护一个独立的 cfs_rq 和红黑树。调度器总是选择 vruntime 最小的任务(最左节点)来运行。

1.5 CFS 工作流程简述

CFS 的调度循环可以概括为以下几个步骤:

  1. 时钟中断触发: 周期性时钟中断(tick)调用 scheduler_tick() 函数。
  2. 更新当前任务: update_curr() 函数更新当前正在运行的任务的 vruntime,增加其值。
  3. 检查是否需要抢占: 如果当前任务的 vruntime 超过了红黑树中最左节点的 vruntime,则设置重新调度标志(TIF_NEED_RESCHED)。
  4. 选择下一个任务: 在调度点(如系统调用返回、中断返回),pick_next_task_fair() 从红黑树中选择 vruntime 最小的任务(最左节点)。
  5. 上下文切换: 执行 context_switch(),保存当前任务状态,恢复新任务状态,开始运行新任务。
常见误解: CFS 并不是“完全公平”到让每个任务都获得完全相同的 CPU 时间。它通过权重(weight)机制实现了“按比例公平”。高优先级(低 nice 值)的任务具有更高的权重,其 vruntime 增长更慢,从而获得更多的 CPU 时间。这是下一节“权重计算”的核心内容。