2. CPU域(Scheduling Domain)基础:CPU拓扑结构、sched_domain与sched_group的层次化组织
CPU调度域(Scheduling Domain)是Linux内核实现负载均衡与跨CPU迁移的核心抽象层。它通过将物理CPU拓扑结构映射为层次化的sched_domain与sched_group数据结构,使调度器能够感知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; // 最大空闲负载均衡开销
...
};
域层次结构的构建遵循自底向上原则:
- 最底层(level=0):每个物理核心内的超线程组成一个域,共享L1/L2缓存。
- 中间层:多个核心共享L3缓存的Cluster组成一个域。
- 顶层:跨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拓扑到调度域/调度组的完整映射关系,以及负载均衡的逐层扩散路径:
2.5 关键数据结构关系总结
下表总结了sched_domain与sched_group在负载均衡中的角色差异:
| 维度 | sched_domain | sched_group |
|---|---|---|
| 职责 | 定义负载均衡的边界和策略 | 提供可迁移的CPU集合 |
| 组织方式 | 树状层次(parent/child) | 环形链表(next指针) |
| 核心属性 | flags(策略标志)、imbalance_pct(阈值) | group_capacity(容量)、group_weight(权重) |
| 负载均衡触发 | 决定是否在该层进行均衡 | 提供组间负载对比数据 |
| 迁移粒度 | 跨域迁移(代价较高) | 组内迁移(代价较低) |
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)的域层次差异很大,盲目套用参数可能导致性能下降。