2. CPU域(Scheduling Domain)基础:CPU拓扑结构、sched_domain与sched_group的层次化组织

CPU调度域(Scheduling Domain)是Linux内核实现负载均衡与跨CPU迁移的核心抽象层。它通过将物理CPU拓扑结构映射为层次化的sched_domain与sched_group数据结构,使调度器能够感知CPU之间的缓存共享、内存访问延迟和物理距离,从而做出最优的迁移决策。

核心概念:调度域将CPU组织成树状层次,每一层代表一个共享资源边界(如L1缓存、L2缓存、NUMA节点)。负载均衡从最底层(最紧密耦合的CPU)开始,逐层向上扩散,确保迁移代价最小化。

2.1 CPU拓扑结构:从物理布局到内核视图

现代多核/多路服务器CPU的物理拓扑通常包含以下层级:

拓扑层级 硬件单元 共享资源 典型粒度
Thread 超线程(SMT) L1/L2缓存(部分) 1个逻辑CPU
Core 物理核心 L1/L2缓存(完整) 2~4个线程
Cluster/LLC 最后一级缓存(L3) L3缓存 4~16个核心
NUMA Node 内存控制器+物理插槽 本地内存 1~4个Cluster
System 整机 跨NUMA互联 2~8个NUMA节点

内核通过arch_init_sched_domain_topology()函数读取ACPI/SRAT表或设备树,构建出上述拓扑的CPU位图表示。每个层级对应一个sched_domain_topology_level结构体,其中包含该层级的CPU掩码和跨度信息。

调试技巧:通过 /proc/schedstat 或 /sys/kernel/debug/sched/domains 可以查看当前系统的调度域层次结构。使用 lscpu -e 可快速获取CPU拓扑的物理视图。

2.2 sched_domain:调度域的层次化组织

sched_domain是调度域的核心数据结构,每个CPU都拥有一个指向其所属域层次结构的指针。域之间通过parent和child指针形成树状关系。

// 内核源码:kernel/sched/sched.h (简化结构)
struct sched_domain {
    struct sched_domain *parent;    // 父域(更高层次)
    struct sched_domain *child;     // 子域(更低层次)
    struct sched_group *groups;     // 该域内的调度组链表
    unsigned long span[NR_CPUS];    // 该域覆盖的CPU位图
    int level;                      // 域层级(0=最底层)
    unsigned int flags;             // 域特性标志
    unsigned int imbalance_pct;     // 负载不均衡百分比阈值
    unsigned long max_newidle_lb_cost; // 最大空闲负载均衡开销
    ...
};

域层次结构的构建遵循自底向上原则:

  1. 最底层(level=0):每个物理核心内的超线程组成一个域,共享L1/L2缓存。
  2. 中间层:多个核心共享L3缓存的Cluster组成一个域。
  3. 顶层:跨NUMA节点的所有CPU组成系统域。

每个域都包含一组标志位(flags),用于控制该层级的负载均衡行为:

标志位 含义 典型用途
SD_BALANCE_NEWIDLE 允许在CPU空闲时进行负载均衡 所有域默认开启
SD_BALANCE_EXEC 允许在exec()时进行负载均衡 跨核心域
SD_BALANCE_FORK 允许在fork()时进行负载均衡 跨NUMA域
SD_SHARE_CPUCAPACITY 域内CPU共享计算容量(SMT) 超线程域
SD_SHARE_PKG_RESOURCES 域内CPU共享封装资源(L3缓存) Cluster域
SD_NUMA 域跨越NUMA节点 系统域
性能陷阱:如果某个域的 imbalance_pct 设置过小(如105),会导致调度器频繁尝试跨域迁移,增加缓存失效和NUMA远程访问开销。对于延迟敏感型应用,建议适当增大该值(如120~130)。

2.3 sched_group:负载均衡的基本单元

每个sched_domain内部被划分为多个sched_group,组是负载均衡操作中实际进行任务迁移的粒度。调度器比较同一域内不同组之间的负载差异,决定是否将任务从一个组迁移到另一个组。

// 内核源码:kernel/sched/sched.h (简化结构)
struct sched_group {
    struct sched_group *next;       // 组链表中的下一个组
    unsigned long group_capacity;   // 组的计算容量
    unsigned long group_weight;     // 组内CPU数量
    unsigned long cpumask[NR_CPUS]; // 组覆盖的CPU位图
    unsigned int group_id;          // 组标识符
    ...
};

