4、调度域初始化:sched_init_domains函数流程、CPU拓扑构建、节点与缓存层级映射

调度域(Scheduling Domain)是Linux内核在NUMA架构下实现负载均衡的核心数据结构。它描述了CPU之间的拓扑关系、缓存层级以及内存访问代价,是调度器感知NUMA并做出智能决策的基础。本章将深入分析调度域的初始化流程,重点解析sched_init_domains函数的执行路径、CPU拓扑的构建方法,以及节点与缓存层级的映射关系。

4.1 sched_init_domains函数流程

sched_init_domains是调度域初始化的入口函数,在内核启动阶段由sched_init_smp调用。该函数的核心任务是遍历所有可能的CPU,为每个CPU构建从底层到顶层的完整调度域层级。

关键路径: start_kernel → rest_init → kernel_init → kernel_init_freeable → smp_init → sched_init_smp → sched_init_domains

4.1.1 函数调用流程

  1. 初始化域层级数组:清零sched_domain_topology,准备存储各层级的构建函数指针。
  2. 注册默认拓扑层级:调用set_sched_topology注册默认的CPU拓扑层级描述。
  3. 遍历所有CPU:对每个在线CPU,调用build_sched_domains构建其调度域树。
  4. 检测与修复:调用sched_domain_debug进行一致性检查,修复可能的域层级断裂。
  5. 激活调度域:设置sched_domain_span位图,使能负载均衡。
步骤 函数/操作 说明
1 init_sched_domain_topology 初始化拓扑层级数组,设置默认层级数
2 set_sched_topology 注册CPU拓扑描述符,包含各层级的构建回调
3 build_sched_domains 为单个CPU构建完整的调度域树,包含域、组和标志
4 sched_domain_debug 验证域层级完整性,输出调试信息
5 update_sched_domain 激活调度域,使能周期性负载均衡

4.1.2 核心数据结构关系

调度域初始化涉及三个核心数据结构:sched_domain(调度域)、sched_group(调度组)和cpumask(CPU位图)。它们之间的关系如下:

// 调度域结构体(简化)
struct sched_domain {
    struct sched_domain *parent;    // 指向父域
    struct sched_domain *child;     // 指向子域
    struct sched_group *groups;     // 本域内的调度组链表
    unsigned long *span;            // 本域覆盖的CPU位图
    int level;                      // 域层级(0=最底层)
    unsigned int flags;             // 域标志(如SD_BALANCE_NEWIDLE)
    unsigned int imbalance_pct;     // 负载不均衡阈值百分比
    unsigned int cache_nice_tries;  // 缓存亲和性尝试次数
};

// 调度组结构体(简化)
struct sched_group {
    struct sched_group *next;       // 链表下一个组
    unsigned long *cpumask;         // 本组包含的CPU位图
    unsigned int group_weight;      // 组内CPU数量
    enum group_type type;           // 组类型(平衡/非平衡)
};
设计要点:调度域采用树形层级结构,每个域包含多个调度组。组是负载均衡的基本单元,域定义了均衡的范围。从底层(单个CPU或SMT线程)到顶层(整个NUMA节点或全系统),层级逐级合并,形成完整的拓扑视图。

4.2 CPU拓扑构建

CPU拓扑构建是调度域初始化的核心环节。内核通过cpu_topology数组记录每个CPU的物理拓扑信息,包括核心ID、簇ID、NUMA节点ID等。调度域构建函数build_sched_domains利用这些信息,自底向上创建域层级。

4.2.1 拓扑层级定义

默认的CPU拓扑层级由default_topology[]数组定义,每个层级包含一个cpu_mask函数和一个sd_flags函数:

// 默认CPU拓扑层级定义(简化)
static struct sched_domain_topology_level default_topology[] = {
    // 第0层:SMT(同步多线程)
    { cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) },
    // 第1层:MC(多核,共享L2/L3缓存)
    { cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) },
    // 第2层:DIE(芯片内,共享L3缓存)
    { cpu_die_mask, cpu_die_flags, SD_INIT_NAME(DIE) },
    // 第3层:NUMA(跨节点)
    { cpu_numa_mask, cpu_numa_flags, SD_INIT_NAME(NUMA) },
    { NULL, },
};
层级名称 掩码函数 覆盖范围 典型粒度
SMT cpu_smt_mask 同一物理核心的硬件线程 2-4个CPU
MC cpu_coregroup_mask 共享最后一级缓存的CPU簇 4-16个CPU
DIE cpu_die_mask 同一芯片封装内的所有CPU 8-64个CPU
NUMA cpu_numa_mask 同一NUMA节点内的所有CPU 16-256个CPU

