引导内存分配器 Buddy 分配器的关系
在 Linux 内核启动的早期阶段,物理内存管理面临着一个“鸡生蛋,蛋生鸡”的问题:内核需要分配内存来初始化用于内存管理的数据结构(如struct page数组),但此时完善的 Buddy 分配器尚未建立。为了解决这个问题,内核引入了简单的引导内存分配器。
一、为什么需要引导内存分配器?
在内核启动初期,内存管理子系统尚未初始化完成,主要面临以下挑战:
- 元数据未就绪:描述物理内存的
mem_map(页描述符数组)尚未分配和初始化。 - Buddy 系统未工作:Buddy 算法依赖于
struct zone和struct page等结构,在这些结构就绪前无法工作。 - 早期分配需求:内核在初始化阶段就需要分配内存,例如用于存放内核页表、设备树(Device Tree)解析结果、initrd 镜像等。
因此,内核需要一个简单、临时的内存分配机制,这就是引导内存分配器。它仅在启动阶段工作,一旦 Buddy 系统初始化完成,引导内存分配器就会将管理权移交出去并退役。
二、两种引导内存分配器:bootmem 与 memblock
随着 Linux 内核的发展,引导内存分配器也经历了演变。
1. bootmem 分配器 (旧架构)
bootmem是较早期的引导内存分配器,主要利用一个位图(bitmap)来管理内存。
- 原理:使用一个位图,每一位代表一个物理页。如果该位为 1,表示该页已被占用;为 0 表示空闲。
- 分配:采用最先适配算法(First Fit),扫描位图找到足够大的连续空闲区域。
- 缺点:
- 位图本身需要占用内存,且随着内存容量增大,位图变得很大。
- 在大型系统(如 NUMA)中,管理多个节点的 bootmem 结构复杂。
- 现状:ARM64 架构内核已不再使用
bootmem,但其他一些处理器架构仍在使用。
2. memblock 分配器 (新架构)
memblock是目前主流(包括 ARM64)使用的引导内存分配器,用于替代bootmem。
- 数据结构:它维护两个列表(数组):
memory:描述系统所有可用的物理内存范围。reserved:描述已经被预留、占用的内存范围。
- 原理:
memblock不使用庞大的位图,而是直接管理“内存区域(Region)”的数组。- 当需要分配内存时,它在
memory列表中查找空闲空间,并将其添加到reserved列表中。
- 优点:
- 结构更紧凑,开销小。
- 支持从设备树(DTS)直接获取内存布局信息。
- 支持热插拔(Hotplug)和复杂的 NUMA 配置。
memblock_add添加内存,memblock_remove移除内存,memblock_alloc分配内存。
三、引导分配器与 Buddy 分配器的交接
引导内存分配器是一个临时的“管家”,它的最终使命是将内存管理权平稳过渡给 Buddy 分配器。这个过程发生在内核初始化函数mm_init()流程中。
交接流程:
收集内存信息:
- 内核启动时,首先通过解析设备树(DTS)或 BIOS/UEFI 获取物理内存布局。
- 调用
memblock_add()将物理内存范围注册到引导分配器中。
早期分配服务:
- 在 Buddy 系统建立前,内核各子系统的内存申请(如页表分配)都由
memblock_alloc()处理。
- 在 Buddy 系统建立前,内核各子系统的内存申请(如页表分配)都由
协助建立 Buddy 结构:
- 内核利用引导分配器分配的内存,来创建和初始化 Buddy 系统所需的关键数据结构,主要是
mem_map(存放所有的struct page)。
- 内核利用引导分配器分配的内存,来创建和初始化 Buddy 系统所需的关键数据结构,主要是
释放空闲内存给 Buddy:
- 当 Buddy 系统的数据结构初始化完成后,内核会调用类似
memblock_free_all()或free_all_bootmem()的函数。 - 核心动作:遍历引导分配器中发现的所有未使用的空闲页,将它们逐个(或成块)释放给 Buddy 系统(通过调用
__free_pages等接口)。 - 此时,这些页会被标记为空闲,挂入 Buddy 系统的
free_area链表中,正式成为 Buddy 系统的可分配资源。
- 当 Buddy 系统的数据结构初始化完成后,内核会调用类似
引导分配器退役:
- 一旦空闲内存全部移交完毕,引导分配器的数据结构本身所占用的内存也会被释放(如果可能),或保留作为存根。
- 此后,所有的内存申请(包括内核和用户空间)都必须通过 Buddy 分配器(如
alloc_pages,kmalloc)进行。
四、总结
| 特性 | 引导内存分配器 (memblock/bootmem) | Buddy 分配器 |
|---|---|---|
| 生命周期 | 仅限于内核启动早期 | 内核初始化完成后一直运行 |
| 管理粒度 | 粗粒度的物理地址范围 | 精细的页框 (Page Frame) |
| 算法复杂度 | 简单(线性扫描/数组管理) | 复杂(阶梯链表、反碎片、LRU等) |
| 主要职责 | 1. 临时响应早期分配请求 2. 协助初始化 Buddy 的元数据 3. 将空闲内存移交给 Buddy | 系统运行时的通用物理内存管理 |
简而言之,引导内存分配器是 Buddy 分配器的前置加载器。它在荒芜的物理内存上建立起最基础的秩序,为构筑宏大的 Buddy 内存帝国铺平道路,并在完成使命后功成身退。