2. Linux调度器基础

2.1 CFS 完全公平调度器核心原理

CFS(Completely Fair Scheduler)是 Linux 内核默认的进程调度器,自 2.6.23 版本引入。其设计哲学是:为每个可运行进程分配 CPU 时间的“公平份额”,而非传统的时间片轮转。CFS 基于一个简单的数学模型——虚拟运行时间(vruntime),来模拟“理想多任务处理器”。

核心思想: 在一个理想的多任务处理器中,N 个可运行进程在任意时刻都能同时获得 1/N 的 CPU 时间。CFS 通过 vruntime 来逼近这一理想状态:每次调度时,选择 vruntime 最小的进程运行。

CFS 使用红黑树(Red-Black Tree)作为调度队列的数据结构,以 vruntime 为键值组织所有可运行的调度实体。红黑树保证了插入、删除和查找操作的时间复杂度均为 O(log N),适合大规模进程调度场景。

CFS 调度决策流程 就绪队列 红黑树(按 vruntime 排序) 选择最小 vruntime 红黑树最左节点 进程运行 执行调度实体 时钟中断 更新当前进程 vruntime 检查是否抢占 当前 vruntime > 最小 vruntime? 周期性触发 触发重新调度 设置 TIF_NEED_RESCHED 是 否 说明:CFS 在每个调度周期(tick)更新当前进程的 vruntime,并与红黑树最左节点比较,决定是否抢占。

2.2 调度实体(sched_entity)与运行队列(rq)

调度实体是 CFS 调度的基本单位,由 struct sched_entity 表示。每个进程(task_struct)内嵌一个调度实体,用于参与 CFS 调度。运行队列(runqueue, rq)是每个 CPU 核心上的调度数据结构,管理该 CPU 上所有可运行的调度实体。

调度实体与运行队列关系结构 CPU 0 运行队列 (rq) cfs_rq rb_root (红黑树根) sched_entity A vruntime=100 weight=1024 sched_entity B vruntime=150 weight=820 sched_entity C vruntime=200 weight=1024 ← curr 指针 关键数据结构说明 1. struct rq:每个 CPU 一个 包含 cfs/rt/dl 三个调度类队列2. struct cfs_rq:CFS 调度类队列 管理所有 CFS 调度实体3. struct sched_entity:调度实体 内嵌于 task_struct 中4. vruntime:虚拟运行时间 决定调度顺序的核心指标5. weight:调度权重 由 nice 值映射而来

2.3 nice 值与权重映射关系

nice 值是 Linux 中用于调整进程优先级的传统接口,范围从 -20(最高优先级)到 +19(最低优先级)。CFS 不再直接使用 nice 值作为时间片长度,而是将其映射为调度权重(weight),影响进程的 vruntime 增长速率。

权重映射的核心公式:

weight = 1024 / (1.25 ^ nice)

其中 1024 是 nice=0 时的基准权重。权重越大,进程的 vruntime 增长越慢,获得的 CPU 时间越多。

数据结构 内核定义 关键字段 作用
调度实体 struct sched_entity vruntime, load.weight, on_rq, run_node 参与 CFS 调度的基本单元,记录虚拟运行时间和权重
CFS 运行队列 struct cfs_rq rb_root, nr_running, min_vruntime, load 管理每个 CPU 上 CFS 调度类的可运行实体
全局运行队列 struct rq cfs, rt, dl, curr, idle 每个 CPU 一个,包含所有调度类的运行队列
nice 值 权重 (weight) 相对 nice=0 的 CPU 占比 典型场景
-20 88761 ≈ 86.7 倍 实时性要求极高的内核线程
-10 11051 ≈ 10.8 倍 音频/视频处理进程
-5 3355 ≈ 3.3 倍 交互式桌面应用
0 1024 1.0 倍(基准) 普通用户进程
5 335 ≈ 0.33 倍 后台编译任务
10 110 ≈ 0.11 倍 低优先级批处理任务
19 15 ≈ 0.015 倍 空闲任务、SETI@home
关键理解: nice 值每相差 1,CPU 时间分配比例相差约 1.25 倍。nice 值从 0 变为 -1,权重从 1024 变为 1277(≈1.25 倍),进程获得的 CPU 时间增加约 25%。这种指数映射保证了优先级差异在极端值下仍然可控。
nice 值 → 权重映射与 vruntime 影响 nice 值范围:-20(最高优先级) → 0(默认) → +19(最低优先级) 权重范围:88761 → 1024 → 15 权重映射公式 weight = 1024 / (1.25 ^ nice) vruntime 增长速率 vruntime += (实际运行时间 × 1024) / weight 调度结果 权重越大 → vruntime 增长越慢 → 获得更多 CPU 时间

2.4 调度延迟与权重的关系

CFS 的调度延迟(sched_latency)是保证所有可运行进程在一个周期内至少获得一次 CPU 时间的目标延迟。默认值为 6ms,可通过内核参数 kernel.sched_latency_ns 调整。

每个进程在一个调度周期内获得的 CPU 时间由权重决定:

进程获得的 CPU 时间 = 调度周期 × (进程权重 / 所有进程权重之和)
性能调优注意: 当可运行进程数量过多时,CFS 会启用“最小粒度”(min_granularity,默认 0.75ms)来防止上下文切换过于频繁。此时实际调度周期会大于 sched_latency,导致调度延迟增加。这是多核系统中需要重点关注的性能瓶颈。

通过 perf sched 工具可以观测调度延迟的实际表现:

# 记录调度事件
perf sched record -- sleep 5

# 分析调度延迟统计
perf sched latency

# 查看具体调度细节
perf sched map
perf sched timehist

2.5 本章小结

  • CFS 核心原理:基于 vruntime 的红黑树调度,每次选择 vruntime 最小的进程运行。
  • 调度实体:struct sched_entity 是 CFS 调度的基本单位,内嵌于进程描述符中。
  • 运行队列:每个 CPU 一个 struct rq,包含 cfs_rq 管理所有 CFS 调度实体。
  • nice 值映射:nice 值通过指数公式映射为权重,权重决定 vruntime 增长速率和 CPU 时间分配。
  • 调度延迟:默认 6ms 的调度周期保证公平性,进程数过多时受最小粒度限制。