所有 kmalloc、vmalloc、page cache、anon page 最终都从 buddy 系统拿物理页。buddy 用 2^order 空闲链表,分裂合并 O(1),简单、确定、快。
一、先看图
flowchart TD
REQ[alloc_pages<br/>order=0] --> PCP{per-CPU page<br/>cache 有空闲?}
PCP -->|有| FAST[直接返回<br/>快速路径]
PCP -->|无| BUDDY[buddy free list<br/>order 0..10]
BUDDY --> SPLIT{order 0 有?}
SPLIT -->|有| RET[返回]
SPLIT -->|无| UP[向上找 order 1]
UP --> SPLIT2{order 1 有?}
SPLIT2 -->|有| SPL[拆成 2×order0<br/>返回 1 个]
SPLIT2 -->|无| UP2[继续向上...]
UP2 --> FAIL[直到 order 10<br/>或失败]
classDef fast fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef slow fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class PCP,FAST fast
class UP,UP2,FAIL slow
二、buddy 算法
每个 zone 维护 11 条空闲链表(order 0-10):
order 0: 4KB 页面
order 1: 8KB(2 页连续)
...
order 10: 4MB(1024 页连续)
分配:从请求的 order 链表取;没有 → 从更高 order 取一块、拆开,一半返回一半放回低 order 链表。
释放:放回对应 order 链表;检查 buddy(伙伴块)是否也空闲 → 合并成更高 order → 递归合并。
查看:
cat /proc/buddyinfo
# Node 0, zone Normal 1234 567 321 89 34 12 4 2 1 0 0
# ^o0 ^o1 ^o2 ...三、Zone 划分
ZONE_DMA 0-16MB ISA DMA 设备(x86 遗留)
ZONE_DMA32 0-4GB 32 位 DMA 设备
ZONE_NORMAL 4GB+ 主力
ZONE_MOVABLE 可选 热插拔/CMA 用
ZONE_HIGHMEM 仅 32 位 64 位无
每个 zone 有独立的 buddy 空闲链表和 watermark。
四、gfp flags
分配时传 gfp_t(Get Free Pages flags)告诉
buddy:
__GFP_DMA // 从 DMA zone
__GFP_DMA32 // 从 DMA32 zone
__GFP_HIGHMEM // 允许 HIGHMEM
__GFP_MOVABLE // 可迁移(compaction 友好)
__GFP_IO // 允许触发 I/O(writeback)
__GFP_FS // 允许文件系统操作
__GFP_NOWAIT // 不等待
__GFP_RETRY_MAYFAIL // 重试但可能失败
__GFP_NOFAIL // 绝不失败(死循环重试)
__GFP_ZERO // 零填充常用组合:
GFP_KERNEL = __GFP_RECLAIM | __GFP_IO | __GFP_FS // 进程上下文
GFP_ATOMIC = __GFP_HIGH | __GFP_KSWAPD_RECLAIM // 中断上下文
GFP_USER = GFP_KERNEL | __GFP_HARDWALL // 用户空间
GFP_HIGHUSER_MOVABLE = GFP_USER | __GFP_HIGHMEM | __GFP_MOVABLE五、per-CPU page cache(pcp)
order-0 分配(最频繁)走 pcp 快速路径:
- 每 CPU 缓存
high个页(默认几百) - 分配/释放不碰 buddy 主锁
- batch 模式补货/归还
cat /proc/zoneinfo | grep -A5 "pagesets"
# cpu: 0
# count: 312
# high: 378
# batch: 63pcp 让 order-0 分配几乎无锁。
六、碎片与 compaction
buddy 的弱点:高 order 分配(order≥2)容易因碎片化失败——虽然总空闲页够,但凑不出连续的。
6.1 migrate type
buddy 把页按 migrate type 分组:
- MIGRATE_UNMOVABLE:内核数据结构
- MIGRATE_MOVABLE:用户匿名/文件页(可迁移)
- MIGRATE_RECLAIMABLE:可回收页
分组目的:把可迁移的页聚在一起 → compaction 更高效。
6.2 compaction
compact_zone 扫描:
- 从 zone 低地址找 movable 页
- 从 zone 高地址找空洞
- 把 movable 页迁移到高地址空洞
- 低地址腾出连续大块
触发:
- alloc_pages 高 order 失败时
- kcompactd 后台线程
echo 1 > /proc/sys/vm/compact_memory手动
6.3 CMA
Contiguous Memory Allocator:启动时预留一块 MIGRATE_CMA 区域,平时给 movable 页用,DMA 需要时强制迁移出来。
dmesg | grep cma # 看 CMA 预留大小七、watermark 与分配失败
D-36 讲过 watermark。buddy 分配时检查:
if (free < min + lowmem_reserve)
→ 触发 direct reclaim / OOM
lowmem_reserve_ratio 保护低
zone(DMA/DMA32)不被高 zone 请求耗尽。
八、大型分配的替代
| 需求 | 方法 |
|---|---|
| order-0(4K) | pcp 快速路径 |
| order 1-2(8-16K) | buddy 直接 |
| order 3+(32K+) | 可能碎片化;考虑 vmalloc |
| 连续大物理块 | CMA / HugeTLB |
| 大虚拟连续 | vmalloc |
九、观察与调试
cat /proc/buddyinfo # 各 order 空闲块数
cat /proc/pagetypeinfo # 按 migrate type 细分
cat /proc/vmstat | grep compact # compaction 统计
cat /proc/zoneinfo # watermark + pcp
# 实时
watch -d cat /proc/buddyinfo
# 碎片指数
cat /sys/kernel/debug/extfrag/extfrag_index 2>/dev/null十、小结
- buddy 用 2^order 空闲链表,分裂/合并 O(1)
- pcp 让 order-0 几乎无锁
- zone + gfp flags 约束分配从哪取
- migrate type + compaction 治碎片
- CMA 为 DMA 设备预留连续内存
参考文献
- Gorman, M. “Understanding the Linux Virtual Memory Manager.” §6 “Physical Page Allocation”
mm/page_alloc.cDocumentation/admin-guide/mm/concepts.rst- Vlastimil Babka, “Compaction improvements.” LPC 2018
include/linux/gfp_types.h
工具
/proc/buddyinfo、/proc/pagetypeinfo/proc/zoneinfovmstat、sar -Btrace-cmd+ page_alloc tracepoints
上一篇:HugeTLB 与 THP 下一篇:Slab/SLUB 分配器
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。