2. 伙伴系统:伙伴算法原理、内存碎片化问题、order与pageblock

2.1 伙伴算法核心原理

伙伴系统(Buddy System)是Linux内核管理物理内存分配与回收的基础算法。它将物理内存划分为2的幂次方大小的连续块,以order(阶)为单位进行管理。一个order为n的块包含2n个连续物理页框(page frame)。

核心思想:当分配内存时,将大块逐级对半切分,直到满足请求的最小order;当释放内存时,检查相邻的“伙伴”块是否空闲,若空闲则合并为更大的块,逐级向上合并,以最大限度减少外部碎片。
伙伴系统分配与合并流程 order=3 (8页) 初始空闲块 分配请求 order=1 order=2 (4页) 左半 order=2 (4页) 右半 order=1 (2页) 分配 order=1 (2页) 伙伴 释放 合并为 order=2 (4页)

2.2 order与pageblock

Linux内核使用order(0~10)来管理不同大小的连续内存块。每个order对应一个空闲链表,存储该大小的空闲块。pageblock是内存迁移和碎片管理的基本单元,通常大小为2order页(典型值为order=3,即8页,共32KB)。

order 页数 大小(4KB页) 典型用途
0 1 4 KB 小对象、slab分配
1 2 8 KB 小文件页、栈
2 4 16 KB 中等大小分配
3 8 32 KB pageblock默认大小
4 16 64 KB 文件系统元数据
5 32 128 KB 中等文件缓存
6 64 256 KB 大文件缓存
7 128 512 KB 内核模块、驱动
8 256 1 MB 大块连续分配
9 512 2 MB 透明大页(THP)
10 1024 4 MB 巨型页、DMA
性能提示:order越大,分配连续物理内存越困难,因为需要更大的空闲块。系统长时间运行后,高order分配失败率上升是内存碎片化的典型信号。

2.3 内存碎片化问题

内存碎片化分为内部碎片和外部碎片。伙伴系统主要面临外部碎片问题:虽然总空闲内存充足,但无法找到足够大的连续物理块。

  • 外部碎片:空闲内存被分割成许多小碎片,无法满足高order分配请求。例如:总空闲内存100MB,但最大连续块只有64KB。
  • 内部碎片:分配的内存块大于实际需求,造成浪费。伙伴系统通过2的幂次分配,内部碎片最多可达50%(平均25%)。
内存碎片化示意图 已分配 已分配 已分配 已分配 空闲 空闲 空闲 空闲 外部碎片:空闲块分散,无法满足大块连续分配 理想布局(迁移后): 已分配页聚集 连续空闲大块(order≥3)

2.4 伙伴系统的数据结构

每个内存区域(zone)维护一个free_area数组,数组索引对应order。每个free_area包含一个空闲链表和nr_free计数。

// 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(order 0~10)。每个order的链表按迁移类型(MIGRATE_TYPES)进一步细分,以优化碎片管理。

2.5 迁移类型与碎片避免

Linux内核引入迁移类型(migrate type)来缓解碎片化。每个pageblock被标记为一种迁移类型,内核在分配时尽量将相同类型的页聚集在一起。

迁移类型 描述 可迁移性
MIGRATE_UNMOVABLE 不可移动页(内核分配、驱动) 不可迁移
MIGRATE_MOVABLE 可移动页(用户空间页、文件缓存) 可迁移
MIGRATE_RECLAIMABLE 可回收页(slab缓存、文件系统元数据) 可回收
MIGRATE_HIGHATOMIC 高优先级原子分配预留 特殊
MIGRATE_CMA 连续内存分配器预留 可迁移
MIGRATE_ISOLATE 临时隔离(用于内存热插拔) 隔离中
注意:如果MIGRATE_UNMOVABLE类型的页大量散布在内存中,会严重阻碍大块连续内存的分配。这是系统长时间运行后出现“内存虽多但分配失败”的根本原因之一。

2.6 碎片化监控指标

通过/proc/buddyinfo可以查看各zone各order的空闲块数量,是诊断碎片化问题的第一手数据。

# cat /proc/buddyinfo
Node 0, zone      DMA      1      0      0      1      2      1      1      0      1      1      3
Node 0, zone    DMA32   1234    567     89     45     23     12      8      4      2      1      0
Node 0, zone   Normal   4567   2345    678    234     89     34     12      5      2      1      0

解读方法:从左到右依次为order 0~10的空闲块数量。如果高order(如order≥4)的数值长期为0或很小,说明碎片化严重。

调优建议:当监控发现高order分配频繁失败时,可考虑:
1. 调整vm.extfrag_threshold(默认500)控制碎片避免的激进程度;
2. 启用compact_memory手动触发内存压缩;
3. 使用numactl绑定内存分配策略,减少跨node碎片。

2.7 伙伴系统与kswapd的协作

kswapd在回收内存时,不仅会扫描LRU链表回收页,还会触发内存压缩(compaction)来合并碎片。压缩过程将可移动页迁移到zone的一端,从而在另一端形成连续大块。

kswapd与伙伴系统协作流程 kswapd唤醒 水位低于low 扫描LRU链表 回收可回收页 释放到伙伴系统 尝试合并大块 检查碎片程度 extfrag_threshold 触发内存压缩 迁移可移动页

2.8 关键调优参数

参数 路径 默认值 说明
extfrag_threshold /proc/sys/vm/extfrag_threshold 500 碎片指数阈值,越低越积极压缩
compact_unevictable_allowed /proc/sys/vm/compact_unevictable_allowed 1 是否允许压缩不可回收页
min_free_kbytes /proc/sys/vm/min_free_kbytes 动态计算 保留最小空闲内存,影响碎片避免
watermark_scale_factor /proc/sys/vm/watermark_scale_factor 10 水位线间距缩放因子
实战经验:在数据库服务器或高频交易系统中,建议将extfrag_threshold降低到200~300,并监控/proc/buddyinfo中order≥4的空闲块数量。如果连续出现0值,应考虑手动触发echo 1 > /proc/sys/vm/compact_memory或调整应用程序的内存分配策略。