2、CFS调度器核心原理:虚拟运行时间(vruntime)、红黑树调度队列、调度周期与时间片计算

完全公平调度器(CFS,Completely Fair Scheduler)是 Linux 内核默认的 CPU 调度器,其设计哲学是“让每个进程都能公平地获得 CPU 时间”。在容器化环境中,CFS 通过 cgroup CPU 子系统实现组调度,确保多个容器或控制组按权重比例共享 CPU 资源。理解 CFS 的核心原理是配置容器 CPU 资源限制的基础。

2.1 虚拟运行时间(vruntime)

CFS 摒弃了传统的时间片轮转和优先级队列,引入了一个关键概念——虚拟运行时间(vruntime)。vruntime 是 CFS 调度决策的核心依据,它衡量了一个进程在理想公平模型下应获得的 CPU 时间。

  • 定义:vruntime 是进程实际运行时间经过优先级加权后的结果。优先级越高的进程,其 vruntime 增长越慢;优先级越低的进程,其 vruntime 增长越快。
  • 计算公式:vruntime += 实际运行时间 × (NICE_0_LOAD / 进程权重)。其中 NICE_0_LOAD 是 nice 值为 0 时的标准权重(1024),进程权重由 nice 值映射表决定。
  • 调度目标:CFS 总是选择 vruntime 最小的进程来运行,从而保证所有进程的 vruntime 尽可能接近,实现“公平”共享 CPU。
关键理解:vruntime 不是真实时间,而是一个经过归一化的“公平时间”度量。在容器组调度中,每个 cgroup 的 vruntime 由其内部所有进程的 vruntime 聚合而成,用于决定组间 CPU 分配。
权重与 nice 值映射示例(部分):
nice 值权重vruntime 增长速率(相对 nice=0)
-2088761极慢(约 1.15%)
-109548慢(约 10.7%)
01024基准(100%)
10335快(约 305%)
1915极快(约 6827%)

权重越高,vruntime 增长越慢,进程获得更多 CPU 时间。

vruntime 增长速率对比(nice 值影响) vruntime nice=0(基准) nice=-20(高权重,增长慢) nice=19(低权重,增长快) 0 实际运行时间 → CFS 选择 vruntime 最小的进程运行,使所有进程的 vruntime 趋向一致

2.2 红黑树调度队列

CFS 使用红黑树(Red-Black Tree)作为就绪队列的数据结构,用于高效管理所有可运行进程的 vruntime 排序。红黑树是一种自平衡二叉查找树,其插入、删除和查找操作的时间复杂度均为 O(log n)。

  • 键值:每个就绪进程的 vruntime 作为红黑树的键值。
  • 最左节点:红黑树的最左侧节点(即 vruntime 最小的进程)是 CFS 下一次要调度的进程。
  • 调度操作:当进程被调度运行时,它从红黑树中移除;当进程时间片用完或主动让出 CPU 时,更新其 vruntime 并重新插入红黑树。
  • 组调度扩展:在 cgroup 组调度中,每个 cgroup 内部维护一个独立的红黑树,同时 cgroup 本身作为“调度实体”参与上层红黑树的排序。
性能注意:红黑树虽然提供了 O(log n) 的查找性能,但在大量短生命周期进程频繁创建和销毁的场景下,树的重平衡开销不可忽视。CFS 通过“min_vruntime”缓存和“skip”机制进行优化。
CFS 红黑树调度队列结构 vruntime=100 vruntime=50 vruntime=150 vruntime=20 vruntime=80 vruntime=130 vruntime=180 ← 下次调度 红黑树按 vruntime 排序,最左节点(最小值)为下一个调度进程

2.3 调度周期与时间片计算

CFS 的调度周期和时间片计算与传统调度器有本质区别。CFS 不预先分配固定时间片,而是通过调度周期(sched_latency)和进程权重动态计算每个进程应获得的运行时间。

2.3.1 调度周期(sched_latency)

调度周期是 CFS 保证所有可运行进程至少被调度一次的时间窗口。默认值为 6ms(可通过内核参数 /proc/sys/kernel/sched_latency_ns 调整)。

  • 目标延迟:在调度周期内,所有就绪进程都应获得一次 CPU 运行机会。
  • 最小粒度:每个进程至少运行一个最小时间片(sched_min_granularity,默认 0.75ms),防止频繁上下文切换。
  • 进程数上限:当就绪进程数过多时,调度周期会按比例扩展,以保证每个进程获得的最小时间片不低于 sched_min_granularity。

2.3.2 时间片计算公式

每个进程在调度周期内获得的时间片由其权重占所有就绪进程总权重的比例决定:

