伙伴系统算法:高效管理内存的二分艺术

摘要:伙伴系统(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 内存分配:最小适配与分割链式反应

步骤分解

  1. 计算最小阶k
     (s为申请大小)。

  2. 搜索匹配块

    • 查询k阶空闲链表 free_area[k].free_list
    • 若空,向上搜索阶m(m>k) ,直至找到非空链表 。
  3. 递归分割

    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阶链表 。
2.2 内存释放:伙伴检测与级联合并

关键操作

  1. 伙伴块定位

    • 释放块地址为 addr,阶为k,则伙伴地址:

      (⊕为异或)。
  2. 合并条件

    • 伙伴块同阶且空闲;
    • 伙伴块属于同一父节点(高位地址一致)。
  3. 递归合并

    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 pageprivate字段标记块阶,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 核心操作函数剖析
  1. 分配入口 __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--;
      }
      
  2. 释放合并 __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等机制的协作,以优化系统整体内存效率 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小李独爱秋

你的鼓励将是我加更的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值