简介

在多核心、NUMA 架构成为服务器主流配置的今天,Linux 内核负载均衡的效率直接决定系统整体性能与任务响应时延。早期 SMP 系统采用全局遍历 CPU 队列的均衡方式,在 CPU 核心数超过 16 后,跨核心、跨 NUMA 节点的任务迁移开销剧增,频繁迁移导致缓存失效、总线争抢,严重影响系统吞吐量。

为解决大规模 CPU 拓扑下的负载均衡难题,Linux 内核引入调度域(sched_domain) 核心机制,将 CPU 按物理拓扑(超线程、核心、NUMA 节点、整机)分层分组,构建 “局部优先、逐级扩散” 的层级化均衡架构。调度域作为负载均衡的核心组织单位,定义了均衡范围、迁移成本与触发策略,确保任务优先在低延迟、高缓存亲和性的域内迁移,仅当局部失衡严重时才向上层域扩散,大幅降低无效迁移开销。

对于内核研发工程师、服务器性能调优工程师、嵌入式 Linux 开发人员而言,吃透sched_domain结构体的字段含义、父子域层级关系、均衡参数配置与运行逻辑,是解决多核心负载不均、CPU 利用率失衡、上下文切换频繁等性能问题的核心能力。本文从核心概念、环境搭建、源码拆解、实操验证、问题排查到最佳实践,全链路剖析调度域工作原理,内容可直接用于内核源码研读、性能调优报告撰写、学术论文实验设计,帮助读者从底层掌握 Linux 多 CPU 负载均衡的核心逻辑。

一、核心概念与术语解析

1.1 调度域(sched_domain)定义

调度域是内核按照 CPU 物理拓扑划分的CPU 集合,是负载均衡的管理与执行单元。每个 CPU 都拥有独立的调度域层级结构,通过parent/child指针串联形成树形拓扑,层级从底层(超线程)到顶层(整机)递增。调度域的核心作用是:划定负载均衡的范围,控制均衡频率与触发阈值,定义任务迁移的成本优先级

1.2 调度组(sched_group)

调度域由若干调度组组成,调度组是负载均衡的最小执行单位。调度组内包含一个或多个 CPU,组内 CPU 负载视为整体,均衡时以组为单位计算负载差,决定是否迁移任务。底层调度域(如超线程域)中,一个调度组通常对应一个 CPU;高层调度域(如 NUMA 域)中,一个调度组可包含多个 CPU 核心。

1.3 调度域层级关系(父子域)

调度域采用自下而上的层级结构,典型 x86 服务器的层级如下(从底层到顶层):

  • MC 域(Multi-Core,层级 0):同一物理核心的超线程 CPU(如 CPU0 与 CPU1),迁移开销最小,缓存亲和性最高;
  • DIE 域(层级 1):同一 CPU 插槽内的所有物理核心,共享 L3 缓存,迁移开销中等;
  • NODE 域(层级 2):同一 NUMA 节点内的所有 CPU,跨节点访问内存延迟高,迁移开销大;
  • ROOT 域(层级 3):整机所有 NUMA 节点的 CPU,全局均衡,迁移开销最大。

层级核心规则:子域的 CPU 范围(span)必须是父域 span 的子集,父域均衡范围覆盖所有子域。

1.4 关键术语

  • span:调度域管辖的 CPU 掩码,标识均衡范围;
  • balance_interval:负载均衡的时间间隔,控制均衡频率;
  • imbalance_pct:触发均衡的负载不平衡百分比阈值;
  • cache_hot_time:缓存热数据保留时间,避免频繁迁移导致缓存失效;
  • SD_* 标志位:控制调度域行为的标志(如 SD_LOCAL、SD_NUMA)。

二、环境准备

2.1 软硬件环境要求

环境类型 版本 / 配置要求
操作系统 Ubuntu 20.04 / 22.04 64 位(支持 NUMA)
内核版本 Linux 5.15、6.1、6.6(支持调度域调试接口)
硬件配置 x86_64 架构,4 核 8G 内存(2 个物理核心,开启超线程,模拟 NUMA)
编译工具 gcc 9.4+、make、libncurses-dev、bison、flex
调试工具 perf、trace-cmd、ftrace、numactl、cpuset

2.2 内核编译与调试配置

