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 合并。
伙伴系统 free_area 数组与空闲链表结构 order 0 map[0] page A page B ... order 1 map[1] page C (empty) order 2 map[2] (empty) order 10 map[10] page Z 每个 order 链表只链接空闲块;map 位图用于快速判断伙伴是否可合并

2.3 页面分配流程

当内核请求分配 2order 个连续页框时,伙伴系统执行以下步骤:

  1. 快速路径(fast path): 从当前 zone 的 free_area[order] 链表头部取出一个空闲块。如果链表非空,直接返回。
  2. 分裂路径(split path): 如果当前 order 链表为空,则向高一级 order 搜索。找到第一个非空链表后,取出一个块,将其分裂为两个大小相等的伙伴块。
  3. 递归分裂: 将高 order 块分裂后,低地址块插入当前 order 链表,高地址块继续向下分裂,直到满足目标 order。
  4. 返回: 将最终得到的块从链表中移除,返回其起始页框描述符(struct page *)。
性能陷阱: 分裂操作会触发写锁(zone->lock),在高并发分配场景下可能成为瓶颈。内核通过 per-CPU 页框缓存(PCP)缓解此问题,避免频繁进入伙伴系统。
伙伴系统页面分配流程(order=1 请求示例) 请求 order=1 (2页) order 1 链表空 搜索 order 2 找到 order 2 块 分裂:order 2 块 → 两个 order 1 伙伴块(低地址 + 高地址) 低地址伙伴 → 返回给调用者 高地址伙伴 → 插入 order 1 链表 最终:调用者获得 2 个连续页框,order 1 链表新增一个空闲块 分裂过程需要持有 zone->lock,避免并发分配导致伙伴关系错乱

2.4 页面释放流程

释放页面时,伙伴系统尝试与伙伴块合并,以减少内存碎片:

  1. 检查伙伴: 计算待释放块的伙伴地址。伙伴地址 = 块起始地址 XOR (1 << (order + PAGE_SHIFT))。
  2. 位图判断: 检查 free_area[order].map 中对应位。如果伙伴空闲且可合并,则执行合并。
  3. 合并: 将伙伴块从链表中移除,合并后的块进入 order+1 链表,并更新位图。
  4. 递归合并: 继续检查更高 order 的伙伴,直到无法合并或达到 MAX_ORDER-1。
  5. 插入: 将最终块插入对应 order 的空闲链表。
合并条件严格性: 两个块必须满足:
(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 的连续物理内存,直接依赖伙伴系统。
伙伴系统在内核内存分配器中的位置 slab 分配器 kmalloc vmalloc 伙伴系统(Buddy System) 物理页框(Physical Page Frames)