Linux 内核早期启动阶段Early Boot的 CXL 内存初始化从 EFI 内存映射到 NUMA 与内存层级【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文基于 Linux 内核源码树中 Documentation/driver-api/cxl/linux/early-boot.rst 整理撰写。CXLCompute Express Link内存要最终出现在内核页分配器中必须经过早期启动阶段这第一道关卡内核在此阶段把 EFI/BIOS 固件描述的物理资源如 EFI 内存映射、ACPI 表转化为自身可消费的抽象NUMA 节点、内存 zone、内存层级。读完本文你将掌握早期启动阶段与 CXL 内存相关的 4 个关键配置选项、内存映射的创建逻辑、NUMA 节点保留规则、内存层级的初始化时机以及 CMA 与 CXL 内存配合时的限制。为什么需要单独研究早期启动阶段Linux 的配置过程被拆分为两个主要阶段早期启动Early-Boot和其余一切驱动探测、内存热插拔等运行时操作。早期启动阶段的特点是内核在此阶段设置不可变资源例如 NUMA 节点。这些资源一旦确定后续运行时无法再修改——NUMA 节点不能在运行时创建。因此BIOS/EFI 必须在这个阶段之前就提供足够的信息内核则通过读取 EFI 与 ACPI 信息来构建设备的逻辑表示。从实现角度看早期启动对应内核中所有带有__init修饰符的函数这些函数在启动完成后会被释放回收。系统在此阶段将 EFI/BIOS 创建的资源ACPI 表参见 Documentation/driver-api/cxl/platform/acpi.rst转换为内核可以消费的资源。对于 CXL 内存而言这一阶段的决策直接决定了后续CXL 内存是直接作为 SystemRAM 暴露给页分配器还是被软保留Soft Reserved交给驱动管理如果交给驱动是通过 DAX 设备暴露还是随后热插拔为 SystemRAM内存会被放入哪个 zoneZONE_NORMAL还是ZONE_MOVABLE相关的 NUMA 节点与内存层级memory tier如何建立。BIOS、构建与启动选项影响早期内存管理的 4 个开关在编译内核并启动系统之前有4 个预启动选项需要综合考虑它们共同决定了 Linux 在早期启动阶段如何管理内存。1.EFI_MEMORY_SPBIOS/EFI 选项EFI_MEMORY_SP是 UEFI 规范UEFI 2.8 引入定义的内存属性位由 BIOS/EFI 固件决定某段内存是SystemRAM还是Specific Purpose特定用途内存。若该位未置位EFI_MEMORY_SP0内存被视为普通系统内存若置位则表示该内存具有独特性能特征、且预期有应用特定用途Specific Purpose 内存会被推迟defer给驱动管理不会立即暴露为系统 RAM。在内核源码中该属性位被广泛用于识别需要特殊处理的内存描述符例如 drivers/firmware/efi/efi.c 中打印内存属性时会用SP标记EFI_MEMORY_SPdrivers/firmware/efi/efi-init.c 中early_init_dt_add_memory_arch()会跳过带EFI_MEMORY_SP且启用了软保留的描述符。2.CONFIG_EFI_SOFT_RESERVE内核构建选项这是 Linux 构建配置项决定内核是否支持 Specific Purpose 内存。其 Kconfig 定义位于 drivers/firmware/efi/Kconfigconfig EFI_SOFT_RESERVE bool Reserve EFI Specific Purpose Memory depends on EFI EFI_STUB ACPI_HMAT default ACPI_HMAT help On systems that have mixed performance classes of memory EFI may indicate specific purpose memory with an attribute (See EFI_MEMORY_SP in UEFI 2.8). A memory range tagged with this attribute may have unique performance characteristics compared to the systems general purpose System RAM pool. ...注意其依赖关系需要同时启用EFI、EFI_STUB和ACPI_HMAT。这也解释了为何 HMAT 相关能力内存性能数据与软保留机制在设计上紧密关联。在 x86 启动路径中软保留的实际落地发生在 EFI stub 阶段当efi_soft_reserve_enabled()为真且内存描述符带有EFI_MEMORY_SP时内存会被标记为E820_TYPE_SOFT_RESERVED而非E820_TYPE_RAM参见 drivers/firmware/efi/libstub/x86-stub.ccase EFI_BOOT_SERVICES_CODE: case EFI_BOOT_SERVICES_DATA: case EFI_CONVENTIONAL_MEMORY: if (efi_soft_reserve_enabled() (d-attribute EFI_MEMORY_SP)) e820_type E820_TYPE_SOFT_RESERVED; else e820_type E820_TYPE_RAM; break;同理drivers/firmware/efi/libstub/mem.c 在分配内存时也会跳过这类描述符确保软保留区域不会被随机分配代码占用。3.CONFIG_MHP_DEFAULT_ONLINE_TYPE内核构建选项该构建选项决定被软保留、随后转换为 DAX 设备dax设备的 Specific Purpose 内存默认应如何被管理——是保持 DAX 形态还是在线online为 SystemRAM以及在线到哪个 zoneZONE_NORMAL或ZONE_MOVABLE。对应内核中的解析逻辑位于 mm/memory_hotplug.c 的mhp_get_default_online_type()static int mhp_default_online_type -1; enum mmop mhp_get_default_online_type(void) { if (mhp_default_online_type 0) return mhp_default_online_type; if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_OFFLINE)) mhp_default_online_type MMOP_OFFLINE; else if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_AUTO)) mhp_default_online_type MMOP_ONLINE; else if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_KERNEL)) mhp_default_online_type MMOP_ONLINE_KERNEL; else if (IS_ENABLED(CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_MOVABLE)) mhp_default_online_type MMOP_ONLINE_MOVABLE; else mhp_default_online_type MMOP_OFFLINE; return mhp_default_online_type; }从源码结构可以看出其取值语义包括_OFFLINE默认保持离线即保持 DAX 形态等待显式操作_ONLINE_AUTO默认自动上线由内核选择 zone_ONLINE_KERNEL默认上线到ZONE_NORMAL内核 zone_ONLINE_MOVABLE默认上线到ZONE_MOVABLE可移动 zone。需要注意默认在线行为存在优先级CONFIG_MHP_DEFAULT_ONLINE_TYPE构建选项 memhp_default_state启动参数 /sys/devices/system/memory/auto_online_blocks运行时值这属于后续热插拔阶段的主题详见 Documentation/driver-api/cxl/linux/memory-hotplug.rst。4.nosoftreserve内核启动参数nosoftreserve是内核命令行启动参数用于在运行时关闭 Soft Reserve 支持其效果与关闭CONFIG_EFI_SOFT_RESERVE类似。解析代码位于 drivers/firmware/efi/efi.cif (parse_option_str(str, nosoftreserve)) set_bit(EFI_MEM_NO_SOFT_RESERVE, efi.flags);而在 EFI stub 侧drivers/firmware/efi/libstub/efi-stub-helper.c该参数仅在编译期启用了CONFIG_EFI_SOFT_RESERVE时才会真正生效efi_nosoftreserve IS_ENABLED(CONFIG_EFI_SOFT_RESERVE) parse_option_str(val, nosoftreserve);也就是说若构建时未启用CONFIG_EFI_SOFT_RESERVE则软保留机制本身就不存在nosoftreserve参数没有实际意义。四个选项的取舍关系小结选项层级作用关闭后果EFI_MEMORY_SPBIOS/EFI标记内存为 Specific Purpose固件不标记内核无法识别CONFIG_EFI_SOFT_RESERVE内核构建使内核支持软保留所有 CXL 内存默认落入 SystemRAMCONFIG_MHP_DEFAULT_ONLINE_TYPE内核构建决定 DAX 内存默认在线行为与 zone需结合热插拔阶段处理nosoftreserve启动参数运行时禁用软保留等价于构建时关闭软保留内存映射创建SOFT_RESERVED 与 zone 归属在内核解析 EFI 内存映射的过程中如果 Specific Purpose 内存被支持且被检测到该区域会被单独放置为SOFT_RESERVED在 x86 上体现为 e820 类型E820_TYPE_SOFT_RESERVED。随后存在两条截然不同的路径路径 ACXL 内存直接作为 SystemRAM如果满足以下任一条件Linux 会将 CXL 设备内存区域默认视为 SystemRAMEFI_MEMORY_SP0固件未标记CONFIG_EFI_SOFT_RESERVEn内核不支持软保留nosoftreservey运行时禁用软保留。此时内存会暴露给内核页分配器并放入ZONE_NORMAL可用于绝大多数分配包括struct page和页表。路径 B软保留后由配置决定去向如果 Specific Purpose 已设置且被支持则由CONFIG_MHP_DEFAULT_ONLINE_TYPE_*决定_OFFLINE默认不上线保持 DAX 形态_ONLINE_*默认上线并由_NORMAL或_MOVABLE决定目标 zone。ZONE_MOVABLE 的性能代价文档特别强调了一个关键的性能注意点如果内存被放入ZONE_MOVABLE它就无法用于大多数内核分配例如struct page或页表。对于 CXL 内存容量较大的系统这会显著影响性能——因为struct page和页表等内核结构必须从其他节点/zone 分配。从源码结构可以推断ZONE_MOVABLE的设计初衷是保留内存块的热插拔能力使得整个区域在之后可以被热拔出见 Documentation/driver-api/cxl/linux/memory-hotplug.rst因此它必须限制不可迁移的内核分配而ZONE_NORMAL中的容量应被视为永久附着于页分配器。NUMA 节点保留PXM、SRAT、CEDT 与伪 PXMPXM 与 NUMA 节点的映射关系Linux 依据 SRATStatic Resource Affinity Table 中定义的临近域PXMProximity Domain在acpi_numa_init()中创建 NUMA 节点。典型情况下PXM与 NUMA 节点 ID 之间存在1:1的对应关系。但需要澄清的是PXM 与 NUMA 节点大致等价并非严格保证 1:1 映射——例如可能出现 Proximity Domain 4 映射到 NUMA Node 3 的场景。在内核源码 drivers/acpi/numa/srat.c 中pxm_to_node_map[]与node_to_pxm_map[]两张表维护了 PXM 与逻辑节点 ID 之间的双向转换acpi_map_pxm_to_node()负责在首次见到某 PXM 时分配节点号可见两者是多对一的可能映射。SRAT 是 ACPI 中唯一定义 Proximity Domain 的途径。Linux 选择最多将其与 NUMA 节点 1:1 映射。而 CEDTCXL Early Discovery Table 补充描述了 SPASystem Physical Address范围Linux 可能将这些范围映射到一个或多个 NUMA 节点。CFMWS 与伪 PXM关键场景如果 CFMWSCXL Fixed Memory Window Structure中存在 CXL 范围但 SRAT 中没有对应条目那么截至 v6.15内核会创建一个**伪 PXMfake PXM**来关联该范围。这是为了在缺少 SRAT 描述的情况下仍能为 CXL 窗口建立 NUMA 拓扑。文档同时指出未来趋势由于伪 PXM 在临近域关联上存在歧义未来 Linux 可能会拒绝那些未被 SRAT 描述的 CFMWS 窗口。这意味着平台固件如果做了任何 CXL 织物decoder编程就应当为对应内存提供 SRAT 条目SRAT Memory Affinity 子表否则后续内核版本可能直接拒绝该窗口。NUMA 节点不能在运行时创建这是早期启动阶段最重要的约束之一NUMA 节点创建无法在运行时完成。所有可能的 NUMA 节点都在__init时间内识别更具体地说是在mm_init期间。因此CEDT 和 SRAT 必须包含足够的PXM数据内核才能据此识别 NUMA 节点及其关联的内存区域一旦错过这个窗口内存将无法获得正确的节点归属。相关实现位于linux/drivers/acpi/numa/srat.c。更多平台级配置示例参见 Documentation/driver-api/cxl/platform/example-configs.rst。内存层级创建默认 DRAM tier 与 access_coordinates什么是内存层级内存层级Memory Tier是按性能特征分组的 NUMA 节点集合。早期启动期间内核初始化一个默认内存层级包含所有标记为N_MEMORY的节点节点只有在拥有**在线内存online memory**时才会被标记为N_MEMORY。两个初始化入口的时机差异文档明确指出存在两个初始化调用点源码 mm/memory-tiers.c 中也有对应实现memory_tier_init()以subsys_initcall注册在启动阶段为所有默认在线内存的节点建立默认层级memory_tier_late_init()以late_initcall注册为驱动配置期间例如 CXL 驱动探测阶段建立的节点补充初始化/* * This is invoked via late_initcall() to initialize memory tiers for * memory nodes, both with and without CPUs. After the initialization of * firmware and devices, adistance algorithms are expected to be provided. */ static int __init memory_tier_late_init(void) { ... establish_demotion_targets(); ... } late_initcall(memory_tier_late_init);两者配合实现了文档中描述的机制启动早期先处理默认在线节点晚些时候再处理由驱动如 CXL 驱动配置起来的节点。如何查看层级归属层级成员关系可以通过 sysfs 检查/sys/devices/virtual/memory_tiering/memory_tierN/nodelist 0-1示例输出0-1表示该层级包含 node 0 和 node 1。性能差异与 HMAT/CDAT如果被归组的节点之间存在明显的性能差异应检查 CXL 节点的 HMAT 与 CDAT 信息。所有节点默认归入 DRAM 层级除非 HMAT/CDAT 信息通过access_coordinates上报给 memory_tier 组件。access_coordinates机制的具体计算如何把 SRAT/HMAT 的 Generic Port 性能、CDAT 的 DSMAS/DSLBIS/SSLBIS 数据汇总为访问坐标详见 Documentation/driver-api/cxl/linux/access-coordinates.rst——其要点是固件无法为热插拔的 CXL 设备预先提供性能数据因此内核通过 SRAT Generic Port Affinity 子表 HMAT 设备 CDAT 逐段计算端到端延迟求和与带宽逐段取 min最终形成每个 CXL 区域的访问坐标。连续内存分配CMA早期启动的关键限制连续内存分配器CMA可以在早期启动期间在 NUMA 节点上预留连续内存区域但有一个关键限制CMA 无法在早期启动期间尚未在线的 NUMA 节点上预留内存。文档给出了伪代码形态的示意void __init hugetlb_cma_reserve(void) { if (!node_online(nid)) /* do not allow reservations */ }对 CXL 场景的直接影响这一限制对 CXL 部署有两个直接推论如果用户打算将 CXL 内存的管理推迟给驱动即软保留后交给 CXL/DAX 驱动节点在早期启动时处于离线状态那么CMA 无法用于保证巨页huge page分配。这也解释了 Documentation/driver-api/cxl/allocation/hugepages.rst 中的结论被推迟给驱动管理的 CXL 内存其容量无法被 CMA 分配因为承载容量的 NUMA 节点在__init时间——即 CMA 切出连续容量时——处于离线状态。如果在早期启动期间将 CXL 内存作为 SystemRAM 上线到ZONE_NORMAL那么承载节点会在 CMA 执行时处于在线状态此时可以使用内核命令行参数cma_pernuma或numa_cma为每个节点进行 CMA 预留。巨页可用性的额外提示从 hugepages 文档可以进一步延伸不同巨页大小对 CXL 内存的可用性不同——2MB 巨页无论配置时机或内存 zone所有 CXL 容量均可用于 2MB 巨页1GB 巨页只有上线在ZONE_NORMAL的 CXL 容量可用于 1GB Gigantic Page上线在ZONE_MOVABLE的容量不可用。这与前文ZONE_MOVABLE不允许struct page等内核分配的语义一脉相承1GB 巨页需要对应的页表/元数据结构而ZONE_MOVABLE不提供这类容量。串联整体流程从 EFI 到页分配器综合上述各环节CXL 内存从固件到可用的完整早期启动路径可以概括为固件侧BIOS/EFI 在 EFI 内存映射中为 CXL 内存设置EFI_MEMORY_SP属性可选并在 CEDT 中通过 CHBS/CFMWS 描述 CXL 主机桥与固定内存窗口在 SRAT 中提供 Memory Affinity / Generic Port Affinity 条目EFI stub 阶段x86若启用了软保留带EFI_MEMORY_SP的内存被标记为E820_TYPE_SOFT_RESERVED否则为普通 RAM早期启动阶段内核解析 EFI 内存映射与 ACPI 表acpi_numa_init()依据 SRAT/CEDT 建立 NUMA 节点必要时创建伪 PXMmm_init期间固定所有节点拓扑内存映射决策软保留的内存要么保持SOFT_RESERVED等待驱动接管DAX要么依据CONFIG_MHP_DEFAULT_ONLINE_TYPE上线到ZONE_NORMAL/ZONE_MOVABLE非软保留的 CXL 内存直接落入ZONE_NORMAL的 SystemRAM内存层级建立memory_tier_initsubsys_initcall处理默认在线节点memory_tier_late_initlate_initcall处理驱动配置节点所有节点默认归入 DRAM 层级性能差异由 HMAT/CDAT 的access_coordinates修正CMA 预留只有早期启动期间已在线即直接上线为 SystemRAM的节点才能使用cma_pernuma/numa_cma预留连续内存用于保证巨页分配。其中第 4 步之后DAX 驱动将驱动管理的内存区域通过 memory-hotplug 组件呈现给页分配器属于热插拔阶段的工作详见 Documentation/driver-api/cxl/linux/memory-hotplug.rst早期启动阶段为其打下的基础——节点拓扑、内存映射标记、默认在线策略——决定了热插拔阶段的行为边界。总结与部署建议早期启动阶段是 CXL 内存管理的地基NUMA 节点一经建立便不可更改内存 zone 归属直接影响内核分配能力与性能软保留机制决定了内存由内核还是驱动管理。部署时的核心决策点可归纳为想让 CXL 内存直接可被内核使用保持EFI_MEMORY_SP0或禁用软保留CONFIG_EFI_SOFT_RESERVEn/nosoftreserve内存将作为 SystemRAM 落入ZONE_NORMAL想用驱动管理 热插拔灵活性启用软保留配合CONFIG_MHP_DEFAULT_ONLINE_TYPE或memhp_default_state控制默认上线行为与 zone同时意识到ZONE_MOVABLE会牺牲内核分配能力、CMA 也无法为离线节点预留巨页想保证大页分配在早期启动阶段将 CXL 内存上线到ZONE_NORMAL并使用cma_pernuma/numa_cma按节点预留 CMA 区域平台固件要求只要 BIOS 做了 CXL decoder 编程就应确保 SRAT 中有对应的 Memory Affinity / Generic Port Affinity 条目避免未来内核版本因 CFMWS 缺乏 SRAT 描述而拒绝该窗口。相关深入资料内存热插拔细节见 Documentation/driver-api/cxl/linux/memory-hotplug.rst访问坐标计算见 Documentation/driver-api/cxl/linux/access-coordinates.rst平台 ACPI 表结构见 Documentation/driver-api/cxl/platform/acpi.rst。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考