2. Linux 内核调度器基础:CFS 调度器原理、时间片与虚拟运行时、调度实体与运行队列
2.1 CFS 调度器核心设计理念
完全公平调度器(Completely Fair Scheduler, CFS)自 Linux 2.6.23 引入以来,取代了原有的 O(1) 调度器,成为主流调度算法。CFS 的核心哲学是:在理想的多任务处理器上,每个进程应获得等比例的 CPU 时间。CFS 不依赖固定时间片,而是通过维护一个红黑树来模拟“理想公平 CPU”,每次选择虚拟运行时间(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 异常而出现调度延迟。
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 |
进程累计实际运行时间 |
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):队列中所有实体的总权重,用于负载均衡决策。
2.5 调度周期与时间片计算
CFS 的调度周期(sched_latency)是保证每个可运行进程至少获得一次 CPU 执行的时间窗口。默认值为 6ms,可通过 /proc/sys/kernel/sched_latency_ns 调整。进程的时间片由调度周期和进程数量动态决定:
时间片 = sched_latency / nr_running
当运行进程数量过多时,时间片可能过小导致上下文切换开销增大。此时 CFS 引入 sched_min_granularity(默认 0.75ms)作为最小时间片下限,防止切换过于频繁。
nr_running,导致每个进程获得的时间片缩短。需要根据负载特征权衡绑定策略。
2.6 调度实体操作流程
CFS 调度器对调度实体的核心操作流程如下:
- 入队(enqueue_task_fair):将调度实体插入红黑树,更新
min_vruntime和队列负载。 - 选择下一个(pick_next_task_fair):从红黑树最左节点取出 vruntime 最小的调度实体。
- 执行(context_switch):切换进程上下文,更新
exec_start时间戳。 - 出队(dequeue_task_fair):进程阻塞或退出时从红黑树移除,更新队列状态。
- 负载均衡(load_balance):在 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 以减少切换开销。