2. 伙伴系统原理:伙伴算法的核心思想,内存块合并与分裂机制

2.1 伙伴系统概述

伙伴系统(Buddy System)是 Linux 内核管理物理内存的核心算法,负责解决物理内存分配与回收中的外部碎片问题。它将物理内存划分为 2 的幂次方大小的连续块,通过分裂与合并操作,在分配灵活性与碎片控制之间取得平衡。

核心目标:在 O(log N) 时间复杂度内完成内存分配与释放,同时将外部碎片控制在可接受范围内。

2.2 伙伴算法的核心思想

伙伴算法基于以下三个基本原则:

  1. 幂次方划分:所有内存块大小均为 2k 页(k 为 0 到 MAX_ORDER-1)。
  2. 伙伴关系:两个大小相同、地址连续且由同一个更大块分裂而来的内存块互为“伙伴”。
  3. 延迟合并:释放时立即检查伙伴是否空闲,若空闲则合并为更大块,并递归向上合并。
阶数 (Order) 块大小 (页数) 块大小 (4KB 页) 典型用途
0 20 = 1 4 KB 小块分配、slab 缓存
1 21 = 2 8 KB 小文件系统元数据
3 23 = 8 32 KB 进程栈、小缓冲区
5 25 = 32 128 KB 中等 DMA 缓冲区
10 210 = 1024 4 MB 大块连续分配

2.3 内存块分裂机制

当内核请求一个大小为 2k 页的内存块时,分配流程如下:

  1. 查找空闲链表:从 order = k 的空闲链表开始查找。
  2. 向上搜索:若当前 order 链表为空,则向 order+1 链表查找。
  3. 分裂操作:从更高 order 链表中取出一个块,将其分裂为两个大小相等的伙伴块。
  4. 递归分裂:将其中一个伙伴块放入低一阶的链表,另一个继续分裂,直到达到目标 order。
  5. 分配返回:将最终得到的 2k 大小块返回给调用者。
性能要点:分裂操作仅涉及链表操作和位图标记,时间复杂度为 O(1)。最坏情况下需要从 MAX_ORDER-1 向下搜索,但实际系统中大部分分配集中在 order 0-3。
伙伴系统分裂过程示例(请求 order=1,即 2 页) 初始空闲块:order=3 (8 页连续) 伙伴 A:order=2 (4 页) 伙伴 B:order=2 (4 页) 伙伴 A1:order=1 (2 页) 伙伴 A2:order=1 (2 页) ✅ 分配:order=1 (2 页) 分裂结果状态 • 伙伴 A1 → 分配给请求者 • 伙伴 A2 → 放入 order=1 空闲链表 • 伙伴 B → 放入 order=2 空闲链表

2.4 内存块合并机制

当内存块被释放时,内核执行反向合并操作:

  1. 检查伙伴:计算当前释放块的伙伴地址,检查伙伴是否在空闲链表中。
  2. 合并条件:伙伴必须满足:大小相同、地址连续、且当前也在空闲链表中。
  3. 执行合并:将两个伙伴块从当前 order 链表移除,合并为 2k+1 大小的块。
  4. 递归合并:将合并后的块放入高一阶链表,并继续检查其伙伴是否空闲。
  5. 终止条件:直到伙伴不空闲或达到 MAX_ORDER-1 为止。
关键约束:伙伴地址必须满足 buddy_pfn = pfn ^ (1 << order) 的异或关系。只有通过分裂得到的两个块才互为伙伴,任意两个连续块不一定满足伙伴关系。

2.5 伙伴地址计算

伙伴地址的计算是合并操作的核心,其数学原理基于二进制地址的位操作:

/* 内核源码:mm/page_alloc.c */
static inline unsigned long
__find_buddy_pfn(unsigned long page_pfn, unsigned int order)
{
    return page_pfn ^ (1 << order);
}

/* 示例:假设 page_pfn = 0x1000, order = 2 */
/* 计算:0x1000 ^ (1 << 2) = 0x1000 ^ 0x4 = 0x1004 */
/* 伙伴 PFN 为 0x1004,即两个 4 页块互为伙伴 */
阶数 (order) 块大小 (页) 伙伴异或掩码 地址对齐要求
0 1 0x1 2 字节对齐
1 2 0x2 4 字节对齐
2 4 0x4 8 字节对齐
3 8 0x8 16 字节对齐

2.6 合并过程完整示例

以下展示从 order=1 块释放到合并为 order=3 块的完整流程:

伙伴合并过程:从 order=1 到 order=3 步骤 1:释放 order=1 块 释放块 (2页) 伙伴空闲 (2页) 检查伙伴 步骤 2:合并为 order=2 合并后:order=2 (4 页) 递归向上 步骤 3:检查 order=2 伙伴 合并块 (4页) 伙伴空闲 (4页) 伙伴空闲 步骤 4:合并为 order=3 最终合并:order=3 (8 页连续块) 合并终止条件 • 若高一阶伙伴不空闲 → 停止合并,将当前块放入对应 order 空闲链表 • 若达到 MAX_ORDER-1 → 停止合并,该块为系统最大连续块

2.7 伙伴系统的数据结构

Linux 内核使用 struct free_area 管理每个 order 的空闲块链表:

/* 内核源码:include/linux/mmzone.h */
struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];
    unsigned long       nr_free;
};

struct zone {
    /* ... */
    struct free_area    free_area[MAX_ORDER];
    /* ... */
};

/* MAX_ORDER 默认值为 11,即最大连续块为 2^10 = 1024 页 */
数据结构 作用 关键字段
struct free_area 管理单个 order 的空闲块 free_list:按迁移类型分组的链表
nr_free:空闲块计数
struct zone 内存管理区(DMA/Normal/HighMem) free_area[MAX_ORDER]:所有 order 的空闲块数组
struct page 物理页描述符 lru:链表节点
private:order 信息
flags:PG_buddy 标记

2.8 性能优化要点

调优建议:
  • 避免频繁 order-0 分配:大量 order-0 分配会导致伙伴系统链表操作频繁,建议使用 slab 缓存或 kmem_cache 替代。
  • 关注 MAX_ORDER 配置:对于需要大块连续内存的系统(如 GPU、RDMA),可适当增大 MAX_ORDER,但会增加内存碎片风险。
  • 迁移类型分组:伙伴系统按 MIGRATE_UNMOVABLE、MIGRATE_RECLAIMABLE、MIGRATE_MOVABLE 等类型分组,避免不可移动页阻塞大块合并。
  • 监控碎片指数:通过 /proc/buddyinfo 观察各 order 空闲块分布,若高阶块持续为 0 则说明碎片严重。

2.9 伙伴系统 vs 其他分配算法

特性 伙伴系统 SLAB 分配器 kmalloc
分配粒度 2k 页 任意大小(缓存对齐) 任意大小(2n 字节)
外部碎片 低(幂次方对齐) 无(固定大小缓存) 中(2n 对齐)
内部碎片 高(平均 50%) 低(对象大小匹配) 中(向上取整)
分配速度 O(log N) O(1) O(log N)
适用场景 物理页帧分配 内核对象缓存 通用小内存分配
理解要点:伙伴系统是 Linux 物理内存分配的基石,SLAB 和 kmalloc 最终都通过伙伴系统获取物理页。伙伴系统的分裂/合并效率直接影响系统整体内存性能,尤其在内存压力大或碎片严重时表现更为明显。