4.2.2 构建过程详解

build_sched_domains函数为每个CPU执行以下步骤:

  1. 分配域节点:从sched_domains内存池中分配sched_domain结构体。
  2. 设置CPU位图:调用层级对应的cpu_mask函数,获取本域应包含的CPU集合。
  3. 创建调度组:根据CPU位图划分调度组,每个组包含一个或多个CPU。
  4. 设置域标志:调用sd_flags函数,根据层级特性设置SD_BALANCE_*等标志。
  5. 链接父子关系:将当前域设置为上一层的子域,形成树形结构。
  6. 计算均衡参数:设置imbalance_pct、cache_nice_tries等调优参数。
关键算法:调度组的划分采用"最小化组间通信代价"策略。在同一域内,内核尽量将共享缓存的CPU划分到同一组,以减少跨组迁移时的缓存失效开销。

4.3 节点与缓存层级映射

NUMA节点与缓存层级的映射关系决定了调度域的结构深度和负载均衡策略。内核通过numa_distance表和cache_shared_cpu_map来建立这种映射。

4.3.1 NUMA节点距离矩阵

每个NUMA节点对之间都有一个"距离"值,存储在numa_distance矩阵中。距离值越小,表示内存访问延迟越低,调度域合并的优先级越高。

// NUMA距离矩阵示例(4节点系统)
// 节点0 节点1 节点2 节点3
// [10,   20,   30,   40]   // 节点0到各节点的距离
// [20,   10,   40,   30]   // 节点1到各节点的距离
// [30,   40,   10,   20]   // 节点2到各节点的距离
// [40,   30,   20,   10]   // 节点3到各节点的距离

// 内核根据距离阈值决定是否合并域
#define NUMA_DISTANCE_THRESHOLD 20  // 距离小于等于20视为"近邻"

4.3.2 缓存层级映射规则

缓存层级映射遵循以下规则:

  • L1/L2缓存:通常为每个CPU核心私有,不参与调度域映射。
  • L3缓存(LLC):同一簇内CPU共享,映射到MC层级。
  • 跨芯片缓存:同一NUMA节点内可能包含多个芯片,映射到DIE层级。
  • 跨节点缓存:不同NUMA节点之间无共享缓存,映射到NUMA层级。
注意:在AMD Zen/Zen2/Zen3架构中,CCX(Core Complex)内部的L3缓存是共享的,但跨CCX的L3缓存不共享。这导致MC层级可能进一步细分为CCX域,需要内核通过ACPI SRAT表或CPUID指令动态识别。

4.3.3 映射关系SVG图

NUMA节点与缓存层级映射关系 NUMA节点0 (距离=10) DIE 0 (L3共享) CCX 0 (L3共享) CPU0 CPU1 CPU2 CPU3 CCX 1 (L3共享) CPU4 CPU5 CPU6 CPU7 DIE 1 (L3共享) CCX 2 (L3共享) CPU8 CPU9 CPU10 CPU11 CCX 3 (L3共享) CPU12 CPU13 CPU14 CPU15 NUMA节点1 (距离=20) DIE 2 (L3共享) CCX 4 (L3共享) CPU16 CPU17 CPU18 CPU19 CCX 5 (L3共享) CPU20 CPU21 CPU22 CPU23 DIE 3 (L3共享) CCX 6 (L3共享) CPU24 CPU25 CPU26 CPU27 CCX 7 (L3共享) CPU28 CPU29 CPU30 CPU31 距离=20 图例:蓝色=NUMA节点,青色=DIE/芯片,紫色=CCX/簇,红色=CPU核心。虚线表示跨节点连接,距离值反映内存访问代价。

4.4 调度域初始化完整流程SVG

sched_init_domains 函数完整流程 sched_init_domains 1. 初始化拓扑层级数组 2. 注册默认拓扑层级 3. 遍历所有在线CPU build_sched_domains 子流程 分配域节点 设置CPU位图 创建调度组 设置域标志 链接父子关系 计算均衡参数 ← 上一章 📖 返回目录 下一章 →