2. 伙伴系统原理
2.1 伙伴系统概述
伙伴系统(Buddy System)是 Linux 内核管理物理内存的核心算法,负责解决物理内存分配与回收中的外部碎片问题。它将物理内存划分为 2 的幂次方大小的连续块,通过分裂与合并操作,在分配灵活性与碎片控制之间取得平衡。
核心目标:在 O(log N) 时间复杂度内完成内存分配与释放,同时将外部碎片控制在可接受范围内。
2.2 伙伴算法的核心思想
伙伴算法基于以下三个基本原则:
- 幂次方划分:所有内存块大小均为 2k 页(k 为 0 到 MAX_ORDER-1)。
- 伙伴关系:两个大小相同、地址连续且由同一个更大块分裂而来的内存块互为“伙伴”。
- 延迟合并:释放时立即检查伙伴是否空闲,若空闲则合并为更大块,并递归向上合并。
| 阶数 (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 页的内存块时,分配流程如下:
- 查找空闲链表:从 order = k 的空闲链表开始查找。
- 向上搜索:若当前 order 链表为空,则向 order+1 链表查找。
- 分裂操作:从更高 order 链表中取出一个块,将其分裂为两个大小相等的伙伴块。
- 递归分裂:将其中一个伙伴块放入低一阶的链表,另一个继续分裂,直到达到目标 order。
- 分配返回:将最终得到的 2k 大小块返回给调用者。
性能要点:分裂操作仅涉及链表操作和位图标记,时间复杂度为 O(1)。最坏情况下需要从 MAX_ORDER-1 向下搜索,但实际系统中大部分分配集中在 order 0-3。
2.4 内存块合并机制
当内存块被释放时,内核执行反向合并操作:
- 检查伙伴:计算当前释放块的伙伴地址,检查伙伴是否在空闲链表中。
- 合并条件:伙伴必须满足:大小相同、地址连续、且当前也在空闲链表中。
- 执行合并:将两个伙伴块从当前 order 链表移除,合并为 2k+1 大小的块。
- 递归合并:将合并后的块放入高一阶链表,并继续检查其伙伴是否空闲。
- 终止条件:直到伙伴不空闲或达到 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 块的完整流程:
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 最终都通过伙伴系统获取物理页。伙伴系统的分裂/合并效率直接影响系统整体内存性能,尤其在内存压力大或碎片严重时表现更为明显。