2. Linux 内核调度器基础:CFS 调度器原理、时间片与虚拟运行时、调度实体与运行队列

2.1 CFS 调度器核心设计理念

完全公平调度器(Completely Fair Scheduler, CFS)自 Linux 2.6.23 引入以来,取代了原有的 O(1) 调度器,成为主流调度算法。CFS 的核心哲学是:在理想的多任务处理器上,每个进程应获得等比例的 CPU 时间。CFS 不依赖固定时间片,而是通过维护一个红黑树来模拟“理想公平 CPU”,每次选择虚拟运行时间(vruntime)最小的进程执行。

关键设计目标: 在真实硬件上逼近理想多任务处理器的公平性,同时保持低调度延迟和高交互响应。
CFS 调度器核心架构 CFS 调度器核心架构 红黑树根节点 左子树 (vruntime 小) 右子树 (vruntime 大) 调度实体 A 调度实体 B 调度实体 C 调度实体 D CFS 运行队列 (cfs_rq) 调度器每次选择红黑树最左节点(vruntime 最小)的调度实体投入运行

2.2 虚拟运行时(vruntime)与时间片

CFS 摒弃了传统固定时间片机制,引入 虚拟运行时(vruntime) 作为调度决策的核心度量。vruntime 是进程实际运行时间经过优先级加权的结果,其计算公式为:

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

其中 NICE_0_LOAD 是 nice 值为 0 时的基准权重(1024)。该公式确保:

  • 高优先级进程(权重更大):vruntime 增长更慢,因此更容易被调度器选中,获得更多 CPU 时间。
  • 低优先级进程(权重更小):vruntime 增长更快,在红黑树中向右移动,调度优先级降低。
实践要点: 通过 /proc/[pid]/sched 可以查看进程的 se.vruntime 值,用于诊断进程是否因 vruntime 异常而出现调度延迟。
不同优先级进程 vruntime 增长对比 不同优先级进程 vruntime 增长对比 实际运行时间 (ms) vruntime nice=-20 (高优先级) nice=0 (基准) nice=19 (低优先级) 斜率越大表示 vruntime 增长越快,调度优先级越低;高优先级进程 vruntime 增长最慢

2.3 调度实体(sched_entity)

调度实体是 CFS 调度器管理的基本单元,在内核中由 struct sched_entity 表示。每个进程(task_struct)内嵌一个调度实体,用于参与 CFS 的调度决策。关键字段包括:

字段名 类型 说明
load struct load_weight 进程权重,由 nice 值映射而来
run_node struct rb_node 红黑树节点,用于插入 CFS 运行队列
vruntime u64 虚拟运行时间,调度决策核心依据
on_rq int 标记是否在运行队列上
exec_start u64 当前调度周期开始时间戳
sum_exec_runtime u64 进程累计实际运行时间
性能调优注意: 在 CPU 亲和性配置场景中,调度实体与 CPU 的绑定关系直接影响负载均衡行为。当进程通过 sched_setaffinity() 绑定到特定 CPU 时,其调度实体仅在该 CPU 的 CFS 运行队列上排队,不再参与跨 CPU 负载均衡。

2.4 CFS 运行队列(cfs_rq)

每个 CPU 拥有一个独立的 CFS 运行队列(struct cfs_rq),用于管理该 CPU 上所有可运行的调度实体。运行队列的核心数据结构是红黑树,以 vruntime 为键值组织所有调度实体。

CFS 运行队列的关键属性:

  • 红黑树根节点(tasks_timeline):维护所有可运行实体的有序集合。
  • 最小 vruntime(min_vruntime):跟踪队列中最小的 vruntime 值,用于新进程的 vruntime 初始化。
  • 运行实体计数(nr_running):当前队列中可运行的调度实体数量。
  • 负载统计(load):队列中所有实体的总权重,用于负载均衡决策。
CFS 运行队列与调度实体关系 CFS 运行队列与调度实体关系 CPU 0 CFS 运行队列 红黑树根 左子 (vruntime 最小) 右子 (vruntime 较大) 调度实体 A 调度实体 B 调度实体 C CPU 1 CFS 运行队列 红黑树根 左子 (vruntime 最小) 右子 (vruntime 较大) 调度实体 D 调度实体 E 每个 CPU 拥有独立的 CFS 运行队列,调度实体通过红黑树按 vruntime 排序;CPU 亲和性绑定后,实体仅出现在指定 CPU 的队列中

2.5 调度周期与时间片计算

CFS 的调度周期(sched_latency)是保证每个可运行进程至少获得一次 CPU 执行的时间窗口。默认值为 6ms,可通过 /proc/sys/kernel/sched_latency_ns 调整。进程的时间片由调度周期和进程数量动态决定:

时间片 = sched_latency / nr_running

当运行进程数量过多时,时间片可能过小导致上下文切换开销增大。此时 CFS 引入 sched_min_granularity(默认 0.75ms)作为最小时间片下限,防止切换过于频繁。

性能调优关联: 在 CPU 亲和性配置中,将相关进程绑定到同一 CPU 可以减少跨 CPU 调度延迟,但也会增加该 CPU 运行队列的 nr_running,导致每个进程获得的时间片缩短。需要根据负载特征权衡绑定策略。

2.6 调度实体操作流程

CFS 调度器对调度实体的核心操作流程如下:

  1. 入队(enqueue_task_fair):将调度实体插入红黑树,更新 min_vruntime 和队列负载。
  2. 选择下一个(pick_next_task_fair):从红黑树最左节点取出 vruntime 最小的调度实体。
  3. 执行(context_switch):切换进程上下文,更新 exec_start 时间戳。
  4. 出队(dequeue_task_fair):进程阻塞或退出时从红黑树移除,更新队列状态。
  5. 负载均衡(load_balance):在 CPU 之间迁移调度实体,平衡各队列负载。
CFS 调度实体操作流程 CFS 调度实体操作流程 入队 选择下一个 上下文切换 出队 负载均衡 流程说明 1. 进程就绪时入队,插入红黑树 2. 调度器选择 vruntime 最小的实体执行 3. 上下文切换后更新执行时间戳 4. 进程阻塞/退出时出队;负载均衡跨 CPU 迁移实体

2.7 关键内核参数与调优接口

以下内核参数直接影响 CFS 调度行为,在 CPU 亲和性调优场景中需要重点关注:

参数路径 默认值 调优影响
/proc/sys/kernel/sched_latency_ns 6000000 (6ms) 增大可减少上下文切换,但增加交互延迟
/proc/sys/kernel/sched_min_granularity_ns 750000 (0.75ms) 最小时间片,防止切换过频
/proc/sys/kernel/sched_migration_cost_ns 500000 (0.5ms) 进程迁移代价估算,影响负载均衡决策
/proc/sys/kernel/sched_nr_migrate 32 负载均衡时一次最多迁移的进程数
调优建议: 对于延迟敏感型应用(如数据库、实时音视频),建议将 sched_latency_ns 适当降低(如 3ms),并结合 CPU 亲和性绑定,确保关键进程获得更频繁的调度机会。对于计算密集型批处理任务,可适当增大 sched_latency_ns 以减少切换开销。