1. 下载内核源码并安装依赖
# 安装编译依赖
sudo apt update && sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev numactl

# 下载Linux 6.1 LTS源码
wget shturl.cc/9vYMKZA5N8LJiB6968nzoGTthIvO6YDQmi4zyacmO6gGGgp1KVv
tar -xf linux-6.shturl.c
cd linux-6.1
2. 开启调度域调试与 NUMA 支持
cp -v /boot/config-$(uname -r) .config
make menuconfig

必须开启以下配置项:

CONFIG_SCHED_DOMAIN=y      # 启用调度域
CONFIG_NUMA=y              # 支持NUMA架构
CONFIG_DEBUG_KERNEL=y      # 内核调试
CONFIG_SCHED_DEBUG=y       # 调度器调试
CONFIG_FTRACE=y            # 函数跟踪
CONFIG_PROC_FS=y           # proc文件系统(查看调度域信息)
3. 编译安装内核
make -j$(nproc)
sudo make modules_install
sudo make install
sudo update-grub

重启系统,选择新编译内核进入。

2.3 调度域信息查看工具

内核调试文件系统提供调度域详细信息,直接挂载即可查看:

# 挂载调试文件系统
sudo mount -t debugfs none /sys/kernel/debug

# 查看所有CPU的调度域层级
ls /sys/kernel/debug/sched/domains/

# 查看CPU0的层级0(MC域)详细信息
cat /sys/kernel/debug/sched/domains/cpu0/domain0/

三、应用场景

调度域的层级化均衡机制是多核心、NUMA 服务器性能优化的核心,在数据库、云计算、高性能计算(HPC)等场景中至关重要。MySQL/PostgreSQL 数据库服务器多为 NUMA 架构,调度域优先在 NUMA 节点内均衡任务,避免跨节点内存访问延迟,保障查询响应速度;云计算宿主机需承载大量虚拟机,通过调度域将虚拟机任务绑定到指定 NUMA 节点,减少跨节点迁移,提升虚拟机性能稳定性;HPC 集群的计算节点依赖调度域的层级均衡,确保计算任务在低延迟的核心间分布,避免局部核心过载、空闲核心资源浪费。此外,嵌入式多核心处理器通过简化调度域层级,实现实时任务的快速均衡,保障硬实时场景的时间确定性。

四、实际案例与源码深度剖析

4.1 sched_domain 结构体核心源码解析

调度域结构体定义在include/linux/sched/topology.h,以下为 5.15 内核简化版源码,附带详细注释:

// include/linux/sched/topology.h
struct sched_domain {
    /* 父子域指针:构建层级拓扑,RCU保护无锁访问 */
    struct sched_domain __rcu *parent;  // 父调度域(高层级)
    struct sched_domain __rcu *child;   // 子调度域(低层级)

    /* 调度组链表:域内CPU分组,环形单向链表 */
    struct sched_group *groups;

    /* 均衡时间参数:控制均衡频率 */
    unsigned long min_interval;    // 最小均衡间隔(ms)
    unsigned long max_interval;    // 最大均衡间隔(ms)
    unsigned int busy_factor;      // 忙碌时间隔乘数(繁忙时降低均衡频率)

    /* 均衡触发阈值:控制均衡时机 */
    unsigned int imbalance_pct;    // 负载不平衡百分比阈值(默认125%)
    unsigned long cache_hot_time;  // 缓存热数据保留时间(避免频繁迁移)

    /* 层级与标志 */
    int level;                      // 调度域层级(0=底层,递增)
    unsigned int flags;             // 域行为标志(SD_LOCAL/SD_NUMA等)

    /* 统计信息:调试与优化用 */
    unsigned long last_balance;    // 上次均衡时间
    unsigned int nr_balance_failed; // 均衡失败次数

    /* 动态CPU掩码:管辖范围,柔性数组 */
    unsigned long span[];
};
关键字段深度解析
  1. parent/child:构建层级树,parent为 NULL 表示顶层域,child为 NULL 表示底层域;
  2. groups:指向调度组链表,均衡时遍历组间负载差;
  3. min_interval/max_interval:均衡间隔动态调整,系统空闲时用 max_interval 降低均衡频率,繁忙时用 min_interval 提升均衡效率;
  4. imbalance_pct:核心阈值,当某组负载超过其他组的imbalance_pct% 时触发均衡;
  5. span:柔性数组,存储 CPU 掩码(如 0b110 表示 CPU1、CPU2),定义均衡范围。

