numa信息传递
2026/7/24 12:45:49 网站建设 项目流程

先把结论说清楚:SRAT 和 SLIT 不是存在某个 QEMU 文件里,也不是存在 Guest 磁盘上,而是 QEMU 在启动时动态拼出来、放进「Guest 物理内存里的 ACPI 表区域」,再通过 RSDP→XSDT→SRAT/SLIT 的链式指针暴露给 Guest 内核;Linux 启动早期直接用 ACPI 子系统解析,用户态再看/sys/firmware/acpi/tables/下的镜像

下面分「QEMU 侧放哪」和「Linux 侧怎么拿」两层说。


一、QEMU 里 SRAT / SLIT 存在哪里(构建期 + 运行期)

1. 构建期:在GArray缓冲区里拼二进制

ACPI 表是 QEMU 运行时生成的,不是读文件。

  • 入口:hw/i386/acpi-build.cacpi_build()(ARM 在hw/arm/virt-acpi-build.c

  • 拼表函数:

    • SRAT:hw/acpi/aml-build.cbuild_srat()→ 调build_srat_memory()等追加子结构,最后build_header()封头,签名"SRAT"

    • SLIT:hw/acpi/aml-build.cbuild_slit()→ 先推AcpiTableHeader,再写节点数 + N×N 距离矩阵,封头签名"SLIT"

  • 这些字节先写在tables_blob(一个GArray<uint8_t>​ 里,此时还在 QEMU 进程的用户态内存,不是 Guest 内存。

数据来源就是前面读的那些结构:

  • SRAT 内容 ←MachineState::numa_state->nodes[]NodeInfo.present/has_cpu/node_memdev等)

  • SLIT 内容 ←NodeInfo.distance[],且必须numa_state->have_numa_distance == true才生成

2. 运行期:通过 fw_cfg + BIOSLinker 搬进 Guest 物理内存

acpi_build()拼完所有表后:

  1. tables_blob交给BIOSLinkerhw/acpi/bios-linker-loader.c

  2. Linker 在表尾计算每张表的物理偏移,把XSDT 里的 8 字节指针槽​ 填成 SRAT/SLIT 在 blob 中的偏移

  3. 整个 blob 通过fw_cfg 设备-fw_cfg)暴露给 SeaBIOS/EDK2/OVMF

  4. 固件(SeaBIOS/EDK2)把 blob 拷贝到 Guest 物理内存的某个预留区(一般靠近 0xFFFFFFF0 附近的 ACPI reclaim/Reserved 区),并把RSDP 地址​ 写进 EBDA 或0x000E0000~0x00100000的 BIOS 区域

  5. Guest 开机后,CPU 复位 → 固件交权给内核 → 内核从 RSDP 开始扫

所以最终存放位置是:

Guest 物理地址空间中的「ACPI 表保留内存区」(类型在 e820 里标ACPI ReclaimableACPI NVS,取决于表),由 RSDP(实模式可见地址)→ XSDT(64 位指针)→ SRAT/SLIT 三级串起来。

QEMU 自己构建完就不管了,表里的内容对 Guest 来说就是「硬件固件给的」。


二、Linux 通过什么接口访问

1. 内核启动早期:ACPI 子系统直接解析物理内存里的表

路径(x86 为例):

setup_arch() └─ initmem_init() └─ x86_numa_init() └─ x86_acpi_numa_init() └─ acpi_numa_init() /* drivers/acpi/numa/srat.c */ ├─ acpi_get_table(ACPI_SIG_SRAT, ...) 取 SRAT 基址 ├─ acpi_table_parse_srat(...) 拆 CPU/Memory Affinity ├─ acpi_get_table(ACPI_SIG_SLIT, ...) └─ acpi_parse_slit() 填 node_distance[]
  • 内核用acpi_get_table()从 RSDP/XSDT 链拿到 SRAT/SLIT 的物理地址,临时映射解析

  • 解析结果不直接留「表原文」,而是转成内核结构:

    • numa_meminfo:(起始地址,长度,node)

    • node_distance[i][j]:来自 SLIT

    • cpu_to_node()映射:来自 SRAT APIC Affinity

  • 之后/proc/sys/kernel/numa_distances、调度器、内存分配器用的都是这些内核结构,不是每次都重新读表

2. 用户态:两个接口

(a) 原始表镜像(调试/取证最常用)

/sys/firmware/acpi/tables/SRAT /sys/firmware/acpi/tables/SLIT /sys/firmware/acpi/tables/data/ /* 某些发行版拆了子项 */

这些是内核把解析前的 ACPI 表原样 expose 出来的struct acpi_table_header二进制,可以直接iasl -d SRAT反编译看。

(b) 解析后的 NUMA 视图(日常用)

/sys/devices/system/node/node0/cpulist /sys/devices/system/node/node0/meminfo /sys/devices/system/node/node0/distance /* 就是 SLIT 里那一行 */

numactl -H也是读这些 sysfs +syscalls出来的。

注意:用户态不能通过某个ioctl直接「再读一次 SRAT」,SRAT 只在 boot 阶段被内核读一次;用户态要看原表只能读/sys/firmware/acpi/tables/里的快照。


三、把前后知识串起来(对应 QEMU 结构)

QEMU 命令行 -numa node,memdev=,cpus= → NumaNodeOptions → NodeInfo[] -numa dist,src,dst,val → NumaDistOptions → NodeInfo.distance[] │ ▼ hw/acpi/aml-build.c build_srat() ← NodeInfo.present/has_cpu/node_memdev build_slit() ← NodeInfo.distance[] │ BIOSLinker + fw_cfg ▼ Guest 物理内存: RSDP → XSDT → SRAT / SLIT │ ▼ Linux drivers/acpi/numa/srat.c acpi_numa_init() → numa_meminfo + node_distance[] │ ┌────────────────────┴─────────────────────┐ ▼ ▼ 内核 NUMA 子系统 /sys/firmware/acpi/tables/{SRAT,SLIT}

一句话收口:

  • SRAT/SLIT 在 QEMU 里是aml-build.c拼出来的内存块,经 fw_cfg 搬进 Guest 物理内存的 ACPI 保留区;

  • Linux 启动期由acpi_numa_init()通过 RSDP/XSDT 链解析物理内存里的表,用户态再通过/sys/firmware/acpi/tables/看原始表、通过/sys/devices/system/node/看解析结果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询