摘要:伙伴系统(Buddy System)是操作系统中解决内存碎片问题的核心算法,尤其适用于大块连续物理内存分配。本文深入剖析其工作原理、实现机制及在Linux中的应用。
1. 算法核心思想:二分管理与碎片控制
1.1 数学本质与伙伴关系
- 幂次划分:内存按 2^k(k为阶order)划分,确保块地址对齐(如地址末k位为0),提升硬件访问效率 。
- 伙伴定义:两区块需同时满足:
- 大小相同(同阶order);
- 物理地址连续;
- 低位地址对齐(如块A地址为 p x 2^(k+1) ,则伙伴B地址为 p x 2^(k+1) + 2^k)。
- 二叉树映射:系统通过完全二叉树管理块关系,每个节点对应一个内存块,子节点为父节点的二等分伙伴 。
1.2 设计哲学与约束
- 外部碎片消除:合并机制确保相邻空闲块快速重组为大块,避免内存“空洞” 。
- 内部碎片代价:申请非2幂大小(如3KB)需分配4KB,浪费率最高达50% 。
- Linux实践限制:
- 最大阶数
MAX_ORDER=11(1024页),即单次分配上限4MB(x86架构页大小4KB); - 分区管理:针对DMA、NORMAL等不同内存区域(zone)独立运行伙伴系统 。
- 最大阶数
2. 工作流程详解:分割与合并的递归艺术
2.1 内存分配:最小适配与分割链式反应
步骤分解:
-
计算最小阶k:
(s为申请大小)。 -
搜索匹配块:
- 查询k阶空闲链表
free_area[k].free_list; - 若空,向上搜索阶m(m>k) ,直至找到非空链表 。
- 查询k阶空闲链表
-
递归分割:
while (m > k) { split_block(block_m); // 分割为两个2^{m-1}块 add_to_free_list(block_m1, m-1); // 一块加入m-1阶链表 block_m = block_m2; // 另一块继续分割 m--; } allocate_block(block_k); // 分配最终块示例扩展(512KB初始内存):
- P3申请192KB → 需256KB块(k = 8):
- 8阶无空闲,搜索9阶(512KB块);
- 分割512KB为两个256KB(伙伴A/B);
- 分配A给P3,B插入8阶链表 。
- P3申请192KB → 需256KB块(k = 8):
2.2 内存释放:伙伴检测与级联合并
关键操作:
-
伙伴块定位:
- 释放块地址为
addr,阶为k,则伙伴地址:

(⊕为异或)。
- 释放块地址为
-
合并条件:
- 伙伴块同阶且空闲;
- 伙伴块属于同一父节点(高位地址一致)。
-
递归合并:
while (k < MAX_ORDER-1 && buddy_is_free(buddy_addr, k)) { remove_from_list(buddy_addr, k); // 移出伙伴块 addr = min(addr, buddy_addr); // 新块起始地址取两者最小值 k++; // 阶提升 buddy_addr = addr ^ (1 << (PAGE_SHIFT + k)); // 计算新伙伴 } add_to_free_list(addr, k); // 插入合并后块Linux优化:通过
struct page的private字段标记块阶,flags位图记录伙伴状态 。
3. 数据结构实现:Linux内核的工程实践
3.1 多级管理架构
// 关键数据结构(简化版)
struct zone {
struct free_area free_area[MAX_ORDER]; // 11阶空闲链表数组
...
};
struct free_area {
struct list_head free_list; // 同阶空闲块链表头
unsigned long nr_free; // 当前阶空闲块计数
};
struct page {
unsigned int order; // 块所属阶(分割/合并依据)
unsigned int private; // 标识所属链表阶数
struct list_head list; // 链表指针
...
};
3.2 核心操作函数剖析
-
分配入口
__rmqueue_smallest():- 深度优先搜索:从申请阶k向MAX_ORDER遍历,返回首个非空链表块 。
- 分割操作
expand():while (order > target_order) { new_block = block + (1 << (order-1)); // 计算子块地址 add_to_free_area(new_block, order-1); // 子块插入低阶链表 set_page_order(new_block, order-1); // 设置子块阶 order--; }
-
释放合并
__free_pages_ok():- 伙伴状态检查:通过
page_is_buddy()验证伙伴块同阶且空闲 。 - 位图优化:使用
zone->buddy_bitmap加速伙伴状态判断(bit=0表示双块空闲)。
- 伙伴状态检查:通过
3.3 性能与碎片权衡
| 指标 | 优势 | 缺陷与优化 |
|---|---|---|
| 时间复杂度 | O(logN) 的稳定分配/释放效率 | 小内存分配需多次分割 → 引入Slab分配器 |
| 外部碎片 | 合并机制显著减少碎片 | 无法消除内部碎片 → 浪费率公式:1 - (s/2^k) |
| 扩展性 | 分区(zone)设计适应异构内存 | 最大连续内存受限 → 驱动需分段申请大内存 |
4. 优缺点分析
优势:
- 高效防碎片:合并机制显著减少外部碎片,确保大块连续内存可用 ;
- O(logN)时间复杂度:分配/释放操作在完全二叉树下高效完成 ;
- 地址自动对齐:块起始地址天然对齐,提升访问效率 。
缺陷:
- 内部碎片:申请非2幂次大小时产生浪费(如申请3KB需分配4KB);
- 小内存效率低:分配小块需多次分割,故Linux结合Slab分配器优化小对象分配 。
5. Linux中的实践
- 起源:伙伴系统源自Solaris,后成为Linux物理内存管理核心 。
- 双路径分配:
- 快速路径:直接分配空闲块(
rmqueue函数); - 慢速路径:触发内存回收后重试(如水位线检查与页框回收)。
- 快速路径:直接分配空闲块(
- 最大连续内存:默认支持4MB(1024页),满足多数驱动需求 。
6. 总结
伙伴系统通过二分分割与伙伴合并的优雅设计,在保证低碎片率的同时实现高速内存分配。尽管存在内部碎片问题,其仍是现代操作系统(如Linux)管理大块连续内存的基石。开发者需理解其与Slab等机制的协作,以优化系统整体内存效率 。
842

被折叠的 条评论
为什么被折叠?