进程时间片 = 调度周期 × (进程权重 / 所有就绪进程总权重)

示例:
- 调度周期 = 6ms
- 进程 A 权重 = 1024(nice=0)
- 进程 B 权重 = 335(nice=10)
- 总权重 = 1024 + 335 = 1359

进程 A 时间片 = 6ms × (1024 / 1359) ≈ 4.52ms
进程 B 时间片 = 6ms × (335 / 1359) ≈ 1.48ms
容器组调度中的时间片计算:在 cgroup 组调度模式下,每个 cgroup 被视为一个“调度实体”。cgroup 的时间片由其权重(cpu.shares)决定,cgroup 内部再按进程权重分配时间片。例如:
层级实体权重时间片分配
系统层cgroup_A(容器A)20486ms × (2048/4096) = 3ms
系统层cgroup_B(容器B)10246ms × (1024/4096) = 1.5ms
系统层其他进程10246ms × (1024/4096) = 1.5ms
cgroup_A内进程A1(nice=0)10243ms × (1024/2048) = 1.5ms
cgroup_A内进程A2(nice=5)10243ms × (1024/2048) = 1.5ms
调度周期与时间片分配流程 调度周期(sched_latency)= 6ms 所有就绪进程在此周期内至少被调度一次 计算总权重:sum(所有就绪进程权重) 时间片 = 调度周期 × (进程权重 / 总权重) 进程获得动态时间片,开始运行

2.4 CFS 在容器化环境中的关键参数

在容器化环境中,CFS 的行为可以通过以下内核参数和 cgroup 接口进行调优:

参数/接口路径说明默认值
sched_latency_ns/proc/sys/kernel/sched_latency_ns调度周期(纳秒)6000000(6ms)
sched_min_granularity_ns/proc/sys/kernel/sched_min_granularity_ns进程最小运行时间(纳秒)750000(0.75ms)
sched_wakeup_granularity_ns/proc/sys/kernel/sched_wakeup_granularity_ns唤醒抢占粒度(纳秒)1000000(1ms)
cpu.shares/sys/fs/cgroup/cpu/<group>/cpu.sharescgroup 的 CPU 权重(相对值)1024
cpu.cfs_period_us/sys/fs/cgroup/cpu/<group>/cpu.cfs_period_usCFS 带宽控制周期(微秒)100000(100ms)
cpu.cfs_quota_us/sys/fs/cgroup/cpu/<group>/cpu.cfs_quota_usCFS 带宽控制配额(微秒,-1 表示无限制)-1
容器配置建议:在 Kubernetes 等容器编排平台中,cpu.shares 对应 requests.cpu(权重保证),cpu.cfs_period_us 和 cpu.cfs_quota_us 对应 limits.cpu(硬限制)。理解 CFS 的时间片计算机制有助于合理设置容器 CPU 资源请求与限制。

2.5 组调度与 vruntime 继承

在 cgroup 组调度模式下,CFS 引入了调度实体(sched_entity)的概念,每个 cgroup 和每个进程都是一个调度实体。组调度的 vruntime 计算遵循以下规则:

  • 组 vruntime:cgroup 的 vruntime 由其内部所有进程的 vruntime 加权平均得到,用于在父层级中与其他 cgroup 或进程竞争 CPU。
  • 权重继承:cgroup 的权重(cpu.shares)决定了其在父层级中的时间片比例,内部进程再按各自权重分配组内时间片。
  • 嵌套调度:CFS 递归遍历调度层级树,从根红黑树开始,逐层选择 vruntime 最小的调度实体,直到选中一个具体的进程。
cgroup 组调度中的 vruntime 层级结构 根红黑树(系统层) cgroup_A(权重=2048) cgroup_B(权重=1024) 进程A1(权重=1024) 进程A2(权重=335) 进程B1(权重=1024) 进程B2(权重=1024) 调度顺序:根红黑树 → 选择 cgroup_A → cgroup_A 内部红黑树 → 选择进程A1 每个层级独立维护红黑树,vruntime 在层级间隔离

2.6 总结与关键要点

CFS 核心原理速查表:
概念作用容器化意义
vruntime衡量进程公平性的虚拟时间决定容器组内进程的 CPU 分配顺序
红黑树高效管理就绪进程的 vruntime 排序支持大规模容器场景下的 O(log n) 调度
调度周期保证所有进程获得 CPU 的时间窗口影响容器 CPU 延迟和吞吐量
时间片计算按权重比例动态分配 CPU 时间实现容器 CPU 请求(requests)的公平共享
组调度支持 cgroup 层级嵌套调度实现多租户容器环境的 CPU 资源隔离