1. 调度器概述:Linux CFS调度器设计哲学、核心数据结构
1.1 CFS 设计哲学:公平不是时间片,而是权重比例
完全公平调度器(Completely Fair Scheduler, CFS)自 Linux 2.6.23 引入,取代了传统的 O(1) 调度器。其核心设计哲学可概括为:“在理想的多任务处理器上,每个任务应获得相等的 CPU 时间比例”。
CFS 摒弃了固定时间片(time slice)模型,转而采用 虚拟运行时间(vruntime) 作为调度决策的唯一依据。vruntime 越小,表示任务被“亏待”得越多,调度器就越倾向于选择它。
1.2 核心数据结构:调度器的骨架
CFS 的调度决策依赖于三个核心数据结构,它们构成了调度器的“骨架”:rq(运行队列)、cfs_rq(CFS 运行队列)和 sched_entity(调度实体)。
| 数据结构 | 所属层级 | 核心作用 | 关键字段(示例) |
|---|---|---|---|
| rq | 每个 CPU 一个 | 管理该 CPU 上所有调度类的任务(CFS、RT、DL) | nr_running, cfs, rt, dl |
| cfs_rq | 每个 CPU 一个(嵌套于 rq) | 管理该 CPU 上所有 CFS 调度实体的红黑树 | rb_root, min_vruntime, nr_running |
| sched_entity | 每个任务一个(可嵌套) | 表示一个可被调度的实体(任务或任务组) | vruntime, load, on_rq, parent |
sched_entity 是 CFS 调度的基本单元。一个进程(task_struct)内嵌一个 sched_entity 实例。当启用 CPU 控制组(cgroup)时,一个任务组也可以作为一个 sched_entity 参与调度,实现组间公平。
1.3 数据结构关系:从 CPU 到任务
下图展示了 rq、cfs_rq 和 sched_entity 之间的层级关系与数据流向。
1.4 vruntime:调度决策的“货币”
vruntime 是 CFS 调度决策的核心指标。其计算方式决定了调度的公平性:
// 核心计算公式(简化)
vruntime += (delta_exec * NICE_0_LOAD) / se->load.weight;
// 其中:
// delta_exec: 实际运行的物理时间
// NICE_0_LOAD: nice=0 时的标准权重(1024)
// se->load.weight: 当前调度实体的权重(由 nice 值决定)
load.weight 越大,vruntime 增长越慢,因此更容易被调度器选中,获得更多 CPU 时间。反之,权重越低(高 nice 值),vruntime 增长越快,被调度的机会越少。
1.5 调度实体权重与 nice 值映射
nice 值范围是 [-20, 19],映射到权重数组 sched_prio_to_weight。下表展示了部分映射关系:
| nice 值 | 权重 (load.weight) | 相对 nice=0 的 CPU 时间比例 | 典型场景 |
|---|---|---|---|
| -20 | 88761 | 约 86.7 倍 | 实时音频处理 |
| -10 | 11051 | 约 10.8 倍 | 交互式应用 |
| 0 | 1024 | 1 倍(基准) | 普通后台任务 |
| 10 | 304 | 约 0.3 倍 | 批处理任务 |
| 19 | 15 | 约 0.015 倍 | 低优先级清理任务 |
1.6 调度周期与粒度控制
CFS 通过 sched_latency_ns 和 sched_min_granularity_ns 两个内核参数控制调度行为:
- sched_latency_ns(默认 6ms):CFS 期望在一个调度周期内,所有可运行任务至少被调度一次。
- sched_min_granularity_ns(默认 0.75ms):任务被调度后至少运行的时间,防止过于频繁的上下文切换。
当可运行任务数量增多时,每个任务获得的时间片 = sched_latency_ns / nr_running。如果该值小于 sched_min_granularity_ns,则使用 sched_min_granularity_ns * nr_running 作为实际调度周期。
sched_latency_ns 和 sched_min_granularity_ns 直接影响任务在 CPU 之间的迁移频率和调度延迟。过小的粒度会导致频繁迁移,增加缓存失效开销;过大的粒度则可能导致负载不均。
1.7 本章小结
CFS 调度器通过 vruntime 和 红黑树 实现了 O(log n) 的调度复杂度,同时保证了公平性。核心数据结构 rq、cfs_rq、sched_entity 构成了调度器的骨架,为后续的负载均衡和跨 CPU 域迁移提供了基础。理解这些概念是调试调度器内核参数的前提。