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_MOVABLEMIGRATE_MOVABLE用户态匿名页、可移动映射
__GFP_RECLAIMABLEMIGRATE_RECLAIMABLEkmem_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 迁移,必须同时满足以下条件:

  1. 必须在线:pfn_valid(pfn) —— 不属于 hole 区域
  2. 必须属于该 zone:page_zonenum(page) == zonenum
  3. 不能被锁定:!PageCompound(page) 或 PG_compound_lock 未持锁
  4. 不能是内核线性映射页:不能是PageKmemcg等"不可移动"的 slab 页
  5. 必须是 LRU:PageLRU(page) 或 __PageMovable(page)
  6. 不能是正在回写/脏页:PageWriteback(page) 和脏映射页特殊处理
  7. 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_eligibleunmap 候选页的碎片阈值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默认 CompactionProactive Compaction
THP fault 成功率34%67%82%
2MB 连续块平均大小287MB1.2GB3.4GB
CMA 分配延迟 (P99)84ms23ms8ms

9.2 开销评估

指标kcompactd CPU 开销页面迁移吞吐量IO 影响
空闲期 proactive< 0.3%12k pages/sec可忽略
负载时 sync1-3%8k pages/sec可能影响 IOPS 2-5%
compact_memory 手动3-8%15k pages/sec可能影响 IOPS 5-10%

9.3 关键结论

  1. Proactive compaction 是有益的:在空闲时执行,几乎无性能开销,能显著降低碎片化
  2. Async compaction 比 Sync 更优:非阻塞模式不影响用户进程延迟
  3. 顺序依赖是关键:compaction_proactiveness 调整可平衡碎片预防与 CPU 开销
  4. 大页需要 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_kbytes auto 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 是一个精密的工程系统,它通过以下机制协同工作来解决外部碎片问题:

  1. MIGRATE_TYPES —— 按迁移能力对页面分类,建立 fallback 链
  2. 双指针对向扫描 —— 在一端找可迁移页,另一端找空闲化目的页
  3. fragmentation_index —— 量化碎片程度,决定是否需要 compact
  4. CMA 协作 —— 允许"借用"预留区域,需要时通过 migration 回收
  5. Proactive Compaction —— kcompactd 主动周期性规整,预防碎片化
  6. Tracepoint & Monitor —— 完整的可观测性支持

生产环境中,合理配置 extfrag_threshold 和 compaction_proactiveness 能显著改善 THP 成功率和大页分配延迟,同时对 CPU 开销影响极小。理解这些底层机制,是 Linux 系统调优和故障排查的关键一步。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部