2. Linux CFS调度器原理:完全公平调度器(CFS)核心概念与数据结构
完全公平调度器(Completely Fair Scheduler, CFS)自 Linux 2.6.23 引入以来,一直是默认的进程调度器。其设计哲学颠覆了传统基于时间片轮转的调度模型,转而采用基于“虚拟运行时间”的无限小粒度调度,旨在为每个进程提供对 CPU 时间的“完全公平”分配。理解 CFS 是进行 Docker 容器 CPU 性能调优的理论基石。
2.1 核心设计理念:公平与虚拟运行时间
CFS 不直接分配固定时间片,而是维护一个称为 虚拟运行时间(vruntime) 的度量。每个可运行进程的 vruntime 按实际消耗的 CPU 时间加权(考虑优先级)增长。CFS 的调度决策极其简单:始终选择 vruntime 最小的进程来运行。
核心公式:
vruntime += 实际运行时间 * (NICE_0_LOAD / 进程权重)
其中 NICE_0_LOAD 是 nice 值为 0 时的标准权重(1024)。权重越高(优先级越高),vruntime 增长越慢,从而获得更多 CPU 时间。
关键洞察: CFS 的目标是让所有进程的 vruntime 尽可能接近。当一个进程运行足够长的时间后,它的 vruntime 会超过其他等待的进程,从而被换出,让给下一个“最饥饿”的进程。
2.2 核心数据结构:红黑树与调度实体
CFS 使用 红黑树(Red-Black Tree) 作为就绪队列的核心数据结构,以 vruntime 为键值组织所有可运行进程。这使得“查找最小 vruntime 进程”的操作时间复杂度为 O(1)(直接取最左节点)。
| 数据结构 | 内核源码表示 | 作用与说明 |
|---|---|---|
| 调度实体 (sched_entity) | struct sched_entity |
CFS 调度的基本单位。每个进程(task_struct)内嵌一个 se 实例,包含 vruntime、权重、负载贡献等关键字段。 |
| CFS 就绪队列 (cfs_rq) | struct cfs_rq |
管理某个调度类(CFS)下所有可运行进程的队列。包含红黑树根节点 tasks_timeline、最小 vruntime min_vruntime、运行负载 load 等。 |
| 红黑树节点 (rb_node) | struct rb_node |
嵌入在 sched_entity 中,用于将 se 链接到 cfs_rq 的红黑树上。键值为 se->vruntime。 |
容器调优注意: 在 Docker 容器场景下,每个容器内的进程组(通过 CPU Cgroup)会被视为一个 组调度实体(task group)。CFS 在 cfs_rq 中同时管理普通进程实体和组实体,这使得容器级别的 CPU 份额控制成为可能。
2.3 关键调度流程与参数
CFS 的调度周期由 sched_latency_ns(调度延迟)和 min_granularity_ns(最小粒度)两个核心参数控制。它们决定了进程在被抢占前可以运行的最短和最长时长。
- 调度延迟(sched_latency_ns): CFS 期望在一个调度周期内,所有可运行进程至少被调度一次。默认值通常为 6ms(
6000000)。 - 最小粒度(sched_min_granularity_ns): 进程被抢占前至少运行的时间,防止过于频繁的上下文切换。默认值通常为 0.75ms(
750000)。 - 目标延迟(targeted_latency): 实际计算出的时间片。当进程数 N 较少时,时间片 = sched_latency_ns / N;当 N 较大时,时间片被限制在 min_granularity_ns 以上。
时间片计算示例(假设 sched_latency=6ms, min_granularity=0.75ms):
- 2 个进程:每个进程时间片 = 6ms / 2 = 3ms
- 10 个进程:每个进程时间片 = 6ms / 10 = 0.6ms,但低于 min_granularity,因此实际时间片 = 0.75ms
这意味着当运行队列很长时,CFS 会牺牲“完全公平”的周期,转而保证每个进程至少获得一个最小运行时间片。
2.4 与 Docker 容器调优的关联
Docker 通过 CPU Cgroup 子系统与 CFS 交互。每个容器对应一个 Cgroup,CFS 通过以下参数实现容器级别的 CPU 限制和权重分配:
| Docker 参数 | 影响的 CFS 内核参数 | 调优效果 |
|---|---|---|
--cpus |
cpu.cfs_period_us 和 cpu.cfs_quota_us |
限制容器在周期(默认 100ms)内最多使用的 CPU 时间。例如 --cpus=1.5 意味着每 100ms 最多使用 150ms 的 CPU 时间。 |
--cpu-shares |
cpu.shares (映射到 CFS 权重) |
设置容器在 CPU 竞争时的相对权重。默认 1024。权重越高,在 CPU 繁忙时获得的份额越多。 |
--cpu-rt-runtime |
cpu.rt_runtime_us |
限制实时进程的 CPU 时间,防止实时进程饿死 CFS 进程。 |
调优实践: 对于延迟敏感的容器(如 Web 服务器),可以适当减小
sched_min_granularity_ns(通过内核参数 kernel.sched_min_granularity_ns),让 CFS 更频繁地进行调度,减少进程的等待时间。但需注意,过小的粒度会增加上下文切换开销。