4.2 调度域层级构建源码流程

内核初始化时,通过sched_domain_init构建调度域层级,核心函数build_sched_domains按 CPU 拓扑生成父子域关系:

// kernel/sched/topology.c
static void build_sched_domains(void)
{
    int cpu;
    struct sched_domain *sd;

    // 遍历所有CPU,构建独立层级
    for_each_possible_cpu(cpu) {
        // 1. 创建底层调度域(MC域,level=0)
        sd = alloc_sched_domain(cpu);
        sd->level = 0;
        sd->min_interval = 10;    // 10ms
        sd->max_interval = 100;  // 100ms
        sd->imbalance_pct = 125; // 负载差超125%触发均衡
        cpumask_set_cpu(cpu, sd->span); // 初始仅包含当前CPU

        // 2. 向上构建父域(DIE/NODE/ROOT)
        sd->parent = build_parent_domain(sd, cpu);
        if (sd->parent)
            sd->parent->child = sd; // 绑定父子关系
    }
}

代码说明:每个 CPU 独立构建层级,底层域仅包含自身 CPU,父域逐步扩展 CPU 范围,最终形成完整层级树。

4.3 负载均衡核心逻辑:balance_domain

调度域均衡核心函数balance_domain,遍历域内调度组,计算负载差,决定是否迁移任务:

// kernel/sched/fair.c
static int balance_domain(struct sched_domain *sd, struct rq *rq)
{
    struct sched_group *group;
    struct rq *busiest_rq, *idlest_rq;
    unsigned int imbalance;

    // 1. 检查是否到达均衡间隔
    if (time_after(jiffies, rq->last_balance + sd->min_interval))
        return 0;

    // 2. 遍历域内调度组,查找最忙/最闲CPU队列
    group = sd->groups;
    busiest_rq = idlest_rq = group->rq;
    do {
        if (group->rq->nr_running > busiest_rq->nr_running)
            busiest_rq = group->rq;
        if (group->rq->nr_running < idlest_rq->nr_running)
            idlest_rq = group->rq;
        group = group->next;
    } while (group != sd->groups);

    // 3. 计算负载差,判断是否触发均衡
    imbalance = (busiest_rq->nr_running * 100) / idlest_rq->nr_running;
    if (imbalance > sd->imbalance_pct) {
        // 4. 执行任务迁移:从最忙队列迁移到最闲队列
        migrate_tasks(busiest_rq, idlest_rq);
        return 1;
    }
    return 0;
}

核心逻辑:先检查均衡间隔,再找最忙 / 最闲 CPU,负载差超阈值则迁移任务,优先迁移缓存热度低的任务。

4.4 实操:查看与修改调度域参数

1. 查看 CPU0 调度域层级详情
# 查看层级0(MC域)
cat /sys/kernel/debug/sched/domains/cpu0/domain0/
# 输出包含:level、span、min_interval、imbalance_pct等

# 查看层级1(DIE域)
cat /sys/kernel/debug/sched/domains/cpu0/domain1/
2. 动态修改 imbalance_pct 阈值
# 将CPU0层级0的不平衡阈值改为110%(更敏感)
echo 110 | sudo tee /sys/kernel/debug/sched/domains/cpu0/domain0/imbalance_pct
3. Ftrace 跟踪 balance_domain 函数
# 清空跟踪缓存
echo > /sys/kernel/debug/tracing/trace

# 设置跟踪函数
echo balance_domain >> /sys/kernel/debug/tracing/set_ftrace_filter
echo migrate_tasks >> /sys/kernel/debug/tracing/set_ftrace_filter

# 开启跟踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on

# 压测:用stress工具创建8个CPU密集型任务
stress -c 8

# 停止跟踪
echo 0 > /sys/kernel/debug/tracing/tracing_on

# 查看跟踪日志,验证均衡触发时机
cat /sys/kernel/debug/tracing/trace

4.5 调度组(sched_group)核心源码

// include/linux/sched/topology.h
struct sched_group {
    struct sched_group *next;    // 环形链表下一个组
    struct rq *rq;                // 组内CPU队列(底层域)
    cpumask_t cpumask;            // 组内CPU掩码
    unsigned int load;             // 组总负载
};

