
alloc_low_pages是 x86 架构 Linux 内核启动早期用于分配物理上连续的内存页以构建内核页表的核心函数。它的实现巧妙地解决了一个“先有页表还是先有内存”的难题建立内存映射需要分配页表内存而分配页表内存又依赖已建立好的内存映射。核心工作原理一个两阶段的分配策略alloc_low_pages的内部逻辑分为两个清晰的阶段分别对应着不同的内存来源来适应启动流程的不同阶段第一阶段从 BRK 区域的pgt_buf中分配早期在init_mem_mapping()建立直接映射的初期系统还未准备好使用更通用的内存分配器memblock。alloc_low_pages会优先从一个名为pgt_buf的内存池中分配页。pgt_buf这是内核映像 BSS 段中预留的一块内存区域由RESERVE_BRK机制定义。它的大小是固定的为早期映射如 ISA 区域和第一个 2MB 大页映射提供了空间-2。实现方式函数会维护pgt_buf_end指针从pgt_buf_start开始按需向后“推移”这个指针来分配页-5。这保证了最早期代码能稳定、快速地获得内存。第二阶段回退到memblock分配器后期当pgt_buf中的页被耗尽后alloc_low_pages会自动切换到使用memblock分配器来获取内存页。实现方式此时init_mem_mapping()已经建立起了部分直接映射使得memblock可以正常工作。alloc_low_pages会调用memblock_find_in_range()在已映射的物理内存范围内寻找空闲页并使用memblock_reserve()将它们标记为已分配。内存类型在系统初始化后期alloc_low_pages可能会通过__get_free_pages向伙伴系统申请内存。为什么叫“low”这源于 32 位系统的历史背景。早期 x86 架构中内核在低端内存前 16MB建立直接映射从pgt_buf分配的页也位于这个区域。虽然现代 x86_64 内核的地形已改变但这个名称作为传统被保留下来。关键设计细节分配顺序alloc_low_pages的一个重要设计是支持从低地址向高地址分配。这在某些需要保证页表顺序的场景下如 kmap是必要的。函数通过一次分配多个页num参数然后逐个返回给调用者确保了分配顺序的可控性。Xen 兼容性注释明确指出alloc_low_pages返回的页是已经被直接映射过的。这个特性对于 Xen 虚拟机至关重要因为其页表机制有特殊的内存要求。调用限制alloc_low_pages主要用于系统启动早期。虽然它在运行期间也可能被调用例如内存热插拔时但后续的补丁已经将其使用的 GFP 标志从GFP_ATOMIC修正为更合适的GFP_KERNEL。启动困境与解决在内核启动早期建立直接内存映射Direct Mapping需要分配页表内存但分配内存本身又依赖已经建好的页表。为了打破这个循环内核在编译时预先在自身的 BSS 段中预留了一块称为pgt_buf的内存池。由于整个内核映像在启动早期就已经被映射这片区域天然可访问因此可以作为页表分配的“启动资金”。两阶段分配流程alloc_low_pages的逻辑清晰地将分配过程分为两个阶段通过指针递增和条件判断自然过渡。第一阶段从预留池 (pgt_buf) 分配这是启动初期最快的分配方式。函数通过维护pgt_buf_end指针来追踪当前已分配的位置。当请求num个页面时只需将pgt_buf_end向后增加num并返回原来的起始位置即可。这个池的大小在启动时由early_alloc_pgt_buf函数初始化早期默认大小为5 * PAGE_SIZE即20KB用于满足最初的映射需求。第二阶段切换到memblock分配器当pgt_buf的容量不足以满足新的分配请求时函数会切换到更通用的memblock分配器。分配范围它会在已经被直接映射过的内存区域即min_pfn_mapped到max_pfn_mapped之间查找空闲空间。关键操作通过memblock_find_in_range找到一块空闲区域后立即调用memblock_reserve将其标记为已占用防止被其他用途分配。