Linux 拓扑构建:build_sched_domains 的调度域生成流程
简介
在 Linux SMP(对称多处理器)与 NUMA(非统一内存访问)系统中,调度域(sched_domain) 是内核实现跨 CPU 负载均衡的核心基础设施。系统启动阶段或 CPU 热插拔时,内核必须依据 CPU 物理拓扑(SMT 超线程、物理核心、NUMA 节点)构建分层调度域与调度组(sched_group),为后续负载均衡提供拓扑依据与约束规则。
build_sched_domains作为调度域生成的核心函数,承担着解析 CPU 拓扑、分配调度域 / 调度组内存、构建层级关系、初始化负载均衡参数的关键职责。若调度域拓扑构建异常,会直接导致负载均衡失效、CPU 利用率失衡、跨 NUMA 节点调度时延激增、实时任务抢占紊乱等严重问题。
对于内核开发者、嵌入式 Linux 工程师、服务器性能调优人员而言,吃透build_sched_domains的实现逻辑、拓扑层级生成规则、调度组划分策略,是理解 Linux 多核心调度架构、排查负载均衡异常、优化 NUMA 系统性能、定制化调度拓扑的必备能力。本文从核心概念、环境搭建、源码逐行解析、实操验证、问题排查到最佳实践,全链路拆解调度域生成流程,内容可直接用于内核源码研读、学术论文撰写、服务器性能优化方案落地。
一、核心概念与术语解析
1.1 调度域 struct sched_domain
调度域是内核为 CPU 划分的调度管理单元,每个 CPU 对应一个分层的调度域链表(通过parent指针串联),核心作用是定义负载均衡的范围与规则。
// kernel/sched/sched.h 核心结构体
struct sched_domain {
struct sched_domain *parent; /* 父调度域,指向更高层级 */
struct sched_domain *child; /* 子调度域,指向更低层级 */
struct sched_group *groups; /* 调度组链表,循环链表 */
struct cpumask span; /* 该调度域覆盖的CPU掩码 */
unsigned int level; /* 拓扑层级(SMT/MC/SMP/NUMA) */
unsigned long flags; /* 负载均衡控制标志(SD_*) */
int imbalance_pct; /* 负载不均衡阈值 */
/* 负载统计、均衡周期等成员省略 */
};
- 层级关系:从底层 SMT(超线程)→ MC(多核心)→ SMP(物理 CPU)→ NUMA(节点)逐层向上,
span范围逐层扩大。 - 核心规则:负载均衡仅在同一调度域内的调度组之间进行,跨域不触发均衡。
1.2 调度组 struct sched_group
调度组是调度域内的 CPU 分组单元,将调度域的 CPU 划分为若干组,作为负载均衡的基本操作对象。
// kernel/sched/sched.h 核心结构体
struct sched_group {
struct sched_group *next; /* 同域调度组循环链表 */
atomic_t ref; /* 引用计数(多CPU共享) */
unsigned int group_weight; /* 组内CPU数量 */
struct sched_group_capacity *sgc; /* 组容量信息 */
unsigned long cpumask[0]; /* 组内CPU掩码(柔性数组) */
};
- 分组规则:调度域内所有调度组的
cpumask并集等于调度域span,交集为空。 - 共享特性:调度组为只读数据,可被同层级多个 CPU 的调度域共享,减少内存占用。
1.3 CPU 物理拓扑层级
Linux 内核默认按 4 层拓扑构建调度域,层级从低到高:
- SMT 层(超线程):同一物理核心下的虚拟 CPU(如 CPU0/1 为同一核心超线程)。
- MC 层(多核心):同一物理 CPU 下的多个核心(不含超线程)。
- SMP 层(物理 CPU):同一 NUMA 节点下的所有物理 CPU。
- NUMA 层(节点):整个系统的所有 NUMA 节点(最高层级)。
1.4 关键函数与调用链路
build_sched_domains:调度域生成入口,解析拓扑、构建层级、初始化参数。build_sched_domain:构建单个层级的调度域,分配内存、设置span与flags。build_sched_groups:为调度域划分调度组,构建循环链表。- 调用链路:
start_kernel→sched_init_smp→init_sched_domains→build_sched_domains。
二、环境准备
2.1 软硬件环境要求
| 环境类型 | 版本 / 配置要求 |
|---|---|
| 操作系统 | Ubuntu 20.04 / 22.04 64 位(支持 SMP/NUMA) |
| 内核版本 | Linux 5.15、6.1、6.6(LTS 版,拓扑逻辑稳定) |
| 硬件配置 | 4 核 8G 以上(支持 SMT/NUMA,推荐 Intel i7 或 AMD 锐龙) |
| 编译工具 | gcc 9.4+、make、libncurses-dev、bison、flex |
| 调试工具 | gdb、kgdb、perf、trace-cmd、ftrace、numactl |
2.2 内核源码获取与编译配置
1. 下载内核源码
# 安装依赖
sudo apt update && sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
# 下载Linux 6.1 LTS源码
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz
tar -xf linux-6.1.tar.xz
cd linux-6.1
2. 开启调度域调试与拓扑选项
cp -v /boot/config-$(uname -r) .config
make menuconfig
必须开启以下配置:
CONFIG_SCHED_DOMAIN=y # 启用调度域
CONFIG_SCHED_DEBUG=y # 调度调试
CONFIG_FTRACE=y # 函数跟踪
CONFIG_NUMA=y # 支持NUMA拓扑
CONFIG_SMP=y # 支持SMP
CONFIG_DEBUG_KERNEL=y # 内核调试
3. 编译安装内核
make -j$(nproc)
sudo make modules_install
sudo make install
sudo update-grub
重启后选择新内核进入。
2.3 源码定位
调度域核心源码路径:
kernel/sched/topology.c // build_sched_domains主逻辑
kernel/sched/sched.h // sched_domain/sched_group结构体
kernel/sched/core.c // 调度域挂载与初始化
三、应用场景
调度域拓扑构建是多核心 / NUMA 系统性能的基石,在服务器、嵌入式、高性能计算场景中至关重要。云服务器 NUMA 架构下,build_sched_domains按节点划分调度域,确保负载优先在本地 NUMA 节点内均衡,避免跨节点内存访问的高时延(跨 NUMA 访问时延是本地的 2-3 倍)。工业嵌入式多核心设备(如 PLC、运动控制器)中,SMT/MC 层级调度域隔离实时与非实时任务,防止非实时任务抢占导致实时任务超时。高性能计算(HPC)集群中,调度域拓扑约束负载均衡范围,减少跨核心 / 节点的上下文切换与缓存失效,提升计算密集型任务吞吐量。此外,CPU 热插拔场景下,build_sched_domains动态重构拓扑,保障系统稳定性与负载均衡连续性。
四、实际案例与源码深度剖析
4.1 调度域生成入口:init_sched_domains
系统启动时初始化调度域,调用build_sched_domains完成核心构建:
// kernel/sched/topology.c
int init_sched_domains(const struct cpumask *cpu_map)
{
int err;
// 架构层更新CPU拓扑(如NUMA节点、SMT信息)
arch_update_cpu_topology();
// 分配调度域数组(排除隔离CPU)
ndoms_cur = 1;
doms_cur = alloc_sched_domains(ndoms_cur);
if (!doms_cur)
doms_cur = &fallback_doms;
cpumask_andnot(doms_cur[0], cpu_map, cpu_isolated_map);
// 核心:调用build_sched_domains生成调度域拓扑
err = build_sched_domains(doms_cur[0], NULL);
if (err)
return err;
// 注册调度域sysctl接口(/proc/sys/kernel/domain)
register_sched_domain_sysctl();
return 0;
}
代码说明:cpu_map为在线 CPU 掩码,函数先过滤隔离 CPU,再调用核心函数生成拓扑。
4.2 核心函数:build_sched_domains 源码解析
该函数完成拓扑解析、调度域分配、层级构建、调度组划分,核心逻辑如下(精简版,带注释):
// kernel/sched/topology.c
static int
build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *attr)
{
struct sched_domain_topology_level *tl;
struct sched_domain *sd, *prev_sd;
struct cpumask *tmp_mask;
int cpu, i;
// 1. 初始化拓扑层级(SMT→MC→SMP→NUMA)
tl = sched_domain_topology;
for (; tl->init; tl++) {
// 遍历每个CPU,构建对应层级调度域
for_each_cpu(cpu, cpu_map) {
// 分配调度域内存(per-CPU,无锁访问)
sd = __sdt_alloc(cpu);
if (!sd)
return -ENOMEM;
// 2. 设置调度域span(覆盖CPU范围)
tl->mask(cpu, &sd->span);
cpumask_and(&sd->span, &sd->span, cpu_map);
// 3. 初始化调度域标志(负载均衡规则)
sd->flags = tl->flags;
sd->level = tl->level;
sd->imbalance_pct = tl->imbalance_pct;
// 4. 构建层级关系(child/parent指针)
prev_sd = per_cpu(d.sd, cpu);
sd->child = prev_sd;
if (prev_sd)
prev_sd->parent = sd;
per_cpu(d.sd, cpu) = sd;
}
}
// 5. 为每个调度域构建调度组
for_each_cpu(cpu, cpu_map) {
for (sd = per_cpu(d.sd, cpu); sd; sd = sd->child) {
// 划分调度组,构建循环链表
build_sched_groups(sd, cpu);
// 初始化调度组容量(CPU算力、负载能力)
init_sched_groups_capacity(cpu, sd);
}
}
// 6. 挂载调度域到CPU运行队列
for_each_cpu(cpu, cpu_map) {
rq = cpu_rq(cpu);
rq->sd = per_cpu(d.sd, cpu);
}
return 0;
}
核心逻辑拆解:
- 拓扑遍历:按
SMT→MC→SMP→NUMA顺序遍历层级,sched_domain_topology为全局拓扑表。 - 调度域分配:每个 CPU per-CPU 分配调度域,无锁访问,避免缓存伪共享。
- Span 设置:通过
tl->mask获取当前层级 CPU 范围,过滤后作为span。 - 层级关联:通过
child/parent指针串联层级,底层为 child,高层为 parent。 - 调度组构建:
build_sched_groups按物理拓扑分组,如 SMT 层每组 1 个虚拟 CPU,MC 层每组 1 个物理核心。
4.3 调度组构建:build_sched_groups 源码
为调度域划分调度组,构建循环链表,确保负载均衡可正常执行:
// kernel/sched/topology.c
static void build_sched_groups(struct sched_domain *sd, int cpu)
{
struct sched_group *sg, *first_sg;
struct cpumask *span = &sd->span;
int i;
first_sg = NULL;
// 遍历span中的CPU,按物理拓扑分组
for_each_cpu(i, span) {
// 分配调度组(柔性数组存cpumask)
sg = alloc_sched_group();
if (!sg)
panic("Failed to allocate sched group");
// 设置组内CPU掩码
cpumask_set_cpu(i, (struct cpumask *)sg->cpumask);
sg->group_weight = 1;
atomic_set(&sg->ref, 1);
// 构建循环链表
if (!first_sg) {
first_sg = sg;
sg->next = sg;
} else {
sg->next = first_sg->next;
first_sg->next = sg;
}
}
// 调度域关联调度组链表
sd->groups = first_sg;
}
代码说明:调度组为循环链表,groups指向包含当前 CPU 的组,确保负载均衡从本地组开始。
4.4 实操验证:查看调度域拓扑
1. 查看系统调度域层级
# 挂载debugfs
sudo mount -t debugfs none /sys/kernel/debug
# 查看CPU0的调度域层级(SMT→MC→SMP→NUMA)
cat /sys/kernel/debug/sched/domains/cpu0/domain
输出示例(层级从底层到高层):
domain 0 (SMT): span=0-1, groups=0,1
domain 1 (MC): span=0-3, groups=0-1,2-3
domain 2 (SMP): span=0-7, groups=0-3,4-7
domain 3 (NUMA): span=0-15, groups=0-7,8-15
2. 查看调度组信息
# 查看CPU0的SMT层调度组
cat /sys/kernel/debug/sched/domains/cpu0/domain/groups
4.5 Ftrace 跟踪 build_sched_domains 调用
# 清空跟踪缓存
echo > /sys/kernel/debug/tracing/trace
# 设置跟踪函数
echo build_sched_domains >> /sys/kernel/debug/tracing/set_ftrace_filter
echo build_sched_domain >> /sys/kernel/debug/tracing/set_ftrace_filter
echo build_sched_groups >> /sys/kernel/debug/tracing/set_ftrace_filter
# 开启跟踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 重启系统触发调度域重建(或热插拔CPU)
sudo reboot
# 查看跟踪日志
cat /sys/kernel/debug/tracing/trace
日志可清晰看到拓扑构建的完整调用链路,验证源码逻辑。
五、常见问题与解答
Q1:调度域层级顺序错乱,导致负载均衡异常?
解答:大概率是sched_domain_topology全局拓扑表配置错误,或arch_update_cpu_topology未正确识别 CPU 拓扑(如 SMT/NUMA 未开启)。排查:1. 检查内核CONFIG_SMP/NUMA/SMT是否开启;2. 查看/sys/kernel/debug/sched/domains层级是否为 SMT→MC→SMP→NUMA;3. 重新编译内核,确保拓扑表未被篡改。
Q2:NUMA 系统跨节点负载均衡频繁,时延高?
解答:调度域flags未正确设置SD_NUMA标志,导致高层 NUMA 域强制均衡,忽略本地节点优先规则。排查:1. 查看 NUMA 域flags是否包含SD_NUMA;2. 调整imbalance_pct(默认 25%),提高 NUMA 域不均衡阈值,减少跨节点均衡;3. 绑定任务到本地 NUMA 节点,降低跨节点访问。
Q3:CPU 热插拔后,调度域拓扑未更新?
解答:热插拔未触发build_sched_domains重建,或cpu_isolated_map过滤异常。排查:1. 查看热插拔日志dmesg | grep sched_domain;2. 手动触发重建:echo 1 > /sys/kernel/debug/sched/rebuild_domains;3. 检查内核CONFIG_HOTPLUG_CPU是否开启。
Q4:调度组链表断裂,导致内核崩溃?
解答:build_sched_groups分配调度组失败,或循环链表构建错误(next指针异常)。排查:1. 查看内核崩溃日志,确认是否为sched_group空指针;2. 开启CONFIG_SCHED_DEBUG,启用调度域错误检查;3. 重新编译内核,确保内存分配函数正常。
Q5:如何自定义调度域拓扑层级?
解答:内核支持通过set_sched_topology覆盖默认拓扑表,自定义层级与标志。步骤:1. 定义自定义拓扑数组struct sched_domain_topology_level my_topology[];2. 在arch_init_sched_domains中调用set_sched_topology(my_topology);3. 编译内核,验证自定义拓扑是否生效。
六、实践建议与最佳实践
-
内核调试建议:研读源码时重点关注
build_sched_domains的拓扑遍历、调度组构建逻辑,配合ftrace与debugfs动态观测,比静态源码更易理解层级关系;调试负载均衡异常时,优先核查调度域span与flags配置。 -
NUMA 系统优化:关闭不必要的跨 NUMA 节点负载均衡,提高 NUMA 域
imbalance_pct至 30%-40%;将计算密集型任务绑定到本地 NUMA 节点,减少跨节点内存访问时延。 -
SMT 系统调优:SMT 层调度域设置
SD_SMT标志,限制超线程间过度抢占;实时任务绑定到独立物理核心,避免超线程共享核心导致的时延波动。 -
内核定制开发:自定义调度策略时,不要修改默认拓扑生成逻辑,可通过新增调度域层级或修改
flags扩展功能,保留原生负载均衡能力。 -
问题排查规范:负载均衡异常排查顺序:1. 检查调度域层级是否完整;2. 确认
span覆盖范围正确;3. 核查调度组链表是否正常;4. 分析flags与均衡阈值配置,快速定位根因。
七、总结与应用延伸
本文从理论概念、环境搭建、结构体定义、核心源码逐行解析、实操验证、问题排查到工程最佳实践,完整拆解了 Linux 调度域生成核心函数build_sched_domains的设计思想、拓扑构建逻辑与层级管理机制。调度域本质是 CPU 物理拓扑的软件抽象,通过分层span与调度组划分,为负载均衡提供精准的范围约束与操作单元,是 Linux 多核心 / NUMA 系统高性能调度的基石。
从工程应用来看,该机制是云服务器、工业嵌入式、高性能计算集群的底层调度支撑;从学术研究与内核开发角度,掌握build_sched_domains逻辑,可深入理解 Linux SMP/NUMA 调度架构、物理拓扑与软件调度的映射关系、负载均衡的约束设计思想,可直接用于内核源码论文撰写、NUMA 系统性能优化、定制化调度拓扑开发。
建议读者基于本文提供的源码、实操命令与调试方法,自行编译内核复现实验,修改build_sched_domains的拓扑层级或flags配置,观测负载均衡行为与系统性能变化,真正做到从理论到实战吃透 Linux 调度域生成核心原理。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)