3. Linux NUMA 子系统初始化:内核启动时对 NUMA 拓扑的探测
NUMA(Non-Uniform Memory Access)架构的性能优势高度依赖于内核在启动阶段对硬件拓扑的精确探测与建模。本章深入分析 Linux 内核在引导过程中如何解析 ACPI 表、构建节点与 CPU/内存的绑定关系,并最终形成供调度器和内存管理器使用的 NUMA 拓扑视图。
3.1 NUMA 拓扑探测的总体流程
内核在启动早期(x86 架构下通常在 start_kernel → setup_arch 路径中)开始 NUMA 初始化。整体流程分为四个阶段:
- 固件接口准备:解析 ACPI 根表,定位 SRAT 和 SLIT 表。
- SRAT 解析:提取处理器和内存的亲和性信息,建立节点与物理资源的映射。
- SLIT 解析:读取节点间的相对距离,构建延迟矩阵。
- 拓扑绑定:将 CPU 核心、内存区域、I/O 设备(通过 ACPI DSDT)绑定到对应 NUMA 节点。
关键路径文件:
arch/x86/mm/numa.c、arch/x86/mm/srat.c、drivers/acpi/numa.c。
3.2 ACPI SRAT 表解析:处理器与内存的亲和性
SRAT(System Resource Affinity Table)是 ACPI 规范中定义的核心拓扑表。内核通过 acpi_numa_init() 函数遍历 SRAT 中的条目,主要处理两类记录:
| 记录类型 | Type 值 | 关键字段 | 内核处理函数 |
|---|---|---|---|
| 处理器亲和性 | 0 | APIC ID、Proximity Domain、使能标志 | acpi_numa_processor_affinity_init() |
| 内存亲和性 | 1 | 基地址、长度、Proximity Domain、使能标志 | acpi_numa_memory_affinity_init() |
| 处理器本地 x2APIC | 2 | x2APIC ID、Proximity Domain | acpi_numa_x2apic_affinity_init() |
解析要点:内核使用
proximity_domain 字段映射到逻辑 NUMA 节点号(pxm_to_node())。如果固件报告的节点数超过 MAX_NUMNODES(通常 1024),多余节点会被忽略并打印警告。
SRAT 解析完成后,内核构建两个核心位图:
node_possible_map:所有可能存在的节点。node_online_map:当前已激活(有 CPU 或内存)的节点。
3.3 ACPI SLIT 表解析:节点间延迟矩阵
SLIT(System Locality Information Table)定义了节点之间的“相对距离”。距离值越小,表示内存访问延迟越低。内核将其存储在 numa_slit[] 二维数组中,供内存分配策略(如 numa_migrate_preferred())使用。
| 距离值范围 | 典型含义 | 示例(4 节点) |
|---|---|---|
| 10 | 本地节点(同一节点内) | Node0→Node0 = 10 |
| 20 | 同一封装内跨 CCD/CCX | Node0→Node1 = 20 |
| 30~40 | 跨封装(不同物理 CPU) | Node0→Node2 = 30 |
| ≥ 40 | 跨系统(多路服务器) | Node0→Node3 = 40 |
性能陷阱:某些固件可能报告不准确的 SLIT 值(例如所有距离设为 10),导致内核认为所有节点访问延迟相同,从而禁用 NUMA 感知的优化策略。建议使用
numactl --hardware 验证实际距离矩阵。
3.4 节点与 CPU/内存的绑定关系建立
SRAT/SLIT 解析完成后,内核进入拓扑绑定阶段。核心数据结构包括:
| 数据结构 | 类型 | 作用 | 初始化位置 |
|---|---|---|---|
node_to_cpumask_map[] |
cpumask_t * |
每个节点包含的 CPU 位图 | numa_set_node() + set_cpu_numa_node() |
node_to_memmap[] |
struct pglist_data * |
每个节点的内存管理结构(pgdat) | free_area_init_node() |
node_distance() |
宏/函数 | 查询两个节点间的 SLIT 距离 | numa_slit[] 数组 |
cpu_to_node_map[] |
int[] |
每个 CPU 所属的节点号 | numa_set_node() |
绑定顺序:先绑定 CPU(通过 APIC ID → Proximity Domain → Node ID),再绑定内存(通过物理地址范围 → Node ID)。I/O 设备的 NUMA 绑定在后续 ACPI DSDT 解析中完成,通过
acpi_bind_one() 关联到最近节点。
3.5 内核启动日志与调试
NUMA 初始化过程中的关键信息会通过 pr_info() 输出到内核日志。使用 dmesg | grep -i numa 可以查看:
[ 0.000000] NUMA: Node 0 [mem 0x00000000-0x7fffffff] + [mem 0x100000000-0x27fffffff]
[ 0.000000] NUMA: Node 1 [mem 0x280000000-0x4ffffffff]
[ 0.000000] NUMA: Node 0 CPUs: 0-15, 32-47
[ 0.000000] NUMA: Node 1 CPUs: 16-31, 48-63
[ 0.000000] SLIT: Node 0 to Node 0 = 10, Node 0 to Node 1 = 20
[ 0.000000] SLIT: Node 1 to Node 0 = 20, Node 1 to Node 1 = 10
调试技巧:如果发现 CPU 或内存未正确绑定到节点,可以检查:
1. 固件 SRAT 表是否完整(使用
2. 内核是否启用了
3. 启动参数是否意外覆盖了拓扑(如
1. 固件 SRAT 表是否完整(使用
acpidump -t SRAT 导出)。2. 内核是否启用了
CONFIG_NUMA 和 CONFIG_ACPI_NUMA。3. 启动参数是否意外覆盖了拓扑(如
numa=fake=2)。
3.6 拓扑绑定完成后的内核视图
初始化完成后,内核通过 numa_node_id() 和 cpu_to_node() 等接口提供拓扑查询。下图展示了最终形成的 NUMA 拓扑结构:
3.7 常见问题与排查方法
| 问题现象 | 可能原因 | 排查命令/方法 |
|---|---|---|
| 所有 CPU 显示在 Node 0 | SRAT 表缺失或固件未正确报告 Proximity Domain | dmesg | grep SRAT;acpidump -t SRAT |
| 内存分配跨节点延迟异常 | SLIT 距离值不准确或所有距离相同 | numactl --hardware 查看距离矩阵 |
| I/O 中断全部路由到 Node 0 | I/O 设备未绑定到正确节点(ACPI DSDT 缺失) | cat /sys/class/net/eth0/device/numa_node |
| 内核启动时 NUMA 初始化失败 | ACPI 表损坏或内核配置缺少 CONFIG_ACPI_NUMA |
dmesg | grep -i "ACPI.*error" |
生产环境建议:在部署 NUMA 敏感型应用(如数据库、HPC 计算)前,务必在目标硬件上执行
numactl --hardware 和 lstopo 验证拓扑完整性。如果发现节点绑定异常,优先检查固件版本和 BIOS 设置中的 NUMA 相关选项(如“NUMA Nodes per Socket”)。