组的划分规则与域层级密切相关:

  • 在SMT域:每个物理核心内的超线程集合为一个组。
  • 在Core域:每个物理核心为一个组。
  • 在Cluster域:每个L3缓存共享域为一个组。
  • 在NUMA域:每个NUMA节点为一个组。

组之间通过next指针形成环形链表,调度器遍历该链表收集各组的负载统计信息。

2.4 层次化组织与负载均衡流程

下图展示了从物理CPU拓扑到调度域/调度组的完整映射关系,以及负载均衡的逐层扩散路径:

CPU拓扑与调度域层次化组织 CPU拓扑 → sched_domain → sched_group 层次化组织 物理CPU拓扑 Core 0 (L1/L2) Thread 0 Thread 1 Core 1 (L1/L2) Thread 2 Thread 3 Core 2 (L1/L2) Thread 4 Thread 5 Core 3 (L1/L2) Thread 6 Thread 7 L3 Cache Shared Domain (Cluster 0: Core 0-3) sched_domain 层次 SMT Domain (level 0) flags: SD_SHARE_CPUCAPACITY SMT Domain (level 0) flags: SD_SHARE_CPUCAPACITY SMT Domain (level 0) flags: SD_SHARE_CPUCAPACITY SMT Domain (level 0) flags: SD_SHARE_CPUCAPACITY Core Domain (level 1) flags: SD_BALANCE_NEWIDLE Core Domain (level 1) flags: SD_BALANCE_NEWIDLE Cluster Domain (level 2) flags: SD_SHARE_PKG_RESOURCES sched_group 组织(环形链表) Group 0 (Core 0) Group 1 (Core 1) Group 2 (Core 2) Group 3 (Core 3) 负载均衡路径:Thread组内均衡 → Core组内均衡 → Cluster组内均衡 → NUMA节点间均衡(逐层扩散)

2.5 关键数据结构关系总结

下表总结了sched_domain与sched_group在负载均衡中的角色差异:

维度 sched_domain sched_group
职责 定义负载均衡的边界和策略 提供可迁移的CPU集合
组织方式 树状层次(parent/child) 环形链表(next指针)
核心属性 flags(策略标志)、imbalance_pct(阈值) group_capacity(容量)、group_weight(权重)
负载均衡触发 决定是否在该层进行均衡 提供组间负载对比数据
迁移粒度 跨域迁移(代价较高) 组内迁移(代价较低)
关键洞察:调度器总是优先在最底层域(SMT域)内进行负载均衡,因为组间迁移代价最小(共享L1/L2缓存)。只有当底层域无法解决负载不均衡时,才会逐层向上扩散到Core域、Cluster域,最后到NUMA域。这种设计确保了迁移代价最小化与负载均衡效果之间的最佳平衡。

2.6 实战:查看当前系统的调度域层次

通过以下命令可以快速获取当前系统的调度域拓扑信息:

# 查看CPU拓扑结构
lscpu -e

# 查看调度域层次(需要root权限)
cat /proc/schedstat | grep domain

# 更详细的调试接口(内核需开启CONFIG_SCHED_DEBUG)
cat /sys/kernel/debug/sched/domains/cpu0/domain*/name
cat /sys/kernel/debug/sched/domains/cpu0/domain*/flags
cat /sys/kernel/debug/sched/domains/cpu0/domain*/imbalance_pct

输出示例(4核8线程系统):

CPU NODE SOCKET CORE L1d:L1i:L2 L3
0   0    0      0    0:0:0:0     0
1   0    0      0    0:0:0:0     0
2   0    0      1    1:1:1:1     0
3   0    0      1    1:1:1:1     0
4   0    0      2    2:2:2:2     0
5   0    0      2    2:2:2:2     0
6   0    0      3    3:3:3:3     0
7   0    0      3    3:3:3:3     0

从输出可以看出:CPU 0和1共享同一个物理核心(Core 0),属于同一个SMT域;CPU 0-3共享L3缓存(Cluster 0),属于同一个Cluster域。

调试建议:在调整调度域参数(如 imbalance_pct、max_newidle_lb_cost)之前,务必先通过上述命令确认当前系统的域层次结构。不同硬件拓扑(如AMD Zen vs Intel Xeon)的域层次差异很大,盲目套用参数可能导致性能下降。