2. 伙伴系统:伙伴算法原理、内存碎片化问题、order与pageblock
2.1 伙伴算法核心原理
伙伴系统(Buddy System)是Linux内核管理物理内存分配与回收的基础算法。它将物理内存划分为2的幂次方大小的连续块,以order(阶)为单位进行管理。一个order为n的块包含2n个连续物理页框(page frame)。
核心思想:当分配内存时,将大块逐级对半切分,直到满足请求的最小order;当释放内存时,检查相邻的“伙伴”块是否空闲,若空闲则合并为更大的块,逐级向上合并,以最大限度减少外部碎片。
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%)。
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. 调整
2. 启用
3. 使用
1. 调整
vm.extfrag_threshold(默认500)控制碎片避免的激进程度;2. 启用
compact_memory手动触发内存压缩;3. 使用
numactl绑定内存分配策略,减少跨node碎片。
2.7 伙伴系统与kswapd的协作
kswapd在回收内存时,不仅会扫描LRU链表回收页,还会触发内存压缩(compaction)来合并碎片。压缩过程将可移动页迁移到zone的一端,从而在另一端形成连续大块。
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或调整应用程序的内存分配策略。