一、简介为什么调度域与拓扑感知是现代系统的性能基石随着处理器架构从单核向多核、众核演进现代服务器普遍采用SMT同时多线程→ MC多核→ NUMA非统一内存访问的三级拓扑结构。以 Intel Xeon Platinum 为例单芯片可达 64 核心、128 线程4 路系统形成 512 线程的 NUMA 域。在这种复杂度下调度域Scheduling Domain成为内核理解硬件拓扑、做出最优调度决策的核心抽象。实际痛点跨 NUMA 节点迁移任务 → 内存访问延迟从 80ns 飙升至 300ns性能下降 4 倍SMT 兄弟线程竞争浮点单元 → 吞吐量反而低于单线程负载均衡算法不了解拓扑 → 频繁跨节点迁移缓存失效吞吐崩溃掌握调度域的价值目标人群核心收益内核开发者精确定位load_balance()中的拓扑决策逻辑云原生工程师优化 Kubernetes CPU Manager 的 NUMA 感知策略HPC 研究员理解 MPI 进程绑核背后的内核机制系统优化师通过isolcpus 调度域调优降低延迟 30%二、核心概念调度域与拓扑的 6 个关键词2.1 调度域Scheduling Domain调度域是具有相同负载均衡策略的 CPU 集合内核通过struct sched_domain描述其属性// kernel/sched/sched.h struct sched_domain { struct sched_domain *parent; // 上层域范围更大 struct sched_domain *child; // 下层域范围更小 unsigned long span[0]; // 域覆盖的 CPU 位图 unsigned int level; // 层级0SMT, 1MC, 2NUMA unsigned int flags; // SD_LOAD_BALANCE 等策略标志 unsigned int last_balance; // 上次均衡时间戳 unsigned int balance_interval; // 均衡间隔jiffies // 性能开销参数 unsigned int smt_gain; // SMT 超线程收益系数 unsigned int nr_balance_failed; // 连续失败计数 };2.2 拓扑层级与硬件映射层级英文硬件对应典型延迟调度策略0DIE单个芯片-内部优化1MC/Package多核封装10-20ns核心间均衡2NUMA多路节点100-300ns谨慎迁移3ALL全局系统-紧急均衡2.3 关键术语对照术语说明源码位置SDTLSched Domain Topology Level拓扑层级描述符数组kernel/sched/topology.csched_group调度组域内的 CPU 分组struct sched_groupcpu_capacityCPU 计算能力考虑频率/微架构update_cpu_capacity()imbalance域内负载不平衡度calculate_imbalance()三、环境准备搭建拓扑分析实验平台3.1 硬件环境配置最低要求推荐配置CPU4 核支持超线程2x Intel Xeon / AMD EPYC NUMA 系统内存8 GB每个 NUMA 节点 32 GB工具lscpu,numactlIntel VTune / AMD uProf3.2 软件环境# 1. 安装分析工具 sudo apt update sudo apt install -y \ linux-tools-common linux-tools-generic \ numactl hwloc libhwloc-dev \ sysfsutils debugfs # 2. 获取内核源码 mkdir -p ~/kernel-study cd ~/kernel-study git clone --depth 1 --branch v5.15 \ https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git \ linux-5.15 # 3. 验证拓扑信息 sudo apt install cpuid cpuid | grep -E (SMT|NUMA|topology) # 4. 启用内核调试接口 sudo mount -t debugfs none /sys/kernel/debug3.3 快速拓扑探测脚本#!/bin/bash # file: detect-topology.sh # 功能: 全面检测系统 CPU 拓扑结构 echo CPU 基础信息 lscpu | grep -E (Architecture|CPU\(s\)|Thread|Core|Socket|NUMA) echo -e \n NUMA 拓扑 numactl --hardware 2/dev/null || echo NUMA 不支持 echo -e \n 内核调度域 (sysfs) for cpu in /sys/devices/system/cpu/cpu[0-9]*; do cpuid$(basename $cpu) domain_dir$cpu/cpufreq # 部分系统有 domain 信息 if [ -d $cpu/topology ]; then echo --- $cpuid --- cat $cpu/topology/thread_siblings 2/dev/null | xargs echo SMT siblings: cat $cpu/topology/core_siblings 2/dev/null | xargs echo Core siblings: cat $cpu/topology/physical_package_id 2/dev/null | xargs echo Package: fi done echo -e \n 调度器 debug 信息 cat /proc/schedstat 2/dev/null | head -20 echo -e \n /proc/cpuinfo 核心数 grep -c ^processor /proc/cpuinfo运行示例输出 CPU 基础信息 Architecture: x86_64 CPU(s): 32 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 2 NUMA node(s): 2 NUMA 拓扑 available: 2 nodes (0-1) node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23 node 0 size: 65418 MB node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31四、应用场景云原生数据库的 NUMA 感知调度在云原生分布式数据库如 TiDB、CockroachDB的生产环境中调度域优化直接影响 P99 延迟。典型部署为 2 NUMA 节点 × 64 核心 × 2 SMT 256 线程。当 SQL 执行引擎的线程池被内核调度到跨 NUMA 节点时内存访问延迟从本地 80ns 变为远程 280nsTPC-C 测试显示 QPS 下降 35%。通过配置sched_domain的SD_NUMA标志结合numactl --membind强制本地内存分配可将跨节点迁移率从 12% 降至 1% 以下。此外SMT 感知可避免将两个计算密集型线程置于同一物理核心的超线程对防止浮点单元争用导致的吞吐量损失。这些优化均需深入理解topology.c中的build_sched_domains()和smp.c中的load_balance()决策逻辑。五、实际案例与步骤源码级深度分析5.1 调度域构建流程从 ACPI 到内核数据结构/* * kernel/sched/topology.c - 调度域构建核心 * 以下展示从固件拓扑到调度域的转换流程 */ /* * sched_domain_topology_level - 描述系统支持的拓扑层级 * 典型服务器: SMT → MC → NUMA → ALL */ static struct sched_domain_topology_level default_topology[] { #ifdef CONFIG_SCHED_SMT { cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) }, #endif #ifdef CONFIG_SCHED_MC { cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) }, #endif { cpu_cpu_mask, SD_INIT_NAME(DIE) }, // 单芯片内所有核心 #ifdef CONFIG_NUMA { numa_mask, numa_flags, SD_INIT_NAME(NUMA) }, #endif { NULL, }, }; /* * build_sched_domains() - 为每个 CPU 构建调度域层级 * 调用路径: smp_init() → sched_init_smp() → build_sched_domains() */ static int build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *attr) { struct sched_domain *sd, *parent; int i, j; for_each_cpu(i, cpu_map) { struct sched_domain_topology_level *tl; sd NULL; parent NULL; // 遍历拓扑层级从内到外构建 for (tl sched_domain_topology; tl-init; tl) { sd tl-init(tl, i); // 创建该 CPU 在当前层级的域 if (!sd) break; sd-level tl - sched_domain_topology; sd-parent parent; if (parent) parent-child sd; parent sd; } // 设置该 CPU 的顶层调度域 rq_attach_root(cpu_rq(i), sd); } // 计算域间不平衡阈值 calc_load_balance_metrics(cpu_map); return 0; }5.2 负载均衡决策何时跨域迁移/* * kernel/sched/fair.c - CFS 负载均衡核心 * load_balance() 决定是否以及如何从繁忙 CPU 拉取任务 */ static int load_balance(int this_cpu, struct rq *this_rq, struct sched_domain *sd, enum cpu_idle_type idle, int *continue_balancing) { unsigned long imbalance; struct rq *busiest; struct sched_group *sg; int ld_moved 0; // 1. 找到最繁忙的调度组 sg update_sd_lb_stats(sd, this_cpu, idle, sd_stats, sds); if (!sg) goto out_balanced; busiest find_busiest_queue(sd, this_cpu, idle, imbalance, sg); if (!busiest) goto out_balanced; // 2. 关键决策跨 NUMA 迁移的成本收益分析 if (sd-flags SD_NUMA) { // NUMA 域检查内存本地性 if (!numa_migrate_degrades_locality(this_cpu, busiest-cpu)) { // 迁移会损害本地性放弃或寻找替代 if (numa_has_capacity(this_cpu)) goto out_balanced; } } // 3. 执行迁移 ld_moved detach_tasks(env, lb_stats); if (ld_moved) { attach_tasks(env, lb_stats); sd-nr_balance_failed 0; } else { sd-nr_balance_failed; // 连续失败扩大搜索范围或升级父域 if (sd-nr_balance_failed sd-cache_nice_tries) *continue_balancing 0; } return ld_moved; }5.3 自动化调度域分析工具#!/usr/bin/env python3 # file: sched-domain-analyzer.py # 功能: 解析 /proc/schedstat 和 debugfs 调度域信息 import os import re import json from pathlib import Path from dataclasses import dataclass DEBUGFS_SCHED Path(/sys/kernel/debug/sched) PROC_SCHEDSTAT Path(/proc/schedstat) dataclass class SchedDomain: level: int name: str span: list # 覆盖的 CPU 列表 balance_interval: int nr_balance_failed: int 0 def parse_schedstat(): 解析 /proc/schedstat 获取调度统计 stats {} if not PROC_SCHEDSTAT.exists(): return stats with open(PROC_SCHEDSTAT) as f: lines f.readlines() # 第一行: 版本和时间戳 if lines: stats[version] lines[0].strip() # CPU 级统计 cpu_pattern re.compile(rcpu(\d)\s(\d)\s(\d)\s(\d)) for line in lines[1:]: match cpu_pattern.match(line) if match: cpu, yld, act, irq match.groups() stats[fcpu{cpu}] { yield_count: int(yld), active_count: int(act), irq_count: int(irq) } return stats def parse_domain_tree(): 从 debugfs 解析调度域层级 domains [] # 尝试读取 per-CPU 调度域信息 cpu_dirs list(Path(/sys/kernel/debug/sched).glob(cpu*)) \ if DEBUGFS_SCHED.exists() else [] for cpu_dir in sorted(cpu_dirs): cpu_id int(cpu_dir.name.replace(cpu, )) domain_file cpu_dir / domains if domain_file.exists(): with open(domain_file) as f: content f.read() # 解析域层级 for match in re.finditer( rdomain(\d).*?span:\s*([\d,]).*?level:\s*(\d), content, re.DOTALL ): domain_idx, span_str, level match.groups() span [int(x) for x in span_str.split(,)] domains.append(SchedDomain( levelint(level), namefdomain{domain_idx}_cpu{cpu_id}, spanspan, balance_interval1 int(level) # 指数退避 )) return domains def analyze_numa_topology(): 分析 NUMA 拓扑与调度域的映射 numa_info {} # 读取 NUMA 距离矩阵 distance_file Path(/sys/devices/system/node/distance) if distance_file.exists(): with open(distance_file) as f: lines f.readlines() numa_info[distance_matrix] [ [int(x) for x in line.split()] for line in lines ] # 读取每个节点的 CPU 列表 for node_dir in sorted(Path(/sys/devices/system/node).glob(node*)): cpulist_file node_dir / cpulist if cpulist_file.exists(): with open(cpulist_file) as f: numa_info[node_dir.name] f.read().strip() return numa_info def generate_report(): 生成完整分析报告 report { schedstat: parse_schedstat(), domains: [vars(d) for d in parse_domain_tree()], numa: analyze_numa_topology() } # 分析建议 recommendations [] # 检查 NUMA 不平衡 if report[numa]: nodes [k for k in report[numa].keys() if k.startswith(node)] if len(nodes) 1: recommendations.append( f检测到 {len(nodes)} 个 NUMA 节点 建议启用 numad 或配置 cgroup.cpuset.memory_spread ) # 检查调度域失败 for domain in report[domains]: if domain.get(nr_balance_failed, 0) 10: recommendations.append( f{domain[name]} 连续均衡失败 建议检查 CPU 隔离配置或调整 sched_domain 参数 ) report[recommendations] recommendations return report if __name__ __main__: import sys if len(sys.argv) 1 and sys.argv[1] --json: print(json.dumps(generate_report(), indent2)) else: report generate_report() print( * 60) print(Linux 调度域与拓扑分析报告) print( * 60) print(\n 调度统计 (schedstat):) for key, val in report[schedstat].items(): if isinstance(val, dict): print(f {key}: {val}) print(f\n 调度域层级 ({len(report[domains])} 个):) for d in report[domains]: print(f Level {d[level]}: {d[name]}) print(f Span: CPUs {d[span]}) print(f Balance interval: {d[balance_interval]} jiffies) print(f\n️ NUMA 拓扑:) for key, val in report[numa].items(): if not key.startswith(distance): print(f {key}: CPUs {val}) print(f\n 优化建议:) for rec in report[recommendations]: print(f • {rec}) print(\n * 60)运行示例chmod x sched-domain-analyzer.py sudo ./sched-domain-analyzer.py # 或输出 JSON 供进一步处理 sudo ./sched-domain-analyzer.py --json topology-report.json5.4 内核参数调优控制调度域行为#!/bin/bash # file: tune-sched-domains.sh # 功能: 调整调度域参数以优化特定场景 # 1. 查看当前调度域参数 echo 当前调度域参数 sysctl kernel.sched_domain | head -20 # 2. 调整负载均衡粒度以 jiffies 为单位通常 4ms # 增大间隔减少均衡开销减小间隔提高响应 echo 4 /proc/sys/kernel/sched_domain/cpu0/domain0/min_interval echo 8 /proc/sys/kernel/sched_domain/cpu0/domain0/max_interval # 3. 禁用特定 CPU 的负载均衡用于隔离实时任务 # 假设 CPU 0-3 用于实时任务 for cpu in 0 1 2 3; do echo 0 /sys/devices/system/cpu/cpu${cpu}/online 2/dev/null || true # 或保留在线但禁用均衡 # echo 0 /proc/sys/kernel/sched_domain/cpu${cpu}/domain0/flags done # 4. 启用 NUMA 平衡如果可用 if [ -f /proc/sys/kernel/numa_balancing ]; then echo 1 /proc/sys/kernel/numa_balancing echo NUMA 自动平衡已启用 fi # 5. 验证调整 echo -e \n 调整后状态 cat /proc/sys/kernel/sched_domain/cpu4/domain0/min_interval cat /proc/sys/kernel/sched_domain/cpu4/domain0/max_interval5.5 学术级拓扑可视化#!/usr/bin/env python3 # file: topology-visualizer.py # 功能: 生成调度域拓扑的 Graphviz 可视化 import os import sys from pathlib import Path def generate_dot_graph(): 生成 Graphviz DOT 格式的拓扑图 # 获取 CPU 信息 cpu_count os.cpu_count() or 4 # 简化模型假设 2-socket, 4-core-per-socket, SMT-2 # 实际系统应读取 /sys/devices/system/cpu/ nodes_per_socket cpu_count // 2 if cpu_count 4 else cpu_count dot [digraph sched_topology {] dot.append( rankdirTB;) dot.append( node [shapebox, stylefilled, fillcolorlightblue];) # NUMA 节点层 dot.append( subgraph cluster_numa {) dot.append( labelNUMA Domain (Level 2);) dot.append( styledashed;) # Socket 0 dot.append( subgraph cluster_socket0 {) dot.append( labelSocket 0 (DIE);) dot.append( fillcolorlightgreen;) # Core 层 for core in range(nodes_per_socket // 2): dot.append(f c0{core} [labelCore {core}\\nCPUs {core*2},{core*21}];) dot.append( }) # Socket 1 dot.append( subgraph cluster_socket1 {) dot.append( labelSocket 1 (DIE);) dot.append( fillcolorlightgreen;) for core in range(nodes_per_socket // 2): offset nodes_per_socket dot.append(f c1{core} [labelCore {core}\\nCPUs {offsetcore*2},{offsetcore*21}];) dot.append( }) dot.append( }) # end NUMA # 添加层级边表示调度域父子关系 dot.append( // Scheduling Domain hierarchy edges) dot.append( root - numa [labelparent, stylebold];) dot.append( numa - socket0 [labelchild];) dot.append( numa - socket1 [labelchild];) dot.append(}) return \n.join(dot) def main(): dot_content generate_dot_graph() # 保存 DOT 文件 output_file sched_topology.dot with open(output_file, w) as f: f.write(dot_content) print(fGraphviz DOT 文件已生成: {output_file}) print(渲染命令: dot -Tpng sched_topology.dot -o topology.png) # 尝试自动渲染 if os.system(which dot /dev/null 21) 0: os.system(dot -Tpng sched_topology.dot -o topology.png) print(拓扑图已渲染: topology.png) else: print(请安装 graphviz: sudo apt install graphviz) if __name__ __main__: main()六、常见问题与解答Q1: 如何确认当前系统的调度域层级# 方法1: 读取 debugfs需 root sudo cat /sys/kernel/debug/sched/cpu0/domains 2/dev/null # 方法2: 使用本文脚本 sudo python3 sched-domain-analyzer.py # 方法3: 内核日志启动时 dmesg | grep -i sched.*domain\|topologyQ2: 为什么跨 NUMA 迁移后性能反而下降# 诊断检查内存本地性 numastat -c $(pgrep your_app) # 典型输出numa_miss 高表示远程访问多 # 解决启用自动 NUMA 平衡或手动绑核 echo 1 /proc/sys/kernel/numa_balancing # 或强制本地分配 numactl --membind0 --cpunodebind0 ./your_appQ3: 如何完全禁用某个 CPU 的负载均衡// 内核模块示例动态修改调度域标志 #include linux/module.h #include linux/sched.h #include linux/cpumask.h static int __init disable_lb_init(void) { int cpu 0; // 目标 CPU struct rq *rq cpu_rq(cpu); struct sched_domain *sd; rcu_read_lock(); sd rcu_dereference(rq-sd); if (sd) { // 清除 LOAD_BALANCE 标志 sd-flags ~SD_LOAD_BALANCE; printk(Disabled load balance for CPU %d\n, cpu); } rcu_read_unlock(); return 0; } module_init(disable_lb_init);Q4: SMT 感知调度如何工作# 查看 SMT 拓扑 cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list # 内核通过 cpu_smt_mask 识别兄弟线程 # 在 fair.c 中wake_affine 优先选择空闲的 SMT 线程 # 禁用 SMT 调度紧急情况下 echo 0 /sys/devices/system/cpu/smt/active # 部分系统支持Q5: 如何验证调度域参数修改生效# 1. 修改前记录基准 cat /proc/schedstat /tmp/schedstat.before # 2. 应用修改如本文 tune-sched-domains.sh # 3. 运行负载测试 stress-ng --cpu 16 --timeout 60 # 4. 对比调度统计 cat /proc/schedstat /tmp/schedstat.after diff /tmp/schedstat.before /tmp/schedstat.after七、实践建议与最佳实践7.1 生产环境检查清单检查项命令预期结果NUMA 平衡启用cat /proc/sys/kernel/numa_balancing1调度域层级完整python3 sched-domain-analyzer.py层级 0-2 存在无异常均衡失败grep nr_balance_failed /proc/schedstat值 10CPU 隔离配置cat /sys/devices/system/cpu/isolated实时核已列出7.2 调试技巧ftrace 追踪调度域决策# 启用调度事件追踪 echo 0 /sys/kernel/debug/tracing/tracing_on echo /sys/kernel/debug/tracing/trace # 关注负载均衡事件 echo sched:sched_load_balance \ sched:sched_migrate_task \ sched:sched_wake_idle_without_ipi \ /sys/kernel/debug/tracing/set_event echo 1 /sys/kernel/debug/tracing/tracing_on # 运行测试 ./benchmark # 分析 cat /sys/kernel/debug/tracing/trace | grep -E (load_balance|migrate) | head -507.3 学术研究与论文写作量化指标采集# 持续采集调度统计 while true; do date %s schedstat-series.log cat /proc/schedstat schedstat-series.log sleep 1 done对比实验设计基线默认 CFS 自动 NUMA 平衡实验组禁用跨 NUMA 迁移 手动绑核指标吞吐量、P99 延迟、跨节点迁移次数可视化建议使用本文topology-visualizer.py生成拓扑图用 matplotlib 绘制imbalance随时间变化曲线八、总结与应用场景本文系统剖析了 Linux 调度域Scheduling Domain的层级结构及其与 CPU 拓扑SMT/MC/NUMA的映射机制建立了从硬件固件到内核数据结构的完整认知链路。核心要点调度域通过struct sched_domain层级实现从 SMT 到 NUMA 的渐进式负载均衡topology.c负责从 ACPI/DT 解析硬件拓扑fair.c的load_balance()执行实际决策NUMA 感知调度需权衡内存本地性与负载均衡通过numa_balancing和sched_domain参数精细控制典型应用场景云原生数据库通过调度域优化降低跨 NUMA 访问提升 TPC-C 性能 30%HPC 科学计算利用isolcpus 自定义调度域实现计算核与通信核分离实时交易系统禁用非关键核的负载均衡确保延迟敏感任务独占 CPU 资源边缘 AI 推理SMT 感知调度避免推理线程与系统服务争用执行单元掌握调度域机制意味着能够从内核层面理解并优化系统的资源分配策略。建议读者从修改kernel/sched/topology.c中的调试输出开始逐步深入到负载均衡算法的改进最终为 Linux 调度子系统贡献代码。附录一键实验环境# 获取本文全部脚本 mkdir -p ~/sched-domain-study cd ~/sched-domain-study # 保存 detect-topology.sh, sched-domain-analyzer.py, # tune-sched-domains.sh, topology-visualizer.py # 快速开始 chmod x *.sh sudo ./detect-topology.sh sudo python3 sched-domain-analyzer.py --json | tee baseline.json # 进行对比实验后 sudo python3 sched-domain-analyzer.py --json | tee optimized.json # 生成差异报告 diff baseline.json optimized.json improvement-report.txt本文基于 Linux 5.15 内核源码建议配合 Elixir Cross Referencer 在线浏览与 Intel 64 架构优化手册 使用。
Linux 调度域与拓扑感知:NUMA/SMT 架构下的负载均衡基础
一、简介为什么调度域与拓扑感知是现代系统的性能基石随着处理器架构从单核向多核、众核演进现代服务器普遍采用SMT同时多线程→ MC多核→ NUMA非统一内存访问的三级拓扑结构。以 Intel Xeon Platinum 为例单芯片可达 64 核心、128 线程4 路系统形成 512 线程的 NUMA 域。在这种复杂度下调度域Scheduling Domain成为内核理解硬件拓扑、做出最优调度决策的核心抽象。实际痛点跨 NUMA 节点迁移任务 → 内存访问延迟从 80ns 飙升至 300ns性能下降 4 倍SMT 兄弟线程竞争浮点单元 → 吞吐量反而低于单线程负载均衡算法不了解拓扑 → 频繁跨节点迁移缓存失效吞吐崩溃掌握调度域的价值目标人群核心收益内核开发者精确定位load_balance()中的拓扑决策逻辑云原生工程师优化 Kubernetes CPU Manager 的 NUMA 感知策略HPC 研究员理解 MPI 进程绑核背后的内核机制系统优化师通过isolcpus 调度域调优降低延迟 30%二、核心概念调度域与拓扑的 6 个关键词2.1 调度域Scheduling Domain调度域是具有相同负载均衡策略的 CPU 集合内核通过struct sched_domain描述其属性// kernel/sched/sched.h struct sched_domain { struct sched_domain *parent; // 上层域范围更大 struct sched_domain *child; // 下层域范围更小 unsigned long span[0]; // 域覆盖的 CPU 位图 unsigned int level; // 层级0SMT, 1MC, 2NUMA unsigned int flags; // SD_LOAD_BALANCE 等策略标志 unsigned int last_balance; // 上次均衡时间戳 unsigned int balance_interval; // 均衡间隔jiffies // 性能开销参数 unsigned int smt_gain; // SMT 超线程收益系数 unsigned int nr_balance_failed; // 连续失败计数 };2.2 拓扑层级与硬件映射层级英文硬件对应典型延迟调度策略0DIE单个芯片-内部优化1MC/Package多核封装10-20ns核心间均衡2NUMA多路节点100-300ns谨慎迁移3ALL全局系统-紧急均衡2.3 关键术语对照术语说明源码位置SDTLSched Domain Topology Level拓扑层级描述符数组kernel/sched/topology.csched_group调度组域内的 CPU 分组struct sched_groupcpu_capacityCPU 计算能力考虑频率/微架构update_cpu_capacity()imbalance域内负载不平衡度calculate_imbalance()三、环境准备搭建拓扑分析实验平台3.1 硬件环境配置最低要求推荐配置CPU4 核支持超线程2x Intel Xeon / AMD EPYC NUMA 系统内存8 GB每个 NUMA 节点 32 GB工具lscpu,numactlIntel VTune / AMD uProf3.2 软件环境# 1. 安装分析工具 sudo apt update sudo apt install -y \ linux-tools-common linux-tools-generic \ numactl hwloc libhwloc-dev \ sysfsutils debugfs # 2. 获取内核源码 mkdir -p ~/kernel-study cd ~/kernel-study git clone --depth 1 --branch v5.15 \ https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git \ linux-5.15 # 3. 验证拓扑信息 sudo apt install cpuid cpuid | grep -E (SMT|NUMA|topology) # 4. 启用内核调试接口 sudo mount -t debugfs none /sys/kernel/debug3.3 快速拓扑探测脚本#!/bin/bash # file: detect-topology.sh # 功能: 全面检测系统 CPU 拓扑结构 echo CPU 基础信息 lscpu | grep -E (Architecture|CPU\(s\)|Thread|Core|Socket|NUMA) echo -e \n NUMA 拓扑 numactl --hardware 2/dev/null || echo NUMA 不支持 echo -e \n 内核调度域 (sysfs) for cpu in /sys/devices/system/cpu/cpu[0-9]*; do cpuid$(basename $cpu) domain_dir$cpu/cpufreq # 部分系统有 domain 信息 if [ -d $cpu/topology ]; then echo --- $cpuid --- cat $cpu/topology/thread_siblings 2/dev/null | xargs echo SMT siblings: cat $cpu/topology/core_siblings 2/dev/null | xargs echo Core siblings: cat $cpu/topology/physical_package_id 2/dev/null | xargs echo Package: fi done echo -e \n 调度器 debug 信息 cat /proc/schedstat 2/dev/null | head -20 echo -e \n /proc/cpuinfo 核心数 grep -c ^processor /proc/cpuinfo运行示例输出 CPU 基础信息 Architecture: x86_64 CPU(s): 32 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 2 NUMA node(s): 2 NUMA 拓扑 available: 2 nodes (0-1) node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23 node 0 size: 65418 MB node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31四、应用场景云原生数据库的 NUMA 感知调度在云原生分布式数据库如 TiDB、CockroachDB的生产环境中调度域优化直接影响 P99 延迟。典型部署为 2 NUMA 节点 × 64 核心 × 2 SMT 256 线程。当 SQL 执行引擎的线程池被内核调度到跨 NUMA 节点时内存访问延迟从本地 80ns 变为远程 280nsTPC-C 测试显示 QPS 下降 35%。通过配置sched_domain的SD_NUMA标志结合numactl --membind强制本地内存分配可将跨节点迁移率从 12% 降至 1% 以下。此外SMT 感知可避免将两个计算密集型线程置于同一物理核心的超线程对防止浮点单元争用导致的吞吐量损失。这些优化均需深入理解topology.c中的build_sched_domains()和smp.c中的load_balance()决策逻辑。五、实际案例与步骤源码级深度分析5.1 调度域构建流程从 ACPI 到内核数据结构/* * kernel/sched/topology.c - 调度域构建核心 * 以下展示从固件拓扑到调度域的转换流程 */ /* * sched_domain_topology_level - 描述系统支持的拓扑层级 * 典型服务器: SMT → MC → NUMA → ALL */ static struct sched_domain_topology_level default_topology[] { #ifdef CONFIG_SCHED_SMT { cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) }, #endif #ifdef CONFIG_SCHED_MC { cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) }, #endif { cpu_cpu_mask, SD_INIT_NAME(DIE) }, // 单芯片内所有核心 #ifdef CONFIG_NUMA { numa_mask, numa_flags, SD_INIT_NAME(NUMA) }, #endif { NULL, }, }; /* * build_sched_domains() - 为每个 CPU 构建调度域层级 * 调用路径: smp_init() → sched_init_smp() → build_sched_domains() */ static int build_sched_domains(const struct cpumask *cpu_map, struct sched_domain_attr *attr) { struct sched_domain *sd, *parent; int i, j; for_each_cpu(i, cpu_map) { struct sched_domain_topology_level *tl; sd NULL; parent NULL; // 遍历拓扑层级从内到外构建 for (tl sched_domain_topology; tl-init; tl) { sd tl-init(tl, i); // 创建该 CPU 在当前层级的域 if (!sd) break; sd-level tl - sched_domain_topology; sd-parent parent; if (parent) parent-child sd; parent sd; } // 设置该 CPU 的顶层调度域 rq_attach_root(cpu_rq(i), sd); } // 计算域间不平衡阈值 calc_load_balance_metrics(cpu_map); return 0; }5.2 负载均衡决策何时跨域迁移/* * kernel/sched/fair.c - CFS 负载均衡核心 * load_balance() 决定是否以及如何从繁忙 CPU 拉取任务 */ static int load_balance(int this_cpu, struct rq *this_rq, struct sched_domain *sd, enum cpu_idle_type idle, int *continue_balancing) { unsigned long imbalance; struct rq *busiest; struct sched_group *sg; int ld_moved 0; // 1. 找到最繁忙的调度组 sg update_sd_lb_stats(sd, this_cpu, idle, sd_stats, sds); if (!sg) goto out_balanced; busiest find_busiest_queue(sd, this_cpu, idle, imbalance, sg); if (!busiest) goto out_balanced; // 2. 关键决策跨 NUMA 迁移的成本收益分析 if (sd-flags SD_NUMA) { // NUMA 域检查内存本地性 if (!numa_migrate_degrades_locality(this_cpu, busiest-cpu)) { // 迁移会损害本地性放弃或寻找替代 if (numa_has_capacity(this_cpu)) goto out_balanced; } } // 3. 执行迁移 ld_moved detach_tasks(env, lb_stats); if (ld_moved) { attach_tasks(env, lb_stats); sd-nr_balance_failed 0; } else { sd-nr_balance_failed; // 连续失败扩大搜索范围或升级父域 if (sd-nr_balance_failed sd-cache_nice_tries) *continue_balancing 0; } return ld_moved; }5.3 自动化调度域分析工具#!/usr/bin/env python3 # file: sched-domain-analyzer.py # 功能: 解析 /proc/schedstat 和 debugfs 调度域信息 import os import re import json from pathlib import Path from dataclasses import dataclass DEBUGFS_SCHED Path(/sys/kernel/debug/sched) PROC_SCHEDSTAT Path(/proc/schedstat) dataclass class SchedDomain: level: int name: str span: list # 覆盖的 CPU 列表 balance_interval: int nr_balance_failed: int 0 def parse_schedstat(): 解析 /proc/schedstat 获取调度统计 stats {} if not PROC_SCHEDSTAT.exists(): return stats with open(PROC_SCHEDSTAT) as f: lines f.readlines() # 第一行: 版本和时间戳 if lines: stats[version] lines[0].strip() # CPU 级统计 cpu_pattern re.compile(rcpu(\d)\s(\d)\s(\d)\s(\d)) for line in lines[1:]: match cpu_pattern.match(line) if match: cpu, yld, act, irq match.groups() stats[fcpu{cpu}] { yield_count: int(yld), active_count: int(act), irq_count: int(irq) } return stats def parse_domain_tree(): 从 debugfs 解析调度域层级 domains [] # 尝试读取 per-CPU 调度域信息 cpu_dirs list(Path(/sys/kernel/debug/sched).glob(cpu*)) \ if DEBUGFS_SCHED.exists() else [] for cpu_dir in sorted(cpu_dirs): cpu_id int(cpu_dir.name.replace(cpu, )) domain_file cpu_dir / domains if domain_file.exists(): with open(domain_file) as f: content f.read() # 解析域层级 for match in re.finditer( rdomain(\d).*?span:\s*([\d,]).*?level:\s*(\d), content, re.DOTALL ): domain_idx, span_str, level match.groups() span [int(x) for x in span_str.split(,)] domains.append(SchedDomain( levelint(level), namefdomain{domain_idx}_cpu{cpu_id}, spanspan, balance_interval1 int(level) # 指数退避 )) return domains def analyze_numa_topology(): 分析 NUMA 拓扑与调度域的映射 numa_info {} # 读取 NUMA 距离矩阵 distance_file Path(/sys/devices/system/node/distance) if distance_file.exists(): with open(distance_file) as f: lines f.readlines() numa_info[distance_matrix] [ [int(x) for x in line.split()] for line in lines ] # 读取每个节点的 CPU 列表 for node_dir in sorted(Path(/sys/devices/system/node).glob(node*)): cpulist_file node_dir / cpulist if cpulist_file.exists(): with open(cpulist_file) as f: numa_info[node_dir.name] f.read().strip() return numa_info def generate_report(): 生成完整分析报告 report { schedstat: parse_schedstat(), domains: [vars(d) for d in parse_domain_tree()], numa: analyze_numa_topology() } # 分析建议 recommendations [] # 检查 NUMA 不平衡 if report[numa]: nodes [k for k in report[numa].keys() if k.startswith(node)] if len(nodes) 1: recommendations.append( f检测到 {len(nodes)} 个 NUMA 节点 建议启用 numad 或配置 cgroup.cpuset.memory_spread ) # 检查调度域失败 for domain in report[domains]: if domain.get(nr_balance_failed, 0) 10: recommendations.append( f{domain[name]} 连续均衡失败 建议检查 CPU 隔离配置或调整 sched_domain 参数 ) report[recommendations] recommendations return report if __name__ __main__: import sys if len(sys.argv) 1 and sys.argv[1] --json: print(json.dumps(generate_report(), indent2)) else: report generate_report() print( * 60) print(Linux 调度域与拓扑分析报告) print( * 60) print(\n 调度统计 (schedstat):) for key, val in report[schedstat].items(): if isinstance(val, dict): print(f {key}: {val}) print(f\n 调度域层级 ({len(report[domains])} 个):) for d in report[domains]: print(f Level {d[level]}: {d[name]}) print(f Span: CPUs {d[span]}) print(f Balance interval: {d[balance_interval]} jiffies) print(f\n️ NUMA 拓扑:) for key, val in report[numa].items(): if not key.startswith(distance): print(f {key}: CPUs {val}) print(f\n 优化建议:) for rec in report[recommendations]: print(f • {rec}) print(\n * 60)运行示例chmod x sched-domain-analyzer.py sudo ./sched-domain-analyzer.py # 或输出 JSON 供进一步处理 sudo ./sched-domain-analyzer.py --json topology-report.json5.4 内核参数调优控制调度域行为#!/bin/bash # file: tune-sched-domains.sh # 功能: 调整调度域参数以优化特定场景 # 1. 查看当前调度域参数 echo 当前调度域参数 sysctl kernel.sched_domain | head -20 # 2. 调整负载均衡粒度以 jiffies 为单位通常 4ms # 增大间隔减少均衡开销减小间隔提高响应 echo 4 /proc/sys/kernel/sched_domain/cpu0/domain0/min_interval echo 8 /proc/sys/kernel/sched_domain/cpu0/domain0/max_interval # 3. 禁用特定 CPU 的负载均衡用于隔离实时任务 # 假设 CPU 0-3 用于实时任务 for cpu in 0 1 2 3; do echo 0 /sys/devices/system/cpu/cpu${cpu}/online 2/dev/null || true # 或保留在线但禁用均衡 # echo 0 /proc/sys/kernel/sched_domain/cpu${cpu}/domain0/flags done # 4. 启用 NUMA 平衡如果可用 if [ -f /proc/sys/kernel/numa_balancing ]; then echo 1 /proc/sys/kernel/numa_balancing echo NUMA 自动平衡已启用 fi # 5. 验证调整 echo -e \n 调整后状态 cat /proc/sys/kernel/sched_domain/cpu4/domain0/min_interval cat /proc/sys/kernel/sched_domain/cpu4/domain0/max_interval5.5 学术级拓扑可视化#!/usr/bin/env python3 # file: topology-visualizer.py # 功能: 生成调度域拓扑的 Graphviz 可视化 import os import sys from pathlib import Path def generate_dot_graph(): 生成 Graphviz DOT 格式的拓扑图 # 获取 CPU 信息 cpu_count os.cpu_count() or 4 # 简化模型假设 2-socket, 4-core-per-socket, SMT-2 # 实际系统应读取 /sys/devices/system/cpu/ nodes_per_socket cpu_count // 2 if cpu_count 4 else cpu_count dot [digraph sched_topology {] dot.append( rankdirTB;) dot.append( node [shapebox, stylefilled, fillcolorlightblue];) # NUMA 节点层 dot.append( subgraph cluster_numa {) dot.append( labelNUMA Domain (Level 2);) dot.append( styledashed;) # Socket 0 dot.append( subgraph cluster_socket0 {) dot.append( labelSocket 0 (DIE);) dot.append( fillcolorlightgreen;) # Core 层 for core in range(nodes_per_socket // 2): dot.append(f c0{core} [labelCore {core}\\nCPUs {core*2},{core*21}];) dot.append( }) # Socket 1 dot.append( subgraph cluster_socket1 {) dot.append( labelSocket 1 (DIE);) dot.append( fillcolorlightgreen;) for core in range(nodes_per_socket // 2): offset nodes_per_socket dot.append(f c1{core} [labelCore {core}\\nCPUs {offsetcore*2},{offsetcore*21}];) dot.append( }) dot.append( }) # end NUMA # 添加层级边表示调度域父子关系 dot.append( // Scheduling Domain hierarchy edges) dot.append( root - numa [labelparent, stylebold];) dot.append( numa - socket0 [labelchild];) dot.append( numa - socket1 [labelchild];) dot.append(}) return \n.join(dot) def main(): dot_content generate_dot_graph() # 保存 DOT 文件 output_file sched_topology.dot with open(output_file, w) as f: f.write(dot_content) print(fGraphviz DOT 文件已生成: {output_file}) print(渲染命令: dot -Tpng sched_topology.dot -o topology.png) # 尝试自动渲染 if os.system(which dot /dev/null 21) 0: os.system(dot -Tpng sched_topology.dot -o topology.png) print(拓扑图已渲染: topology.png) else: print(请安装 graphviz: sudo apt install graphviz) if __name__ __main__: main()六、常见问题与解答Q1: 如何确认当前系统的调度域层级# 方法1: 读取 debugfs需 root sudo cat /sys/kernel/debug/sched/cpu0/domains 2/dev/null # 方法2: 使用本文脚本 sudo python3 sched-domain-analyzer.py # 方法3: 内核日志启动时 dmesg | grep -i sched.*domain\|topologyQ2: 为什么跨 NUMA 迁移后性能反而下降# 诊断检查内存本地性 numastat -c $(pgrep your_app) # 典型输出numa_miss 高表示远程访问多 # 解决启用自动 NUMA 平衡或手动绑核 echo 1 /proc/sys/kernel/numa_balancing # 或强制本地分配 numactl --membind0 --cpunodebind0 ./your_appQ3: 如何完全禁用某个 CPU 的负载均衡// 内核模块示例动态修改调度域标志 #include linux/module.h #include linux/sched.h #include linux/cpumask.h static int __init disable_lb_init(void) { int cpu 0; // 目标 CPU struct rq *rq cpu_rq(cpu); struct sched_domain *sd; rcu_read_lock(); sd rcu_dereference(rq-sd); if (sd) { // 清除 LOAD_BALANCE 标志 sd-flags ~SD_LOAD_BALANCE; printk(Disabled load balance for CPU %d\n, cpu); } rcu_read_unlock(); return 0; } module_init(disable_lb_init);Q4: SMT 感知调度如何工作# 查看 SMT 拓扑 cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list # 内核通过 cpu_smt_mask 识别兄弟线程 # 在 fair.c 中wake_affine 优先选择空闲的 SMT 线程 # 禁用 SMT 调度紧急情况下 echo 0 /sys/devices/system/cpu/smt/active # 部分系统支持Q5: 如何验证调度域参数修改生效# 1. 修改前记录基准 cat /proc/schedstat /tmp/schedstat.before # 2. 应用修改如本文 tune-sched-domains.sh # 3. 运行负载测试 stress-ng --cpu 16 --timeout 60 # 4. 对比调度统计 cat /proc/schedstat /tmp/schedstat.after diff /tmp/schedstat.before /tmp/schedstat.after七、实践建议与最佳实践7.1 生产环境检查清单检查项命令预期结果NUMA 平衡启用cat /proc/sys/kernel/numa_balancing1调度域层级完整python3 sched-domain-analyzer.py层级 0-2 存在无异常均衡失败grep nr_balance_failed /proc/schedstat值 10CPU 隔离配置cat /sys/devices/system/cpu/isolated实时核已列出7.2 调试技巧ftrace 追踪调度域决策# 启用调度事件追踪 echo 0 /sys/kernel/debug/tracing/tracing_on echo /sys/kernel/debug/tracing/trace # 关注负载均衡事件 echo sched:sched_load_balance \ sched:sched_migrate_task \ sched:sched_wake_idle_without_ipi \ /sys/kernel/debug/tracing/set_event echo 1 /sys/kernel/debug/tracing/tracing_on # 运行测试 ./benchmark # 分析 cat /sys/kernel/debug/tracing/trace | grep -E (load_balance|migrate) | head -507.3 学术研究与论文写作量化指标采集# 持续采集调度统计 while true; do date %s schedstat-series.log cat /proc/schedstat schedstat-series.log sleep 1 done对比实验设计基线默认 CFS 自动 NUMA 平衡实验组禁用跨 NUMA 迁移 手动绑核指标吞吐量、P99 延迟、跨节点迁移次数可视化建议使用本文topology-visualizer.py生成拓扑图用 matplotlib 绘制imbalance随时间变化曲线八、总结与应用场景本文系统剖析了 Linux 调度域Scheduling Domain的层级结构及其与 CPU 拓扑SMT/MC/NUMA的映射机制建立了从硬件固件到内核数据结构的完整认知链路。核心要点调度域通过struct sched_domain层级实现从 SMT 到 NUMA 的渐进式负载均衡topology.c负责从 ACPI/DT 解析硬件拓扑fair.c的load_balance()执行实际决策NUMA 感知调度需权衡内存本地性与负载均衡通过numa_balancing和sched_domain参数精细控制典型应用场景云原生数据库通过调度域优化降低跨 NUMA 访问提升 TPC-C 性能 30%HPC 科学计算利用isolcpus 自定义调度域实现计算核与通信核分离实时交易系统禁用非关键核的负载均衡确保延迟敏感任务独占 CPU 资源边缘 AI 推理SMT 感知调度避免推理线程与系统服务争用执行单元掌握调度域机制意味着能够从内核层面理解并优化系统的资源分配策略。建议读者从修改kernel/sched/topology.c中的调试输出开始逐步深入到负载均衡算法的改进最终为 Linux 调度子系统贡献代码。附录一键实验环境# 获取本文全部脚本 mkdir -p ~/sched-domain-study cd ~/sched-domain-study # 保存 detect-topology.sh, sched-domain-analyzer.py, # tune-sched-domains.sh, topology-visualizer.py # 快速开始 chmod x *.sh sudo ./detect-topology.sh sudo python3 sched-domain-analyzer.py --json | tee baseline.json # 进行对比实验后 sudo python3 sched-domain-analyzer.py --json | tee optimized.json # 生成差异报告 diff baseline.json optimized.json improvement-report.txt本文基于 Linux 5.15 内核源码建议配合 Elixir Cross Referencer 在线浏览与 Intel 64 架构优化手册 使用。