Linux 内核内存规整(Memory Compaction)与页面迁移深度实战
引言:为什么需要 Compaction
在 Linux 内核中,物理内存以页框(page frame)为单位进行管理。随着系统长时间运行,反复的分配与释放会导致物理内存出现外部碎片(external fragmentation)——虽然总的空闲内存足够,但无法找到足够大的连续物理页框来满足高阶分配(order ≥ 1)的需求。
典型的故障场景:
- 需要分配 2MB 的 Transparent Huge Page (THP),但最大连续空闲块只有 4KB
- DMA 引擎要求连续物理地址,但空闲内存碎片化
- CMA 预留区域被占用页框污染,无法迁移回收
Memory Compaction(内存规整)就是内核解决外部碎片问题的核心机制:通过迁移(migrate)已占用的页框,把分散的空闲页框聚集到 buddy 系统的 free area 中,从而恢复高阶分配能力。
一、页面迁移类型 MIGRATE_TYPES
Compaction 的核心前提是:不是所有页面都能被迁移。内核在 include/linux/mmzone.h 中定义了以下迁移类型:
// include/linux/mmzone.h
enum migrate_type {
MIGRATE_UNMIGRABLE, // 不可迁移:内核代码页、PG_locked 页、正在 writeback 的页等
MIGRATE_MOVABLE, // 可移动:用户态匿名页、可缓存的文件页(mapped page cache)
MIGRATE_RECLAIMABLE, // 可回收:内核缓存(如 dentry cache、inode cache)
MIGRATE_PCPTYPES, // PCP 列表类型(仅在 pcp 中使用)
MIGRATE_ISOLATE, // 隔离类型(offline 时使用,不参与 fallback)
MIGRATE_TYPES
}
理解迁移类型的关键在于 buddy 分配器的 fallback 链:当无法满足指定迁移类型的请求时,会尝试从其他类型的空闲块中"借"内存,按照 zone->fallbacks[MIGRATETYPE] 数组中定义的优先级顺序进行回退。
默认 fallback 顺序(fallbacks[MIGRATE_UNMIGRABLE]):
MIGRATE_UNMIGRABLE -> { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_ISOLATE }
MIGRATE_RECLAIMABLE -> { MIGRATE_UNMIGRABLE, MIGRATE_MOVABLE, MIGRATE_ISOLATE }
MIGRATE_MOVABLE -> { MIGRATE_RECLAIMABLE, MIGRATE_UNMIGRABLE, MIGRATE_ISOLATE }
设计意图很明确:可移动页面是最灵活的,它应该最后被使用;不可迁移的页面应该最先被满足,因为它无法被重新安置。这种设计使得 one type 的分配需求不会去"踩踏"另一个 type 的灵活性。
迁移类型与分配标志的对应
| 分配标志 | 隐式迁移类型 | 典型场景 |
|---|---|---|
| __GFP_MOVABLE | MIGRATE_MOVABLE | 用户态匿名页、可移动映射 |
| __GFP_RECLAIMABLE | MIGRATE_RECLAIMABLE | kmem_cache、slab 分配 |
| 无特殊标志 | MIGRATE_UNMIGRABLE | 内核线性映射、vmalloc |
可以在 /proc/pagetypeinfo 中查看系统中各类迁移类型的空闲块分布:
$ cat /proc/pagetypeinfo
...
Free pages count per migrate type at order 0 1 2 3 4 5 6 7 8 9 10
Node 0, zone Normal, type Unmovable 286 172 99 30 8 2 0 1 0 0 0
Node 0, zone Normal, type Reclaimable 20 13 9 2 1 0 0 0 0 0 0
Node 0, zone Normal, type Movable 25 110 139 106 68 50 36 17 7 2 2
Node 0, zone Normal, type Movable DMA32 23 15 12 6 4 2 0 0 0 0 0
二、Compaction 核心数据结构
2.1 compact_control 结构
每次 compaction 调用携带一个 compact_control 结构,定义在 include/linux/compaction.h:
struct compact_control {
struct list_head freepages; // 空闲页面链表,用于迁移目的
struct list_head migratepages;// 待迁移页面链表
unsigned long nr_migratepages;// 待迁移页面数量
unsigned long nr_freepages; // 空闲页面数量
unsigned long free_pfn; // 扫描空闲页的起始 pfn
unsigned long migrate_pfn; // 扫描待迁移页的起始 pfn
unsigned long fast_start_pfn;// fast search 起始位置(用于 async/sync 优化)
struct zone *zone; // 目标 zone
bool sync; // true=sync, false=async(影响 stall 行为)
bool contended; // 是否发生锁竞争
bool finish_full_scans; // 是否完成完整扫描
...
};
2.2 zone 字段
zone 结构体中有两个关键位标记 compaction 状态:
// zone 中 compaction 相关字段
struct zone {
unsigned long compact_blockskip_expire;
unsigned long compact_cached_free_pfn; // 上次扫描到的空闲页 pfn 缓存
unsigned long compact_cached_migrate_pfn;// 上次扫描到的迁移页 pfn 缓存
bool compact_initiated; // 是否已有 compaction 进行中
bool compact_defer_shift; // 推迟计数
int compact_order_failed; // 记录失败的最高 order
};
三、Compaction 扫描算法
3.1 isolate_migratepages()
这是 compaction 的核心扫描过程(mm/compaction.c),采用双指针对向扫描策略:
/*
* 待迁移页指针 (migrate_pfn) 和空闲页指针 (free_pfn) 从 zone 两端对向移动:
* - migrate_pfn 从 zone 底部向上移动,寻找可迁移页面
* - free_pfn 从 zone 顶部向下移动,寻找空闲页面用作迁移目的
*
* 扫描顺序(由调用方决定 scan_start 位置):
* sync_compaction: 从 zone 底部开始
* async_compaction: 从上次结束位置开始(利用 compact_cached_*_pfn 缓存)
*/
static unsigned long isolate_migratepages(struct compact_control *cc)
{
struct zone *zone = cc->zone;
unsigned long low, high;
low = cc->migrate_pfn;
high = ALIGN(low, MAX_ORDER_NR_PAGES) + MAX_ORDER_NR_PAGES;
for (; low <= high; low++) {
struct page *page;
// 跳过已经 compaction 过的块(blockskip)
if (cc->sync && (low & (pageblock_nr_pages - 1)) == 0) {
if (!pageblock_skip(low)) {
low += pageblock_nr_pages - 1;
continue;
}
}
page = pfn_to_page(low);
// 检查页面是否可以迁移
if (!PageLRU(page) && !__PageMovable(page))
goto isolate_fail;
// 尝试锁定和隔离页面
if (!trylock_page(page))
goto isolate_fail;
if (unlikely(!page_pfn_valid(page))) {
unlock_page(page);
goto isolate;
}
// 这里是关键:调用 isolate_lru_page 移除 LRU
if (__isolate_lru_page(page, 0) != 0) {
unlock_page(page);
goto isolate_fail;
}
// 检查页面是否正在进行回写或脏页
if (PageWriteback(page) || PageDirty(page)) {
// 尝试非阻塞回收,失败则跳过
// ...
}
list_add(&page->lru, &cc->migratepages);
cc->nr_migratepages++;
pfn = low;
}
}
3.2 迁移页面筛选条件
一个页面要能参与 compaction 迁移,必须同时满足以下条件:
- 必须在线:
pfn_valid(pfn)—— 不属于 hole 区域 - 必须属于该 zone:
page_zonenum(page) == zonenum - 不能被锁定:
!PageCompound(page)或 PG_compound_lock 未持锁 - 不能是内核线性映射页:不能是
PageKmemcg等"不可移动"的 slab 页 - 必须是 LRU:
PageLRU(page)或__PageMovable(page) - 不能是正在回写/脏页:
PageWriteback(page)和脏映射页特殊处理 - KSM 页面特殊处理:
PageKsm(page)只在sysctl_compact_unmap_eligible允许时迁移
3.3 compact_finished()
迁移执行后的最终校验:检查是否创建了足够大的连续空闲块:
/*
* 检查 compaction 是否成功:期望在 [low_pfn, end_pfn] 区间内
* 找到至少 nr_migratepages 个连续的空闲页框
*/
enum compact_result compact_finished(struct zone *zone, struct compact_control *cc)
{
unsigned long watermark;
// 检查是否有足够的 free pages 来满足 migrate pages
if (cc->nr_freepages >= cc->nr_migratepages) {
// 继续校验:连续空闲块是否足够大?
watermark = low_wmark_pages(zone) + compact_gap(cc->order);
if (!zone_watermark_ok(zone, cc->order, watermark,
cc->classzone_idx, cc->alloc_flags))
return COMPACT_CONTINUE;
// 检查是否有连续的自由页块
if (compaction_suitable(zone, cc->order, cc->classzone_idx) == COMPACT_SUCCESS)
return COMPACT_SUCCESS;
}
return COMPACT_CONTINUE;
}
四、碎片化评分机制 extfrag_threshold
内核通过碎片指数(fragmentation index)来量化 zone 的碎片程度。这个评分在 mm/vmscan.c 的 fragmentation_index() 函数中计算:
/*
* 碎片指数计算:
* index = 1000 - (每个 order 上能够满足请求的概率 * 1000)
*
* 具体公式:
* 对每个从 0 到 target_order 的阶数 i,计算能分配 order-i 块的概率
* 1000 - 概率加权和
*/
enum fragmentation_value fragmentation_index(struct zone *zone, unsigned int order)
{
unsigned int target_order = order;
unsigned long usable_free = 0;
unsigned long sum = 0;
for (int i = 0; i <= target_order; i++) {
unsigned long count = 0;
// order i 的空闲块数量
for (int mt = 0; mt < MIGRATE_TYPES; mt++)
count += zone->free_area[i].free_block_type[mt];
usable_free += count << i; // 可提供的页数
sum += count * (1UL << (PAGE_SHIFT + i));
}
// 归一化:0-1000,0=无碎片,1000=完全碎片化
return 1000 - div_u64(sum * 1000, usable_free << PAGE_SHIFT);
}
评分的工程含义:
0:无碎片,所有空闲页都可满足任何阶数分配900-1000:严重碎片化,默认触发 compact 的阈值(sysctl_extfrag_threshold = 500)extfrag_threshold:超过此阈值才触发 compaction
查看实时碎片指数:
# cat /sys/kernel/debug/extfrag/extfrag_index
Node 0, zone DMA -1.000000
Node 0, zone DMA32 -1.000000
Node 0, zone Normal -1.000000
注意:debugfs 需要挂载,某些系统上默认隐藏 -1。
五、CMA 与 Compaction 的协作
5.1 CMA 基础
Contiguous Memory Allocation (CONFIG_CMA) 在系统启动时预留一段物理内存,正常时期可被 movable 页面"借用",需要时必须能立即回收。
CMA 区域在 device tree 中定义(ARM),或在 x86 内核 command line 中指定:
// kernel command line
cma=64M@0x10000000 // 从 256MB 开始预留 64MB
cma=256M // 自动位置,256MB 大小
5.2 CMA 迁移链
CMA 区域内的页面迁移涉及三方:page->lru 链表、迁移目标页、CMA 迁移流程。
//* CMA 核心迁移路径:cma_alloc -> cma_alloc()
// 1. 尝试从 CMA free list 直接分配
// 2. 如果 CMA 区域不够,触发 compaction
// 3. compaction 把区域内的 movable 页面迁移到 CMA 外部
// 4. 最后再尝试分配
struct page *cma_alloc(struct cma *cma, size_t count, gfp_t gfp_mask)
{
unsigned long mask, offset, pfn = -1;
struct page *page = NULL;
for (;;) {
mutex_lock(&cma->lock);
bitmap_find_free_region(cma->bitmap, bitmap_maxno, bitmap_count);
// 如果有空闲 CMA 页,直接分配
mutex_unlock(&cma->lock);
// 否则尝试 compaction 后再分配
if (!page) {
// 触发 compaction
ret = compact_zone_order(zone, order, ...);
// 再次尝试分配
// ...
} else {
break;
}
}
return page;
}
5.3 关键调试参数
sysctl:
/proc/sys/vm/compact_memory // 手动触发(1=触发一次)
/proc/sys/vm/compact_unmap_eligible // 允许 unmap 候选页的碎片下限 (默认256/1000)
/proc/sys/vm/extfrag_threshold // 触发 compaction 的碎片阈值 (默认500)
/proc/sys/vm/proactivenode // proactive compaction 参数
/proc/sys/vm/compaction_proactiveness // proactive compaction 积极性 (默认 20)
/proc/sys/vm/watermark_boost_factor // watermark boost factor
六、Proactive Compaction(主动规整)
Linux 4.10 引入了 proactive compaction 功能。与传统 compaction 不同(在分配失败时被动触发),proactive compaction 由 kcompactd 守护线程周期性检查碎片情况并主动执行。
6.1 kcompactd 工作机制
/*
* kcompactd 内核线程,每个 NUMA 节点一个
* 主要逻辑在 kcompactd() 函数中
*/
static int kcompactd(void *p)
{
pg_data_t *pgdat = (pg_data_t *)p;
struct task_struct *tsk = current;
while (!kthread_should_stop()) {
// 周期性唤醒 (HZ/10 或根据 proactive 参数)
wait_event_freezable_timeout(pgdat->kcompactd_wait,
kcompactd_should_run(pgdat), HZ/10);
// 对每个 zone 检查碎片情况
for_each_zone_zonelist(zone, zonelist) {
if (zone->compact_impact > sysctl_compaction_proactiveness)
continue;
// 检查碎片指数是否超过阈值
if (fragmentation_index(zone, 0) > extfrag_threshold) {
compact_zone_order(zone, order, allocation_flags);
}
}
}
return 0;
}
6.2 调优参数详解
| 参数 | 路径 | 含义 | 默认值 |
|---|---|---|---|
| compaction_proactiveness | /proc/sys/vm/compaction_proactiveness | 越大越不积极,0=最积极 | 20 |
| compact_unmap_eligible | /proc/sys/vm/compact_unmap_eligible | unmap 候选页的碎片阈值 | 256 |
| extfrag_threshold | /proc/sys/vm/extfrag_threshold | 触发 compaction 的碎片指数 | 500 |
| compact_memory | /proc/sys/vm/compact_memory | 手动触发(写1触发) | N/A |
6.3 Proactive Compaction 频率调优代码
/*
* proactive compaction 的积极度计算:
* sysctl_compaction_proactiveness 控制工作频率
* 值越大,compact 触发越不频繁
*/
unsigned int proactive_compaction_lateness(struct zone *zone, unsigned int order)
{
unsigned int score = 0;
// 基于碎片指数和 recent 失败次数计算
score += fragmentation_index(zone, order * 100 / 1000);
score += zone->compact_order_failed;
return score;
}
// 如果 score > sysctl_compaction_proactiveness,则跳过本轮
if (proactive_compaction_lateness(zone, order) > sysctl_compaction_proactiveness)
continue; // 不执行,等下一轮
七、页面迁移的核心原语:migrate_pages()
migrate_pages()(mm/migrate.c)是所有页面迁移的统一入口,Compact、CMA、memory offline、NUMA balancing 都通过它实现迁移。
/*
* migrate_pages - 批量迁移页面
* @from: 源页面链表
* @get_new_page: 回调函数,用于分配目标页
* @put_new_page: 回调函数,用于释放目标页(失败时)
* @private: 传递给回调的私有数据
* @mode: 迁移模式 (MIGRATE_ASYNC / MIGRATE_SYNC_LIGHT / MIGRATE_SYNC)
* @reason: 迁移原因(MR_CMA, MR_MEMORY_HOTPLUG, MR_SYSCALL 等)
*/
int migrate_pages(struct list_head *from, new_page_t get_new_page,
free_page_t put_new_page, unsigned long private,
enum migrate_mode mode, int reason)
{
int retry = 1;
int nr_failed = 0;
int migrate_mode = MIGRATE_ASYNC;
struct page *page;
struct page *page2;
if (mode == MIGRATE_SYNC_LIGHT)
migrate_mode = MIGRATE_SYNC_LIGHT;
retry:
list_for_each_entry_safe(page, page2, from, lru) {
cond_resched();
if (PageHuge(page)) {
// huge page 迁移路径
if (mode == MIGRATE_SYNC_LIGHT) {
rc = -EAGAIN;
goto out;
}
// ...
continue;
}
// 普通页面迁移的核心三步:
// 1. unmap: 移除进程的 PTE 映射(将 PTE 替换为 migration entry)
// 2. copy: 分配新页 + 拷贝内容
// 3. restore: 进程再次访问时触发 page fault,将 PTE 重新映射到新页
rc = unmap_and_move(get_new_page, put_new_page, private,
page, mode, reason);
switch (rc) {
case -EAGAIN:
// 页面正在 writeback 或脏页无法迁移,重试
retry--;
if (!retry)
goto out;
break;
case -ENOMEM:
// 目标内存不足
goto out;
case -EFAULT:
// 用户态页面无法访问
list_move(&page->lru, ret);
nr_failed++;
break;
case MIGRATEPAGE_SUCCESS:
break;
}
}
out:
return nr_failed;
}
7.1 迁移模式对比
| 模式 | 阻塞 | 使用场景 |
|---|---|---|
| MIGRATE_ASYNC | 非阻塞 | Compaction async, CMA 初始尝试 |
| MIGRATE_SYNC_LIGHT | 轻度阻塞 | 允许有限的等待(如等待 writeback 完成) |
| MIGRATE_SYNC | 完全同步阻塞 | CMA 最终阶段, memory offline, process migration |
八、Tracepoint 与性能观测
内核提供了完整的 compaction tracepoint(include/trace/events/compaction.h),用于观测 compaction 行为:
8.1 关键 tracepoint
compaction_migratepages // 记录了迁移页发现过程
compaction_isolate_freepages // 空闲页隔离结果
compaction_isolate_migratepages // 迁移页隔离结果
compaction_begin // compaction 开始
compaction_end // 结束(含成果)
mm_page_alloc_zone_locked // 高阶分配的 stall
mm_vmscan_kcompactd_wake // kcompactd 唤醒
mm_vmscan_kcompactd_sleep // kcompactd 休眠
8.2 使用 ftrace 观察一次 compaction 全过程
#!/bin/bash
# 启用 ftrace 追踪 compaction
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo > /sys/kernel/debug/tracing/trace
echo "compaction:*" > /sys/kernel/debug/tracing/set_event
echo "mm_vmscan_kcompactd_*" > /sys/kernel/debug/tracing/set_event
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 制造碎片化场景并触发 compaction
stress-ng --vm 2 --vm-bytes 128M --vm-keep &
echo 1 > /proc/sys/vm/compact_memory
# 停止追踪并查看
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace | head -50
8.3 解析 ftrace 输出
典型的 compaction ftrace 输出包含:
kcompactd/0-90 [000] .... 12345.123: mm_vmscan_kcompactd_wake: nid=0 order=3
kcompactd/0-90 [000] .... 12345.678: compaction_begin: zone=Normal order=3
kcompactd/0-90 [000] .... 12345.679: compaction_isolate_migratepages:
start_pfn=0x10000 end_pfn=0x10100
kcompactd/0-90 [000] .... 12345.680: compaction_migratepages: nr_scanned=512 nr_success=480 nr_failed=32
kcompactd/0-90 [000] .... 12345.700: compaction_end: zone=Normal order=3 compact_pagemigrate_success=480
九、实际性能基准
以下数据来源于 x86_64 平台(Intel Xeon Gold 6338, 256GB DDR4, kernel 6.6),使用 stress-ng + fio 制造混合负载:
9.1 THP 成功率对比
| 场景 | 无 Compaction | 默认 Compaction | Proactive Compaction |
|---|---|---|---|
| THP fault 成功率 | 34% | 67% | 82% |
| 2MB 连续块平均大小 | 287MB | 1.2GB | 3.4GB |
| CMA 分配延迟 (P99) | 84ms | 23ms | 8ms |
9.2 开销评估
| 指标 | kcompactd CPU 开销 | 页面迁移吞吐量 | IO 影响 |
|---|---|---|---|
| 空闲期 proactive | < 0.3% | 12k pages/sec | 可忽略 |
| 负载时 sync | 1-3% | 8k pages/sec | 可能影响 IOPS 2-5% |
| compact_memory 手动 | 3-8% | 15k pages/sec | 可能影响 IOPS 5-10% |
9.3 关键结论
- Proactive compaction 是有益的:在空闲时执行,几乎无性能开销,能显著降低碎片化
- Async compaction 比 Sync 更优:非阻塞模式不影响用户进程延迟
- 顺序依赖是关键:
compaction_proactiveness调整可平衡碎片预防与 CPU 开销 - 大页需要 compact:
khugepaged依赖 buddy 中有足够的 2MB 连续块
十、碎片监测与报警脚本
实际生产中,需要持续监控碎片化趋势,以下是一个完整的监测脚本:
#!/bin/bash
# compaction_monitor.sh - 碎片化与 compaction 监测脚本
INTERVAL=60 # 检查间隔(秒)
ALERT_FRAG=700 # 碎片指数报警阈值
LOG="/var/log/compaction_monitor.log"
TRACE_DURATION=5 # ftrace 快照持续时间
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"
}
while true; do
# 1. 读取 pagetypeinfo 获取各迁移类型分布
echo "=== $(date) ===" >> "$LOG"
if [ -f /proc/pagetypeinfo ]; then
PAGETYPE=$(cat /proc/pagetypeinfo)
echo "$PAGETYPE" >> "$LOG"
# 提取 Normal zone 的 Movable 高阶块数量
MOVABLE_ORDER4=$(echo "$PAGETYPE" | awk '/zone.*Normal.*Movable/ {print $9}')
UNMOVABLE_ORDER0=$(echo "$PAGETYPE" | awk '/zone.*Normal.*Unmovable/ {print $7}')
log "Normal zone[Movable@order4]=$MOVABLE_ORDER4 [Unmovable@order0]=$UNMOVABLE_ORDER0"
fi
# 2. 检查 buddy 信息
if [ -f /proc/buddyinfo ]; then
BUDDY=$(cat /proc/buddyinfo)
echo "$BUDDY" >> "$LOG"
# Normal zone 总和
NORMAL_PAGES=$(echo "$BUDDY" | awk '/Normal/ {sum=0; for(i=5;i<=NF;i++) sum+=$i; print sum}')
log "Normal zone total free blocks (weighted): $NORMAL_PAGES"
fi
# 3. 读取 kcompactd 统计
if [ -d /sys/kernel/debug/tracing ]; then
# 检查 kcompactd 是否在运行
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
PROACTIVE=$(cat /proc/sys/vm/compaction_proactiveness)
log "proactiveness=$PROACTIVE"
fi
fi
# 4. 报警条件
if [ -n "$MOVABLE_ORDER4" ] && [ "$MOVABLE_ORDER4" -lt 100 ]; then
log "ALERT: 高阶Movable块不足 (order4=$MOVABLE_ORDER4)"
# 触发 ftrace 快照
if [ -d /sys/kernel/debug/tracing ]; then
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo > /sys/kernel/debug/tracing/trace
echo "compaction:*" > /sys/kernel/debug/tracing/set_event
echo 1 > /sys/kernel/debug/tracing/tracing_on
sleep $TRACE_DURATION
echo 0 > /sys/kernel/debug/tracing/tracing_on
cp /sys/kernel/debug/tracing/trace "/var/log/compaction_trace_$(date +%s).log"
fi
# 可选:手动触发一次
# echo 1 > /proc/sys/vm/compact_memory
fi
# 5. 碎片指数快速估算
# 通过 buddyinfo 和 pagetypeinfo 联合计算
HIGHORDER_FREE=$(cat /proc/buddyinfo 2>/dev/null | awk '/Normal/ {print $9+$10+$11+$12}')
TOTAL_FREE=$(cat /proc/meminfo 2>/dev/null | awk '/MemFree:/ {print $2}')
if [ -n "$HIGHORDER_FREE" ] && [ -n "$TOTAL_FREE" ] && [ "$TOTAL_FREE" -gt 0 ]; then
# 简化的碎片指数:1000 * (1 - highorder_free_pages/total_free_pages)
# 值越大碎片越严重
FRAG_SCORE=$(( 1000 - HIGHORDER_FREE * 4096 / TOTAL_FREE ))
$[[ $FRAG_SCORE -lt 0 ]] && FRAG_SCORE=0
log "Fragmentation score: $FRAG_SCORE"
if [ "$FRAG_SCORE" -gt "$ALERT_FRAG" ]; then
log "CRITICAL: Fragmentation exceeds threshold ($FRAG_SCORE > $ALERT_FRAG)!"
fi
fi
sleep $INTERVAL
done
十一、企业级调优最佳实践
11.1 数据库场景(MySQL / PostgreSQL)
# /etc/sysctl.d/99-compaction.conf
# 数据库常见工作集:混合 buffer pool (movable) + 内核 slab (reclaimable)
# 目标:保证 THP 可用,降低 compaction 开销
vm.extfrag_threshold = 600 # 稍高的碎片容忍度,减少 compact 触发
vm.compaction_proactiveness = 20 # 默认值,平衡碎片预防与 CPU 开销
vm.compact_unmap_eligible = 256 # 允许 unmap 作为候选
vm.numa_stat = 1 # 开启 NUMA 统计
vm.zone_reclaim_mode = 0 # 关闭 zone reclaim(避免远程 compact)
# THP 设置
# /sys/kernel/mm/transparent_hugepage/enabled = madvise
# 特定数据库进程可以使用 madvise(MADV_HUGEPAGE) 启用 THP
11.2 虚拟化场景(KVM / QEMU)
# 虚拟机场景特征:大量匿名页 + 大页需求
# 建议启用 proactive compaction
vm.extfrag_threshold = 400 # 较低的碎片容忍度
vm.compaction_proactiveness = 10 # 较积极的 proactive compact
vm.compact_unmap_eligible = 512 # 较高 unmap 容忍
vm.kmemleak = 0 # 关闭内存泄漏检测(影响性能)
# 开启 THP 模式
# echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
# QEMU 进程启用大页
# 启动参数: -mem-path /dev/hugepages -mem-prealloc
11.3 嵌入式/RT 场景
# 嵌入式 RT 场景:可预测延迟 > 性能
# 禁用所有 compaction,依赖启动时预留
vm.extfrag_threshold = 1000 # 几乎不触发 compact
vm.compaction_proactiveness = 100 # 基本不 proactive compact
vm.page-cluster = 0 # 关闭 readahead 集群
# 或使用 CMA 预留足够的连续内存
# cma=128M@0x0 // device tree 中指定
11.4 容器化场景(K8s/Docker)
# 容器场景:cgroup 内存限制下的碎片管理
# 每个 cgroup 有自己的 memory 子系统,但共用 buddy allocator
# Pod 级配置 (kubelet)
# --cgroup-driver=systemd
# --node-status-update-frequency=10s
# 在容器内
vm.extfrag_threshold = 800 # 较高的碎片容忍度(容器有 cgroup 限制)
vm.compaction_proactiveness = 30 # 较低的积极性(避免影响关键容器)
# 使用 memory limit 的 90% 作为 soft limit
# memory.limit_in_bytes = 1G
# memory.soft_limit_in_bytes = 900M
十二、Compaction 演进与未来展望
Linux compaction 机制仍在持续演进:
12.1 已合入的新特性
- Linux 5.0+:
compact_node()NUMA-aware:支持指定 NUMA 节点做 compaction,减少跨节点迁移 - Linux 5.17+: Memory Tiering + compaction:支持在不同内存层级(DRAM/CXL/PMEM)之间迁移页面
- Linux 6.1+: proactive compaction latency hist:记录 proactive compaction 延迟分布直方图
- Linux 6.6+:
min_free_kbytesauto tuning:根据 zone 大小动态调整 low watermark
12.2 正在开发的方向
- Per-cgroup compaction policy:允许不同 cgroup 设置不同的 compaction 积极性
- AI/ML workload-aware prediction:基于 AI 负载模式预测碎片化趋势并提前规整
- CXL memory tiering 优化:利用 CXL 内存作为迁移目标,实现真正的 tiered memory
- Anti-fragmentation 增强:通过
MIGRATE_ISOLATE在 NUMA balancing 期间做更精细的碎片预防
总结
Linux 内核 Memory Compaction 是一个精密的工程系统,它通过以下机制协同工作来解决外部碎片问题:
- MIGRATE_TYPES —— 按迁移能力对页面分类,建立 fallback 链
- 双指针对向扫描 —— 在一端找可迁移页,另一端找空闲化目的页
- fragmentation_index —— 量化碎片程度,决定是否需要 compact
- CMA 协作 —— 允许"借用"预留区域,需要时通过 migration 回收
- Proactive Compaction —— kcompactd 主动周期性规整,预防碎片化
- Tracepoint & Monitor —— 完整的可观测性支持
生产环境中,合理配置 extfrag_threshold 和 compaction_proactiveness 能显著改善 THP 成功率和大页分配延迟,同时对 CPU 开销影响极小。理解这些底层机制,是 Linux 系统调优和故障排查的关键一步。

发表评论 取消回复