2. 伙伴系统:Buddy System原理、内存分配与释放、order与page block、碎片整理

2.1 伙伴系统核心原理

伙伴系统(Buddy System)是Linux内核管理物理内存的核心算法,负责将连续的物理页面高效地分配和回收。其基本思想是将物理内存按2的幂次方划分为连续的块(称为“伙伴”),分配时从满足需求的最小块开始,若没有则向上合并;释放时检查其伙伴是否空闲,若是则合并为更大的块。

核心概念: 伙伴(Buddy)是指两个大小相同、地址连续且由同一个更大块分裂而来的内存块。它们必须满足:大小相同、物理地址连续、由同一父块分裂。
设计目标: 伙伴系统通过合并相邻空闲块来减少外部碎片,同时通过2的幂次分配策略使分配和释放操作的时间复杂度稳定在O(log N)。

2.2 Order与Page Block

在伙伴系统中,order 表示内存块的大小指数。一个order为n的块包含2n个连续的物理页面(每个页面通常为4KB)。内核维护一个以order为索引的空闲链表数组,每个链表链接相同大小的空闲块。

Order 页面数量 块大小 典型用途
0 1 4 KB 最小分配单元,进程页表、小对象
1 2 8 KB 小缓冲区、DMA描述符
2 4 16 KB 文件系统元数据
3 8 32 KB 网络协议栈sk_buff
4 16 64 KB 中等大小内核对象
5 32 128 KB SLUB分配器后备缓存
9 512 2 MB 透明大页(THP)
10 1024 4 MB 大页(HugeTLB)
MAX_ORDER-1 2MAX_ORDER-1 最大连续块 系统最大连续物理内存
注意: MAX_ORDER 在大多数架构上默认为11(即最大order为10),对应最大连续块为4MB(4KB × 210)。容器场景下,频繁的order-0分配和释放可能导致碎片化,影响大页分配。

2.3 内存分配流程

当内核请求分配2order个连续页面时,伙伴系统执行以下步骤:

  1. 查找空闲块: 从当前order的空闲链表开始查找,若有空闲块则直接分配。
  2. 向上分裂: 若当前order无空闲块,则向更高order(order+1)查找,将大块分裂为两个伙伴块,一个放入当前order链表,另一个继续分配。
  3. 递归分裂: 重复步骤2直到找到满足需求的块或达到MAX_ORDER。
  4. 分配失败: 若MAX_ORDER也无空闲块,则触发内存回收(kswapd)或直接内存回收(direct reclaim)。
伙伴系统内存分配流程 分配请求 order=n 检查 order=n 空闲链表 有可用块 → 直接分配 无可用块 → 向上查找 分裂 order+1 块为两个伙伴 递归:回到检查 order=n 链表 分裂后一个块分配,一个块入链表

2.4 内存释放与伙伴合并

释放内存时,伙伴系统执行与分配相反的操作——合并(Coalescing)。当释放一个块时,内核检查其伙伴是否也空闲,若是则将两者合并为更大的块,并递归向上合并。

合并条件:
  • 两个块大小相同(order相同)
  • 物理地址连续
  • 由同一个父块分裂而来(通过地址计算判断)
  • 伙伴块当前处于空闲状态

判断伙伴地址的公式:

buddy_pfn = page_pfn ^ (1 << order)

其中 page_pfn 是当前页框号,order 是块大小指数。通过异或操作即可快速定位伙伴的起始页框号。

2.5 碎片整理机制

随着系统长时间运行,频繁的分配和释放会导致物理内存碎片化——虽然总空闲内存充足,但无法分配连续的2order大块。Linux内核提供多种碎片整理手段:

机制 触发条件 工作原理 对容器影响
内存规整(Compaction) 分配大页失败时主动触发 将可移动页面迁移到区域一端,腾出连续空间 可能引起短暂延迟,影响容器QoS
LRU页面回收 kswapd周期性扫描 回收不活跃的匿名页和文件页 减少内存压力,间接缓解碎片
虚拟可移动区(ZONE_MOVABLE) 内核启动时预留 将可迁移页面隔离到专用区域 提高容器内存热插拔效率
页面迁移(Page Migration) 内存规整或CMA调用 将页面内容复制到新位置,更新页表 迁移期间页面不可访问
容器调优建议: 在容器宿主机上,建议启用 vm.compact_memory=1 定期触发内存规整,并设置 vm.extfrag_threshold 控制碎片容忍度。对于延迟敏感的容器,可考虑使用 madvise 或 mbind 绑定大页。

2.6 碎片整理流程

内存规整(Compaction)是内核对抗碎片的主要手段,其工作流程如下:

  1. 扫描迁移源: 从区域尾部开始扫描,标记可移动的页面。
  2. 扫描迁移目标: 从区域头部开始扫描,寻找空闲页面作为迁移目标。
  3. 页面迁移: 将源页面内容复制到目标页面,更新所有映射该页面的页表项。
  4. 释放源页面: 源页面变为空闲,加入伙伴系统。
  5. 重复执行: 直到满足分配需求或无法继续迁移。
内存规整(Compaction)流程 物理内存区域(Zone) 空闲页 已分配 不可迁移 空闲 已分配 空闲页(尾部) 迁移后:连续空闲区域合并 连续空闲区域(可分配大块) 不可迁移 空闲 规整步骤 ① 从区域尾部扫描可迁移页面(蓝色块) ② 从区域头部扫描空闲页面(绿色块)作为迁移目标 ③ 将可迁移页面复制到头部空闲位置,释放尾部空间 ④ 尾部空闲区域合并为连续大块,满足大页分配需求

2.7 容器场景下的碎片挑战

在容器化环境中,伙伴系统的碎片问题尤为突出:

  • 频繁分配释放: 容器启动和销毁导致大量order-0页面分配释放,产生微观碎片。
  • 大页需求: 数据库、缓存类容器常请求2MB大页(order-9),碎片化严重时分配失败。
  • 内存热插拔: 容器内存上限动态调整时,需要连续物理内存区域。
  • NUMA影响: 跨NUMA节点的内存分配加剧碎片化,影响容器内存访问延迟。
性能调优关键: 监控 /proc/buddyinfo 观察各order的空闲块数量。若高阶order(≥4)长期为0,说明碎片严重,需触发内存规整或调整 vm.min_free_kbytes 预留连续内存。

2.8 关键数据结构

伙伴系统的核心数据结构是 struct free_area,每个zone维护一个free_area数组:

struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];
    unsigned long       nr_free;
};

struct zone {
    ...
    struct free_area    free_area[MAX_ORDER];
    unsigned long       _watermark[NR_WMARK];
    ...
};

其中 free_list 按迁移类型(MIGRATE_UNMOVABLE、MIGRATE_RECLAIMABLE、MIGRATE_MOVABLE等)组织,避免不可迁移页面阻塞大块合并。

迁移类型的作用: 将不可迁移页面(如内核分配)与可迁移页面(如用户态匿名页)隔离,使内存规整时只需移动可迁移页面,提高碎片整理效率。