4. 调度域(Scheduling Domain)概念
4.1 调度域的定义
调度域(Scheduling Domain)是 Linux 内核在 CFS(完全公平调度器) 之上构建的一组逻辑 CPU 集合,用于描述 CPU 之间的 负载均衡层级。每个调度域对应一个特定的拓扑层次(如单核、多核、NUMA 节点、整个系统),域内的 CPU 共享相同的调度策略和负载均衡规则。
核心定义: 调度域 = { CPU 位图 + 负载均衡策略 + 子域指针 + 父域指针 }。内核通过递归遍历调度域层级,实现从底层 CPU 到跨 NUMA 节点的全局负载均衡。
调度域的关键属性包括:
- span(CPU 位图):该域覆盖的所有 CPU 编号。
- level(层级编号):从底层(0)到顶层(N-1)递增。
- flags(标志位):控制负载均衡行为,如
SD_BALANCE_NEWIDLE、SD_BALANCE_EXEC、SD_BALANCE_FORK。 - child(子域指针):指向更细粒度的下层域。
- parent(父域指针):指向更粗粒度的上层域。
- groups(调度组链表):域内划分的 CPU 组,用于组间负载均衡。
4.2 调度域的层级结构
调度域采用 树形层级 组织,从最底层的物理 CPU 开始,逐层向上合并,直到覆盖整个系统。典型的层级结构如下:
| 层级 | 名称 | 覆盖范围 | 典型场景 |
|---|---|---|---|
| 0 | MC(Multi-Core) | 单个物理 CPU 的所有核心 | 超线程 / 同核共享 L2 缓存 |
| 1 | DIE(Die) | 单个 NUMA 节点内的所有 CPU | 同节点共享 L3 缓存 / 内存控制器 |
| 2 | NUMA(Non-Uniform Memory Access) | 跨 NUMA 节点的 CPU 集合 | 跨节点内存访问延迟差异 |
| 3 | ALL(System) | 系统中所有 CPU | 全局负载均衡兜底 |
注意: 层级数量取决于硬件拓扑。在单 NUMA 节点系统中,MC 域直接作为顶层域;在大型 NUMA 系统中,层级可能扩展到 4 层甚至更多(如 AMD EPYC 的 CCD → CCX → NUMA → System)。
调度域层级结构的核心遍历逻辑如下:
// 从当前 CPU 的底层域开始,逐层向上执行负载均衡
for_each_domain(cpu, sd) {
// sd 指向当前层级的调度域
// 执行该域内的负载均衡操作
load_balance(cpu, sd, idle);
// 通过 sd->parent 进入上一层域
sd = sd->parent;
}
4.3 调度域与 CPU 拓扑的关系
调度域是 CPU 拓扑的软件抽象。内核在启动时通过 sched_init_topology() 函数,读取 ACPI/DTB 中的 CPU 拓扑信息,动态构建调度域树。CPU 拓扑的物理结构直接决定了调度域的层级数量和覆盖范围。
以下 SVG 图展示了典型的 2-NUMA 节点系统中,调度域与 CPU 拓扑的映射关系:
4.4 调度域的关键数据结构
内核使用 struct sched_domain 描述每个调度域,其核心字段如下:
struct sched_domain {
struct sched_domain *parent; /* 指向父域(上层) */
struct sched_domain *child; /* 指向子域(下层) */
struct sched_group *groups; /* 该域内的调度组链表 */
unsigned long span[0]; /* CPU 位图,标记该域包含的 CPU */
int level; /* 层级编号,从 0 开始 */
unsigned int flags; /* 控制标志位 */
unsigned int imbalance_pct; /* 负载不均衡阈值百分比 */
unsigned int cache_nice_tries; /* 缓存亲和性尝试次数 */
unsigned int busy_factor; /* 繁忙因子 */
unsigned int max_newidle_lb_cost; /* 新空闲负载均衡最大开销 */
unsigned int max_interval; /* 负载均衡最大间隔 */
unsigned int min_interval; /* 负载均衡最小间隔 */
};
关键理解: 调度域不是静态的。当 CPU 热插拔、cgroup 配置变化或 NUMA 节点状态改变时,内核会通过
sched_domain_debug() 和 sched_init_domains() 重新构建调度域树。错误的拓扑配置会导致负载均衡失效,表现为 CPU 利用率不均、跨节点内存访问延迟增加。
4.5 调度域与负载均衡的交互流程
调度域是负载均衡的 执行容器。每次负载均衡触发时,内核从当前 CPU 所属的 最底层调度域 开始,逐层向上检查是否需要迁移任务。具体流程如下:
- 触发条件:任务唤醒、任务 fork、CPU 空闲、定时器触发。
- 域内扫描:在当前调度域内,遍历所有调度组,计算组间负载差异。
- 任务迁移:如果负载差异超过
imbalance_pct阈值,从最忙组迁移任务到最闲组。 - 层级上升:如果当前域无法平衡(所有组负载接近),通过
parent指针进入上层域。 - 全局兜底:到达系统域(SD_ALL)后,若仍不平衡,执行跨 NUMA 节点迁移。
以下 SVG 图展示了负载均衡在调度域层级中的执行路径:
4.6 调度域配置的查看与验证
在实际系统中,可以通过 /proc/schedstat 和 /sys/kernel/debug/sched/domains 查看调度域的实时拓扑。以下命令输出示例:
# 查看所有 CPU 的调度域层级
cat /proc/schedstat | grep "domain"
# 输出示例(2-NUMA 节点,每节点 4 核):
# domain0 0-3 (MC 域:CPU0-3)
# domain1 0-7 (NUMA 域:CPU0-7)
# domain2 0-7 (系统域:全部 CPU)
# 查看每个调度域的详细参数
cat /sys/kernel/debug/sched/domains/cpu0/domain0/flags
# 输出:0x00000fff (SD_BALANCE_NEWIDLE | SD_BALANCE_EXEC | ...)