3. Linux CPU 调度器基础:CFS、调度域与 CPU 亲和性
在深入 containerd 的 CPU 管理策略之前,必须理解底层 Linux 内核的 CPU 调度机制。容器本质上是进程组,其 CPU 资源分配完全由内核调度器决定。本章聚焦于完全公平调度器(CFS)、调度域与调度组,以及 CPU 亲和性(affinity)这三个核心概念,它们是后续绑核调优的理论基石。
3.1 完全公平调度器(CFS)
CFS(Completely Fair Scheduler)自 Linux 2.6.23 起成为默认调度器,其设计哲学是“让每个进程都能公平地获得 CPU 时间”。CFS 摒弃了传统的时间片轮转,引入虚拟运行时间(vruntime)概念。
- 虚拟运行时间(vruntime):每个进程的 vruntime 根据其实际运行时间和优先级加权计算。CFS 总是选择 vruntime 最小的进程来运行,确保所有进程在宏观上获得等比例的 CPU 时间。
- 红黑树(Red-Black Tree):CFS 使用红黑树来组织就绪队列。vruntime 作为键值,树的最左节点即为下一个要运行的进程。插入和查找操作的时间复杂度为 O(log n),高效支持大量进程。
- 调度周期与最小粒度:CFS 通过
sched_latency_ns(调度延迟)和sched_min_granularity_ns(最小粒度)控制调度频率。默认调度延迟为 6ms,最小粒度为 0.75ms,确保交互式进程的响应性。
cpu.weight 接口控制容器的 CFS 权重。当多个容器争抢 CPU 时,CFS 根据权重比例分配时间片,而非固定时间片。
3.2 调度域与调度组
现代服务器通常采用 NUMA(非统一内存访问)架构,CPU 和内存被划分为多个节点。调度域(Scheduling Domain)是内核为管理 NUMA 拓扑而引入的层级结构,调度组(Scheduling Group)则是域内的基本调度单元。
| 层级 | 名称 | 说明 | 负载均衡范围 |
|---|---|---|---|
| L0 | SMT(超线程) | 同一物理核上的多个逻辑核 | 核内均衡 |
| L1 | MC(多核) | 同一 CPU 封装内的所有物理核 | 核间均衡 |
| L2 | NUMA | 同一 NUMA 节点内的所有 CPU | 节点内均衡 |
| L3 | NUMA(跨节点) | 跨 NUMA 节点的所有 CPU | 全局均衡 |
调度域的核心作用是实现负载均衡。内核定期在域内各调度组之间迁移进程,使各组负载趋于均衡。每个调度组包含一组 CPU,组内进程共享该组资源。容器绑核时,若未指定 NUMA 节点,调度器可能将容器进程迁移到远端节点,导致内存访问延迟增加。
numactl --membind),否则绑核收益可能被跨节点内存访问抵消。
3.3 CPU 亲和性(Affinity)
CPU 亲和性决定进程可以在哪些 CPU 上运行。Linux 提供两种亲和性控制机制:软亲和性(通过调度器倾向性)和硬亲和性(通过 CPU 掩码强制绑定)。
- 软亲和性(Soft Affinity):调度器倾向于将进程留在上次运行的 CPU 上,利用 CPU 缓存热度。这是默认行为,但调度器可能因负载均衡而迁移进程。
- 硬亲和性(Hard Affinity):通过
sched_setaffinity()系统调用设置 CPU 掩码(cpumask),强制进程只能在指定 CPU 上运行。容器绑核即采用此机制。
CPU 掩码通常以十六进制表示,例如 0x0F 表示 CPU 0~3。在 containerd 中,绑核通过 cpuset cgroup 控制器实现,其核心文件为 cpuset.cpus 和 cpuset.mems。
# 查看进程当前 CPU 亲和性
taskset -p <PID>
# 设置进程绑定到 CPU 0-3
taskset -p 0x0F <PID>
# 通过 cgroup v2 设置容器绑核
echo "0-3" > /sys/fs/cgroup/<container-path>/cpuset.cpus
echo "0" > /sys/fs/cgroup/<container-path>/cpuset.mems
3.4 核心概念关系图
下图展示了 CFS、调度域与 CPU 亲和性在容器调度中的协作关系:
3.5 调度域拓扑示例
以下 SVG 展示了一个双路 NUMA 服务器的调度域层级结构,帮助理解容器绑核时的拓扑选择:
3.6 关键参数速查表
| 参数/接口 | 所属层级 | 作用 | 容器场景示例 |
|---|---|---|---|
cpu.weight |
cgroup v2 | 设置容器 CFS 权重 | echo 100 > cpu.weight |
cpuset.cpus |
cgroup v2 | 强制绑定容器到指定 CPU | echo "0-3" > cpuset.cpus |
cpuset.mems |
cgroup v2 | 指定容器内存分配 NUMA 节点 | echo "0" > cpuset.mems |
sched_setaffinity() |
系统调用 | 设置进程硬亲和性 | taskset 工具底层实现 |
/proc/cpuinfo |
内核接口 | 查看 CPU 拓扑信息 | grep "physical id" /proc/cpuinfo |
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] 下的 SystemdCgroup = true 启用 cgroup v2,然后利用 Kubernetes 的 cpuManagerPolicy 或直接操作 cpuset 实现绑核。