2. 物理内存管理基础:伙伴系统原理与页面分配释放流程
伙伴系统(Buddy System)是 Linux 内核物理内存管理的核心算法,负责管理连续物理页框的分配与回收。它直接服务于 slab 分配器与 kmalloc,是上层内存分配器的基石。理解伙伴系统的工作原理,是掌握内存分配器选型的前提。
2.1 伙伴系统核心概念
伙伴系统将物理内存划分为 2order 个连续页框组成的块(block),其中 order 为 0 到 MAX_ORDER-1(通常 MAX_ORDER=11)。每个 order 对应一个空闲链表,链表中的每个节点代表一个大小相同的连续空闲内存块。
| Order | 页框数 | 大小(4K 页) | 典型用途 |
|---|---|---|---|
| 0 | 1 | 4 KB | slab 对象、进程页表 |
| 1 | 2 | 8 KB | 小文件系统缓存 |
| 2 | 4 | 16 KB | DMA 缓冲区 |
| 3 | 8 | 32 KB | 网络 sk_buff |
| 4 | 16 | 64 KB | 中等 I/O 请求 |
| 9 | 512 | 2 MB | 透明大页(THP) |
| 10 | 1024 | 4 MB | 内核大块映射 |
关键术语:两个大小相同、地址连续的块,且它们由同一个更大的块分裂而来,互称为“伙伴”(buddy)。伙伴的合并条件是:两个块的物理地址连续,且低地址块的起始地址是 2(order+1) 的整数倍。
2.2 伙伴系统数据结构
每个内存节点(NUMA node)的每个管理区(zone)都维护一个 free_area 数组,数组下标对应 order 值。每个 free_area 包含一个链表头和一个位图(bitmap),用于跟踪该 order 下空闲块的伙伴关系。
// 内核源码 include/linux/mmzone.h (简化示意)
struct free_area {
struct list_head free_list; // 空闲块链表
unsigned long *map; // 伙伴位图
};
struct zone {
// ...
struct free_area free_area[MAX_ORDER]; // 0 ~ MAX_ORDER-1
// ...
};
数据结构要点:
free_list:双向链表,链接所有该 order 的空闲块描述符(struct page)。map:位图,每一位表示一对伙伴的合并状态。1 表示已合并,0 表示未合并。- 分配时从高 order 向低 order 搜索,释放时从低 order 向高 order 合并。
2.3 页面分配流程
当内核请求分配 2order 个连续页框时,伙伴系统执行以下步骤:
- 快速路径(fast path): 从当前 zone 的
free_area[order]链表头部取出一个空闲块。如果链表非空,直接返回。 - 分裂路径(split path): 如果当前 order 链表为空,则向高一级 order 搜索。找到第一个非空链表后,取出一个块,将其分裂为两个大小相等的伙伴块。
- 递归分裂: 将高 order 块分裂后,低地址块插入当前 order 链表,高地址块继续向下分裂,直到满足目标 order。
- 返回: 将最终得到的块从链表中移除,返回其起始页框描述符(struct page *)。
性能陷阱: 分裂操作会触发写锁(zone->lock),在高并发分配场景下可能成为瓶颈。内核通过 per-CPU 页框缓存(PCP)缓解此问题,避免频繁进入伙伴系统。
2.4 页面释放流程
释放页面时,伙伴系统尝试与伙伴块合并,以减少内存碎片:
- 检查伙伴: 计算待释放块的伙伴地址。伙伴地址 = 块起始地址 XOR (1 << (order + PAGE_SHIFT))。
- 位图判断: 检查
free_area[order].map中对应位。如果伙伴空闲且可合并,则执行合并。 - 合并: 将伙伴块从链表中移除,合并后的块进入 order+1 链表,并更新位图。
- 递归合并: 继续检查更高 order 的伙伴,直到无法合并或达到 MAX_ORDER-1。
- 插入: 将最终块插入对应 order 的空闲链表。
合并条件严格性: 两个块必须满足:
(1) 大小相同(相同 order);
(2) 物理地址连续;
(3) 低地址块的起始地址是 2(order+1) 的整数倍;
(4) 两个块当前都处于空闲状态。
(1) 大小相同(相同 order);
(2) 物理地址连续;
(3) 低地址块的起始地址是 2(order+1) 的整数倍;
(4) 两个块当前都处于空闲状态。
2.5 伙伴系统的优缺点
| 维度 | 优点 | 缺点 |
|---|---|---|
| 分配速度 | O(1) 快速路径(链表非空时) | 分裂路径 O(log N),高 order 搜索耗时 |
| 内存碎片 | 合并机制有效缓解外碎片 | 内碎片不可避免(分配粒度 2order) |
| 并发性 | per-zone 锁,NUMA 友好 | 高竞争场景下锁争用严重 |
| 大块支持 | 天然支持 2order 连续内存 | 长时间运行后大块稀缺 |
| 与 slab 关系 | 为 slab 提供连续页框 | slab 对象释放后返回伙伴系统代价高 |
2.6 伙伴系统与上层分配器的关系
伙伴系统是内核物理内存分配的最底层引擎,上层分配器通过以下方式与之交互:
- slab 分配器: 调用
alloc_pages()从伙伴系统获取 order=0 的页框,然后在其上划分对象。 - kmalloc: 对于 > 8KB 的请求,直接调用伙伴系统分配 order≥1 的连续页框。
- vmalloc: 虽然使用伙伴系统分配物理页,但通过页表映射为非连续虚拟地址。
- DMA 缓冲区: 通常要求 order≥0 的连续物理内存,直接依赖伙伴系统。