expand()是伙伴系统分配路径中的核心拆分函数。当从空闲链表取出的页块大于所需阶数时,它负责将大块“切蛋糕”一样逐级拆分,把不需要的部分重新放回低阶空闲链表,最终只留下恰好满足请求的页块。
核心作用与逻辑
expand()的核心任务是在分配高阶页块时进行降级拆分。它的逻辑是:从高阶(high)开始,每次循环将阶数减一,把拆分出的“后半块”页块重新标记为 Buddy 状态并加入到对应阶数的空闲链表中,直到达到所需的低阶(low)。
参数解析
| 参数 | 含义 |
|---|---|
zone | 目标内存区域 |
page | 页块首页 |
low | 目标阶数(实际需要的阶数) |
high | 起始阶数(从空闲链表取出的页块阶数) |
migratetype | 页块的迁移类型 |
核心流程
1. 逐级拆分
函数进入一个while (high > low)循环,每次迭代执行high--,相当于降低一级 order 。然后计算size = 1 << high,定位到拆分出的后半块页块(&page[size])。
2. 标记与归还
对拆分出的页块执行__add_to_free_list()或add_to_free_list()将其加入对应阶数的空闲链表,并调用set_buddy_order()设置其 Buddy 状态和private字段中的 order 值 。
3. 统计更新
现代内核中,expand()会累加拆分归还的页数nr_added,最后统一调用account_freepages()更新统计,避免了在循环中频繁更新 vmstat 的开销 。
在分配路径中的位置
它被__rmqueue_smallest()和__rmqueue_fallback()调用。调用链如下:
__rmqueue_smallest() └── 从 free_area[current_order] 取出页块 └── del_page_from_free_list() // 从链表移除 └── expand(zone, page, order, current_order, migratetype) // 拆分 └── 将剩余部分放回低阶链表
只有current_order > order时才需要拆分;如果恰好相等,expand()直接返回。
关键设计点
1. 拆分顺序影响 I/O 性能
内核注释特别强调,拆分时先分配低地址部分还是高地址部分会显著影响 IO 子系统的 sglist 合并效率,因此这个顺序是经过经验测试固定的 。
2. 尾插优化
有补丁建议将拆分出的页块加入链表尾部而非头部,这样它们不容易被立即再次分配,从而有更大机会与后续释放的伙伴合并,减少碎片 。
3. 迁移类型的处理
如果内核决定改变该页块的迁移类型(如 fallback 时),拆分出的剩余部分也会被放入新的迁移类型链表中 。
总结
expand()是伙伴系统的“切蛋糕”函数:它从高阶空闲块中逐级拆分,将不需要的部分重新标记为 Buddy 状态并归还到低阶链表,最终只保留恰好满足请求大小的页块。它的拆分顺序、统计更新方式和链表插入策略,都是影响伙伴系统碎片化和 IO 性能的关键细节。