register_page_bootmem_info是 Linux 内核启动阶段的一个初始化函数,用于向 bootmem 分配器注册每个 NUMA 节点的元数据信息。它位于mm/memory_hotplug.c或arch/x86/mm/init_64.c中,是连接 SPARSEMEM 内存模型与 bootmem 分配器的关键环节。
核心作用:注册节点元数据
在 SPARSEMEM 模型下,每个 NUMA 节点需要维护一些启动阶段专用的元数据,例如:
usemap:用于跟踪 memory section 中哪些页帧已被使用。pageblock_flags:用于记录 pageblock 的可迁移性类型(用于内存碎片管理)。
这些元数据在启动早期由 bootmem 分配器分配,但它们的struct page描述符需要被注册到 bootmem 的引用计数系统中。register_page_bootmem_info正是完成这项工作的入口。
代码逻辑解析
static void __init register_page_bootmem_info(void) { #if defined(CONFIG_NUMA) || defined(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) int i; for_each_online_node(i) register_page_bootmem_info_node(NODE_DATA(i)); #endif }条件编译:只有在以下两种配置之一启用时,这个函数才有实际工作:
CONFIG_NUMA:NUMA 系统需要为每个节点管理独立的元数据。CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP:大页优化 vmemmap 的场景下,也需要管理额外的元数据。
遍历在线节点:for_each_online_node(i)遍历系统中所有处于在线状态的 NUMA 节点,对每个节点调用register_page_bootmem_info_node(NODE_DATA(i))。
register_page_bootmem_info_node的工作
这个辅助函数会针对单个节点,注册以下元数据的struct page:
node->node_page:节点自身的struct page。pgdat->node_zones[].usemap:每个 zone 的 usemap 页面。pgdat->node_zones[].pageblock_flags:pageblock 标志页面。
对于每一个这样的页面,它会调用get_page_bootmem()来递增该页的引用计数(通过 bootmem 的引用计数机制),确保这些元数据在 bootmem 被释放时不会被错误地回收。
设计意图
这个函数解决了一个生命周期管理问题:SPARSEMEM 的元数据在启动早期由 bootmem 分配,但它们的生命周期需要与struct page数组(vmemmap)保持一致。通过将它们的struct page注册到 bootmem 的引用计数系统中,内核确保了在 bootmem 释放时,这些元数据不会被一并释放,从而保证了内存管理子系统的稳定性。
这个函数是启动流程中一个“不起眼但关键”的环节——没有它,NUMA 系统或大页优化场景下的内存管理元数据可能会在 bootmem 退出时被错误回收,导致系统崩溃。
bootmem 分配器
是 Linux 内核在启动早期、伙伴系统(Buddy System)尚未就绪时使用的临时内存分配器。它的核心任务是为内核初始化过程提供一套可用的物理内存分配机制,直到mem_init()被调用,其管理的内存被移交给伙伴系统后,它便“功成身退”。
核心原理:位图与最先适配
bootmem 的实现相对简单直接,它基于First Fit(最先适配)算法,并使用一个位图(bitmap)来记录物理页的分配状态。
内存记录位图:系统启动时,bootmem 会为管理的物理内存区域分配一个位图。位图中的每一位对应一个物理页。
1表示该页已被占用,0表示空闲。最先适配算法:当内核申请内存时,bootmem 会从头扫描位图,找到第一块足够大的连续空闲页块进行分配。这种算法实现简单,但效率不高,且容易在物理内存中留下“空洞”。
核心数据结构:bootmem_data
bootmem 为每个 NUMA 节点维护一个bootmem_data结构体,用于记录该节点的内存管理信息。
| 字段 | 作用 |
|---|---|
node_boot_start | 该节点管理的物理内存起始地址 |
node_low_pfn | 该节点管理的物理内存结束页帧号 |
node_bootmem_map | 指向内存记录位图的指针 |
last_pos/last_offset | 记录上一次分配的位置,用于加速后续的小块内存分配(支持小于一页的分配) |
主要接口与工作流程
bootmem 提供了一系列以alloc_bootmem和free_bootmem开头的接口,用于内存的分配与释放。
初始化:在
setup_arch()阶段,架构相关代码会调用init_bootmem(),将可用物理内存注册到位图中。预留内存:通过
reserve_bootmem()将内核镜像、initrd、设备树等已占用内存区域标记为“已使用”。分配与释放:内核在初始化期间通过
alloc_bootmem()申请内存。值得注意的是,bootmem 分配的内存默认会被清零,且如果分配失败会触发 panic,这在早期启动阶段是合理的策略。交接给伙伴系统:当内核完成基础初始化,调用
mem_init()时,free_all_bootmem()会遍历位图,将所有标记为“空闲”的页释放给伙伴系统。此后,bootmem 便不再管理这些内存。
历史与现状:被 memblock 取代
bootmem 曾是 Linux 内核多年的标准引导分配器,但随着系统复杂度增加,其局限性(如对非连续内存支持不佳)逐渐暴露。
memblock作为更现代、更简洁的引导内存管理器被引入,并最终取代了 bootmem。从 Linux 5.x 系列开始,bootmem 已被完全移除。为了保持兼容性,内核曾提供mm/nobootmem.c作为包装层,让旧的 bootmem API 调用转向 memblock,但如今主流架构均已直接使用 memblock。