说明:底层域中每个组对应一个 CPU,高层域中组包含多个 CPU,load字段用于快速计算组负载。

五、常见问题与解答

Q1:如何判断系统是否开启调度域?

解答:执行cat /sys/kernel/debug/sched/domains/cpu0/domain0/,若能输出levelspan等字段则已开启;若提示文件不存在,检查内核是否开启CONFIG_SCHED_DOMAIN并重新编译。

Q2:NUMA 系统中,跨节点任务迁移频繁导致性能下降,如何优化?

解答:1. 提高 NODE 域的imbalance_pct(如改为 150%),降低跨节点均衡敏感度;2. 增大 NODE 域的min_interval(如改为 100ms),减少均衡频率;3. 用numactl绑定任务到指定 NUMA 节点,避免跨节点迁移。

Q3:调度域层级过多会影响性能吗?

解答:会。层级越多,均衡时遍历的域数量越多,开销越大。默认层级适配 CPU 拓扑,无需修改;若为 4 核以下小型系统,可通过内核参数简化层级,关闭 MC 域(超线程域)。

Q4:如何排查负载不均是否由调度域参数不合理导致?

解答:1. 用perf sched record -g记录调度事件,分析任务迁移频率;2. 查看/sys/kernel/debug/sched/domains/cpuX/domainY/nr_balance_failed,若失败次数高,说明阈值过严;3. 用 ftrace 跟踪balance_domain,确认均衡触发时机是否合理。

Q5:调度域的 span 字段可以手动修改吗?

解答:不建议手动修改。span 由内核根据 CPU 拓扑自动设置,手动修改会导致均衡范围错乱,出现任务无法调度、CPU 空闲等问题。若需限制任务 CPU 范围,使用cpusettaskset工具。

六、实践建议与最佳实践

  1. NUMA 系统优化:数据库、HPC 场景下,优先调整 NODE 域参数,提高imbalance_pct至 140%-150%,增大min_interval至 50-100ms,减少跨节点迁移;关键任务用numactl --cpunodebind=0绑定到指定 NUMA 节点。

  2. 超线程场景优化:开启超线程后,MC 域(层级 0)均衡开销小,保持默认参数;若超线程性能低于物理核心,可关闭 MC 域,直接在 DIE 域均衡。

  3. 性能调优顺序:先排查应用层负载(如线程数、锁竞争),再调整调度域参数;优先修改底层域参数,再调整高层域,避免全局均衡过于频繁。

  4. 内核定制建议:自研内核时,可根据 CPU 核心数动态调整层级,16 核以下保留 3 层,16 核以上扩展至 4 层;新增调度域标志,支持按业务类型(实时 / 非实时)差异化配置均衡策略。

  5. 调试规范:排查负载不均时,先查看nr_balance_failed判断均衡是否失败,再用 ftrace 跟踪balance_domain,最后分析imbalance_pctbalance_interval参数合理性,快速定位根因。

七、总结与应用延伸

本文从理论概念、环境搭建、结构体解析、核心源码逐行拆解、实操验证、问题排查到工程最佳实践,完整剖析了 Linux 调度域(sched_domain)的层级架构、字段含义与负载均衡工作原理。调度域本质是内核为多核心、NUMA 系统设计的层级化负载均衡框架,通过父子域划分均衡范围、动态调整均衡频率、精细化控制触发阈值,实现 “局部优先、逐级扩散” 的高效均衡,从底层解决大规模 CPU 拓扑下的负载不均与迁移开销问题。

从工程应用来看,调度域是服务器性能调优、嵌入式多核心系统开发、云计算资源调度的核心支撑;从学术研究与内核开发角度,掌握调度域的层级构建、均衡逻辑与参数优化,可深入理解 Linux 内核调度架构、CPU 拓扑感知设计、性能调优方法论,可直接用于内核源码论文撰写、NUMA 系统性能优化、定制化调度策略开发。

建议读者基于本文提供的源码、实操命令与调试方法,自行编译内核复现实验,修改imbalance_pctmin_interval等参数,观察 CPU 负载分布、任务迁移频率与系统吞吐量变化,真正做到从理论到实战吃透 Linux 调度域的核心原理,为实际项目中的性能优化提供底层技术支撑。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