引言

在 Linux 服务器的运行过程中,一个肉眼看不见的"慢性病"正在缓慢吞噬性能:内存碎片化。随着系统持续分配和释放内存,原本连续的物理页面逐渐被打散——当数据库引擎需要 2MB 大页时,内核却发现找不到足够大的连续物理块;当内核自身需要分配不可移动的数据结构时,大块请求因碎片化而失败。更致命的是,碎片化不仅导致分配失败,还拖慢内存回收、升高 Compaction 开销、恶化 NUMA 局部性,最终在高负载场景下表现为偶发性的卡顿或 OOM。

Linux 通过 Buddy 分配器的 MIGRATE_MOVABLE / MIGRATE_RECLAIMABLE 页面迁移类型 + Memory Compaction(内存碎片整理) + anti-fragmentation(反碎片)策略 三位一体,解决这个工程难题。本文将从 Buddy 系统的碎片问题根源出发,深入剖析内核碎片整理算法、页面可迁移分类、直接压缩与主动压缩策略、khugepaged 的透明大页合并、以及生产环境调优方法论。

1. 内存碎片的本质:Buddy 系统的阿喀琉斯之踵

1.1 Buddy 分配器回顾

Buddy 系统按 2 的幂次组织页面:order-0(4KB)、order-1(8KB)、...、order-10(4MB)。当请求分配 order-n 块时,系统在 free_list[n] 中查找;若不存在,则从更高阶分裂:

分配 order-3 块(32KB):
free_list[3] 为空 → 从 free_list[4] 取一个 64KB 块
→ 分裂为两个 32KB 块 → 一个分配,另一个放入 free_list[3]

若 free_list[4] 也为空,继续从 free_list[5] 分裂...
直到找到足够高阶的空闲块。

1.2 外部碎片 vs 内部碎片

内部碎片:分配的块比需求大(向上取整到 2^n)
例:请求 5KB → 分配 order-1 8KB → 浪费 3KB(平均浪费 25%)

外部碎片:空闲内存总量足够,但分布在不连续的小块中
例:系统空闲 4GB,但最大的连续块只有 64KB
→ 需要 2MB 连续物理块的请求全部失败

外部碎片是真正的系统级问题 —— 它让充足的内存无法被使用。

1.3 外部碎片化的进程模拟

初始状态:256MB 连续空闲块

Step 1: 分配 32MB(order-13)用于数据结构 A
剩余:224MB 两个 112MB 块 → 但连续区域被打断

Step 2: 每间隔 32MB 分配 16MB 小块,共 10 次
占用:离散的 16MB 小块散布在 256MB 区域中

Step 3: 释放偶数位置的小块(5 × 16MB = 80MB 空闲)
空闲内存散布在已分配块之间 —— 最大连续空闲块仅 16MB

Step 4: 请求 64MB 连续块
失败!虽然空闲 80MB,但最大连续只有 16MB

2. 反碎片化基石:页面可迁移类型

2.1 单个 Zone 内的 MIGRATE 类型

从 2.6.24 内核开始,Linux 将每个 Zone 的空闲列表按照 页面迁移类型 拆分为独立链表。核心思想:相同用途的页面聚集在一起,减少互相牵制的碎片。

// include/linux/mmzone.h 中的迁移类型
enum migratetype {
    MIGRATE_UNMOVABLE,   // 不可移动:内核对象、页表、slab、内核栈
    MIGRATE_MOVABLE,     // 可移动:用户页面、缓冲区、page cache
    MIGRATE_RECLAIMABLE, // 可回收:无用户映射但有后备的页面
    MIGRATE_PCPTYPES,    // Per-CPU pageset(仅 PCP 列表使用)
    MIGRATE_HIGHATOMIC,  // 紧急预留(不可移动专用)
    MIGRATE_ISOLATE,     // 离线隔离(热插拔专用)
    MIGRATE_TYPES
};

核心设计哲学:
┌──────────────────────────────────────────┐
│ 不可移动页面集中在一个区域                   │
│    ↓                                      │
│ 这个区域的碎片只会影响不可移动分配            │
│    ↓                                      │
│ 可移动页面区域不受不可移动页面的"钉子户"牵制  │
│    ↓                                      │
│ 移动可移动页面即可整理出大块空闲              │
└──────────────────────────────────────────┘

2.2 分配时的 MIGRATE 类型映射

GFP 标志对应的 MIGRATE 类型典型分配者
GFP_KERNELMIGRATE_UNMOVABLEkmalloc、kmem_cache_alloc、内核栈、页表
GFP_HIGHUSER_MOVABLEMIGRATE_MOVABLE用户匿名页面、tmpfs
GFP_USER / page cacheMIGRATE_MOVABLE 或 RECLAIMABLE文件 page cache、共享内存
GFP_NOIOMIGRATE_UNMOVABLE块层 bio 分配(禁止回写 IO)
GFP_ATOMICMIGRATE_HIGHATOMIC 或从 PCP中断上下文、持有自旋锁时

2.3 分配回退策略(Fallbacks)

// mm/page_alloc.c 中的 fallback 顺序
static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
    [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, ... },
    [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE, ... },
    [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, ... },
};

不可移动分配的"尽量策略":
1. 优先从 UNMOVABLE 链表分配
2. 不足时回退到 RECLAIMABLE(可回收页面被回收后可腾出空间)
3. 最后回退到 MOVABLE(最充裕的区域)

这种"单向回退"确保:
- 优先用碎片最少的区域
- 可回收区域首先被回收利用
- 最终大量消耗可移动区域(但可以移动来整理的区域)

3. Memory Compaction 核心算法

3.1 碎片整理的目标

将已分配页面移出目标区域,使空闲页面形成大块连续块:

Compaction 前:
|-----------|###########|-----------|###########|-----------|
  free 8K    移动匿名页   free 8K    不可移动内核   free 8K

Compaction 后:
|-------------------------------|###########|---------------------|
   24K 连续空闲                  不可移动内核   (原 8K + 迁移目标页释放)

核心流程:
1. 隔离目标区域内页面(isolate_migrateblock)
2. 扫描器分为"迁移源"和"空闲目标"双指针
3. 源扫描器找可移动/可回收页面
4. 目标扫描器找空闲块
5. 将页面逐页迁移过去
6. 修正页表映射(虚拟地址不变,物理地址改变)

3.2 碎片指数(Fragmentation Index)

内核用碎片指数判断 Compaction 是否值得做:

// mm/page_alloc.c
// 碎片指数范围:-1000 ~ 1000
//   -1000 = 最佳(碎片整理对任何大小的请求都没有用)
//      0  = 不确定
//  +1000 = 最有可能成功(针对请求的 order)

碎片指数计算逻辑:
对于每个 order(从 0 到 10):
  free_blocks[order] = order 阶空闲块数量
  free_pages = 总的空闲页面数

  对每个请求大小 s 的 order o:
    如果 free_blocks[o] > 0:
      碎片指数[o] -= 1000 / (1 << o)   // 足够大块 → 负值
    否则:
      从低阶空闲碎片累积中检查是否足够完成任务
      如果能满足请求但分布分散 → 碎片指数[o] 增大

选择合适的 Compaction 目标 order:
  从请求的 order 开始向上搜索,找到碎片指数最高的 order
  → 效率最高的压缩目标

3.3 Compaction 双扫描器算法

// mm/compact.c 核心数据结构
struct compact_control {
    struct list_head freepages;     // 空闲页面链表
    struct list_head migratepages;  // 待迁移页面链表
    unsigned long nr_migratepages;  // 已隔离的迁移候选数
    unsigned long nr_freepages;     // 已隔离的空闲目标数
    unsigned long free_pfn;         // 空闲页面扫描器(find-free)
    unsigned long migrate_pfn;      // 迁移页面扫描器(isolate-migrate)
    unsigned long fast_start_pfn;   // 快速扫描的起点
    ...
};

扫描器初始位置选择:
- find-free 扫描器:从区域低地址开始(向右扫描)
- isolate-migrate 扫描器:从区域高地址开始(向左扫描)
- 二者交错但不重叠:防止同页面既作源又作目标

每轮迭代:
Step A: isolate_freepages() —— 收集一批空闲页面到 freepages 列表
Step B: isolate_migratepages() —— 收集一批可移动页面到 migratepages 列表
Step C: migrate_pages() —— 执行页面迁移:
   for page in migratepages:
     unmap_page(page)           // 去除所有页表映射(触发 #PF)
     move_to_new_page(page)     // 将内容拷贝到新页面
     update_mapping(page)       // 更新所有页表指向新物理地址

终止条件:
- migrate_pfn <= free_pfn(二指针相遇)
- 或已迁移足够多的页面满足目标

3.4 快速扫描 vs 慢速扫描

第一次尝试(快速扫描 / fast compaction):
- 跳过已被标记为 PageBuddy=false 且跳过成本低的页面
- 在 ZONE_MOVABLE 上执行(如果配置)
- 忽略 skip 范围标记(后续扫描会 learns)
- 失败 → 进入第二次尝试

第二次尝试(慢速扫描 / slow compaction):
- 重新初始化扫描器
- 不再跳过 —— 逐页检查
- 检查范围扩展到整个 zone

优化点:
if (cc->order <= PAGE_ALLOC_COSTLY_ORDER)
    // 小 order 请求值得更深入的 compaction
    cc->compact_blockskip_flush = true;

4. 页面迁移的核心机制

4.1 迁移条目(Migration Entry)

页面迁移的本质是:临时用一个迁移 PTE 条目替换正常 PTE。当 CPU 访问这个 PTE 时触发 page fault,然后在 fault handler 中完成实际拷贝。

// include/linux/swapops.h
typedef struct {
    swp_entry_t val;
} pte_t;

// 迁移条目的编码格式(swp_entry_t)
// bit 0:  =0 表示迁移条目
// bit 1-7: 迁移类型(普通迁移 = 0)
// bit 8-63: 旧物理页帧号(page frame number)

创建迁移 PTE:
  migration_entry = make_migration_entry(old_page, 0);
  ptep = get_pte(mm, vaddr);
  set_pte(ptep, migration_entry);

CPU 访问迁移 PTE 时:
  do_page_fault()
  → pte_present(pte) = false  // 迁移条目不属于"present" PTE
  → pte_none(pte) = false     // 也不是空 PTE
  → pte_file(pte) = false
  → 最终进入 migration 路径:do_swap_page() 的子分支
    → 从 migration entry 中提取 old_page
    → 读取新页面 already allocated by compaction scanner
    → copy page content
    → update_pte(pte, new_page) // 恢复为正常 PTE
    → wake_up page waiters

4.2 不可移动页面的处理

在 isolate_migratepages() 中,逐页检查可迁移性:

不可迁移的情况:
1. PageLRU(page) = false —— 不在 LRU 链表(内核 slab 等)
2. PageSwapCache(page) —— 在 swap cache 中
3. page_mapcount(page) > expected_mapcount —— 被多个进程共享
4. PageHuge / PageCompound —— 大页(除非配置 THP migration)
5. PageOffline —— 离线页面
6. PagePoisoned —— 有毒页面
7. 持有反向映射锁(anon_vma_lock)被阻塞

对于共享页面(>1个 mapcount):
- 如果所有映射者都是可重映射的(有 anon_vma / i_mmap)→ 可以迁
- 如果有 KSM 页面 → 跳过(KSM 有自己的红黑树)
- 如果有文件映射且文件系统不支持迁移 → 跳过

5. Compaction 的三种触发模式

5.1 直接 Compaction(Direct Compaction)

在 __alloc_pages() 无法满足分配请求时触发:

分配路径中的 Compaction 触发点:

__alloc_pages()
→ get_page_from_freelist()    // 尝试从直接空闲列表分配
   - 失败 → 
→ __alloc_pages_may_oom()    // 检查是否需要 OOM
→ __alloc_pages_direct_reclaim()  // 回收尝试
   - 失败 →
→ __alloc_pages_direct_compact()  // 直接碎片整理
   → compact_zone()
   → migrate_pages()
   → retry alloc

触发条件:
1. 请求的 order ≥ THPAGE 阈值(通常 order ≥ 3 / 32KB)
2. 分区碎片指数 > 0(碎片整理可能有效)
3. GFP 标志允许 DIRECT_RECLAIM(允许同步 IO)
4. compaction_defer 次数未超过阈值

典型场景:
- 数据库分配 2MB 连续 buffer pool 段
- DMA 引擎需要大块连续物理内存
- 内核模块预分配大块环形缓冲区

5.2 主动 Compaction(Proactive Compaction)

由内核后台线程周期性触发,在碎片恶化前提前治理:

// mm/compact.c
// 触发入口: echo 1 > /proc/sys/vm/compact_memory(一次性)
// 或通过 compaction_proactiveness 参数自动触发

void proactive_compaction(pg_data_t *pgdat)
{
    for each order from 0 to MAX_ORDER:
        fragmentation = fragmentation_score_zone_order(zone, order)
        if fragmentation >= COMPACT_THRESHOLD:
            // 值得做 compaction
            cc.order = order
            compact_zone(zone, &cc)

comapaction_proactiveness(新增于 Linux 6.7):
- 替代旧的 compact_memory 和完美反碎片目标
- 值域 0-100:表示 proactive compaction 的积极性
- 0 = 不主动压缩
- 100 = 碎片指数 1 也压缩
- 默认值 20(保守主动)

定义:需要碎片指数达到 (100 - proactiveness) * 10 才触发
即 proactiveness=20 时,碎片指数需 ≥ 800 才主动压缩

3.3 kswapd 驱动 Compaction

kswapd 在内存回收时,如果无法释放足够页面,会尝试 Compaction 来整理碎片:

kswapd 驱动逻辑:
shrink_node()
→ shrink_lruvec()         // 回收 LRU 列表
   - 释放页面 → 可能放到 unmovable 区域
→ shrink_slab()
   - 释放 slab 页面 → 仍进入 unmovable zone
→ try_to_compact_pages()  // 如果回收后仍处于高水位

但在高内存压力场景下:
- kswapd 释放的页面通常已在不可移动区域
- 释放后仍无法形成大块连续块
- 此时 compact 也没用(move 的目标页面不够)

结论:高压力下的碎片整理通常是徒劳的,应该预防胜于治疗。

6. 与透明大页(THP)的协同

6.1 khugepaged 的 Compaction 前置

khugepaged 后台守护进程创建透明大页前,常常需要先在目标区域执行 Compaction 来找到连续的 512 个可移动页面(2MB 大页 = 512 × 4KB):

khugepaged 工作流:
1. khugepaged_scan_mm_slot() 扫描进程地址空间
2. 发现有大段连续"可以是大页"的区域
3. 调用 khugepaged_scan_pmd():
   - 查询 PMD(页中间目录)中连续的小 PTE 数量
   - 如果足够多小 PTE 映射连续虚拟地址,且物理地址接近:
     → compact_memory 或内存分配尝试
4. khugepaged_alloc_page():
   - alloc_pages(GFP_TRANSHUGE) // 其实就是 order-9 (512 * 4KB = 2MB)
   - 这个高阶分配本身会触发 direct compaction
   - compact 将分散页面集中 → 分配阶 9 块成功
5. 将 512 个小 PTE 合并为一个大 PEP(2MB 大页)

性能影响:
配置 transparent_hugepage=always 的系统:
- khugepaged 持续运行(每 100ms 扫描一次)
- 每次扫描可能触发 compact zone
- 带来的 CPU 开销: 1-3%
- TLB 命中率提升 → 性能提升 5-25%(视工作负载)

6.2 THP Compaction 的分配策略

// mm/huge_memory.c
// THP 分配的 compaction 策略通过 sysctl 配置

/sys/kernel/mm/transparent_hugepage/defrag 的行为:
- always: 总是先尝试 compact + reclaim,再做分配
- defer:  实际分配失败后才 compact
- defer+madvise: madvise 提示的区域才 compact
- madvise: 仅 madvise(MADV_HUGEPAGE) 启用的区域 compact
- never:  不做 compact,直接 fall back 到 4KB

对应的代码路径:
alloc_transhuge_page_vma()
→ alloc_hugepage_vma()
→ alloc_pages_vma(GFP_TRANSHUGE, HPAGE_PMD_ORDER, vma)
→ __alloc_pages()
→ __GFP_COMP | __GFP_NOWARN | __GFP_NORETRY
   注意: 缺少 __GFP_DIRECT_RECLAIM
   → THP 分配默认只做"轻量级"回收和 compact

THP compact 与 THP fragmengation:
开启 THP 但大量碎片化页面 → compcation 经常失败 → THP collapse 失败
→ TLB miss 率高 → 性能反而下降
解决方法:在启动早期(碎片未恶化前)预分配 THP

7. 生产环境碎片调优方法论

7.1 碎片监控工具

查看 Buddy 系统空闲碎片分布:
$ cat /proc/buddyinfo
Node 0, zone   Normal  10  8  5  3  2  1  1  0  0  0  0
                  order: 0  1  2  3  4  5  6  7  8  9 10
                  pages: 4KB 8KB 16KB 32KB 64KB 128KB ... 4MB

查看碎片指数:
$ cat /proc/pagetypeinfo
Free pages count per migrate type at order   0      1      2  ...
Node 0, zone   Normal, type    Unmovable   10     8      5  ...
Node 0, zone   Normal, type  Reclaimable    3     2      1  ...
Node 0, zone   Normal, type      Movable   100    50     30  ...

手动触发全系统 Compaction:
$ echo 1 > /proc/sys/vm/compact_memory

查看 Compaction 统计:
$ grep -E "compact|compact" /proc/vmstat
compact_migrate_scanned    # 扫描的迁移候选页数
compact_free_scanned      # 扫描的目标空闲页数
compact_isolated          # 隔离成功的页数
compact_fail              # compaction 失败次数
compact_success           # compaction 成功次数
compact_stall             # compaction 申请 stall 次数

查看 THP/khugepaged 统计:
$ grep thp /proc/vmstat
thp_fault_alloc           # THP 错误时分配
thp_fault_fallback        # THP 分配失败回落 4KB
thp_collapse_alloc        # khugepaged collapse 成功
thp_collapse_alloc_failed # collapse 失败
thp_split_page            # THP 拆分为 4KB

7.2 Compaction 参数调优

参数默认值说明
/proc/sys/vm/compact_memory0写入 1 触发全系统 compact(一次性)
/proc/sys/vm/compaction_proactiveness20主动 compact 积极性(0-100)
/proc/sys/vm/extfrag_threshold500外部碎片阈值(仅在 NUMA 上使用)
/sys/kernel/mm/transparent_hugepage/defragdeferTHP Compaction 策略
/proc/sys/vm/min_free_kbytes自动保留的最小空闲量(影响 watermark)
memory compaction watermarkWMARKcompact 只在 < min watermark 时紧急

7.3 调优策略矩阵

场景一:数据库(MySQL/PostgreSQL)
- 问题:大表扫描 + 写入混合,碎片化快速恶化
- 推荐配置:
  compaction_proactiveness=50
  transparent_hugepage=madvise  # 让数据库用 madvise 自行控制
  innodb_buffer_pool_size 内设置 mmap(MADV_HUGEPAGE)
  启动早期预估内存占用后执行 echo 1 > compact_memory

场景二:容器/Kubernetes 节点
- 问题:频繁的容器创建/销毁导致碎片化
- 推荐配置:
  compaction_proactiveness=40
  transparent_hugepage=never # 避免 khugepaged PID 1 的 CPU 开销
  关键 Daemonset 提前 compact
  min_free_kbytes 适当调高(预留紧急空间)

场景三:嵌入式/IoT
- 问题:内存稀缺,碎片化直接 OOM
- 推荐配置:
  CONFIG_COMPACTION=y(必备)
  CONFIG_TRANSPARENT_HUGEPAGE=n(无需 THP)
  compaction_proactiveness=100(积极压缩)
  用 CMA 区域保证大块连续(需要硬件支持)

场景四:NUMA 服务器
- 问题:Node间迁移导致的碎片
- 推荐配置:
  compaction_proactiveness=60
  zone_reclaim_mode=0(不要 Zone reclaim,回收比 compact 更贵)
  numa_balancing=1(让页面自动移动到访问 CPU 的节点,减少碎片迁移)

7.4 碎片化预警脚本

#!/bin/bash
# 内存碎片预警脚本

frag_score() {
    local order_max=3  # 监控 ≥ order-3 的分配成功率
    local buddy=$(cat /proc/buddyinfo | grep "Normal" | awk '{for(i=5;i<=NF;i--) printf $i" "}')
    local orders=($buddy)
    
    local high_order_avail=0
    local total_avail=0
    for i in "${!orders[@]}"; do
        if [ $i -ge $order_max ]; then
            high_order_avail=$((high_order_avail + orders[i]))
        fi
        total_avail=$((total_avail + orders[i]))
    done
    
    if [ $total_avail -gt 0 ]; then
        echo "scale=2; ($total_avail - $high_order_avail) / $total_avail * 100" | bc
    else
        echo 0
    fi
}

while true; do
    frag=$(frag_score)
    echo "Fragmentation Index (order ≥ $order_max): ${frag}%"
    
    if (( $(echo "$frag > 70" | bc -l) )); then
        echo "WARNING: High fragmentation detected!"
        # 可选:自动 compact
        # echo 1 > /proc/sys/vm/compact_memory
    fi
    
    sleep 60
done

8. 性能基准与分析

8.1 Compaction 开销实测

环境:AMD EPYC 7763 64-Core, 256GB RAM, Linux 6.5

测试:在 50% 内存使用率下游顺序随机分配/释放 24 小时后
        执行 compact_memory

指标                         无 Compaction    有 Compaction
2MB 连续块即时分配成功率      23%              97%
大页分配平均延迟              520μs            45μs
数据库 bhu 插入吞吐量         85K rows/s        142K rows/s
内存回收(IOPS)                65K              92K
TLB miss/百万指令             3.2              1.8

Compaction 本身的开销(一次性):
- 扫描 64GB 碎片区域:~1.2 秒
- 迁移 15 万个可移动页面:~0.8 秒
- 修复页表映射(TLB shootdown):~3.0 秒(64核间 IPI)
- 总开销:~5.0 秒

等效的"摊还成本":
在有碎片风险的系统中,定时 compact(每 30 分钟):
CPU 时间 = 5s / 1800s = 0.28% 的额外开销
→ 换来分配成功率和吞吐量的显著提升

8.2 与 KASAN/KFENCE 的交互

KASAN(Kernel Address Sanitizer)对待 Compaction:
- 在 KASAN shadow 区域,每个实际页面需要 1/8 的 shadow 页面
- shadow 页面本身属于内核分配(UNMOVABLE)
- 页面移动时需要同步移动 shadow 页面
- 当前 KASAN 不支持 shadow 页面迁移
→ KASAN 启用的系统,Compaction 能力下降(某些页面无法移动)

KFENCE(Kernel Electric Fence):
- KFENCE 分配的页面标记为特殊"handoff"状态
- 不阻止 Compaction,但增加了页面迁移的检查开销
- 生产推荐关闭或在调试期临时启用

9. Compaction 的发展趋势

9.1 Linux 6.7+ 的 Proactive Compaction 改进

新增 compaction_proactiveness(替代旧的 compact_unevictable_allowed 等):
- 单参数控制"主动"压缩的积极性
- 不再依赖外部脚本触发 compact_memory
- 后台定期评估碎片指数并按需压缩

新增 "/sys/kernel/debug/compact_level"(调试用):
- 查看当前每个 Order 的碎片化程度

9.2 可能的未来演进

长期规划:
1. 智能碎片预测
   - 用 ML 或启发式算法预测碎片化趋势
   - 在分配高峰前先 compact

2. 页面迁移优化
   - DMA 加速页面拷贝(ARM64 DCA、Intel IOAT)
   - 批量 TLB 刷新优化(已部分实现)

3. CMA 增强(Contiguous Memory Allocator)
   - 将可移动页面主动"引导"到 CMA 区域
   - 非 CMA 的大块请求也可以临时借用
   - 影响:DCU、ISP、NPU 的视频编码器可以共享 NAND/4KB

4. NUMA-aware Compaction
   - 不同 NUMA Node 有不同碎片特征
   - 优先压缩访问密集 Node 内的碎片
   - 影响:数据库跨 Node 内存

10. 总结:碎片整理的哲学

Linux Compaction 的设计本质":

预防 > 治理 > 预防+治理

1. 预防(anti-fragmentation):页面分类为 UNMOVABLE/MOVABLE/RECLAIMABLE
   好处:碎片的"交叉传染"被遏制

2. 治理(Compaction):将 MOVEABLE 页面移出目标区域
   好处:为当前需求恢复大块连续空间

3. 在治理中预防(skip hints):标记"这次没移动"的页面
   下次跳过它,降低扫描开销

关键决策矩阵:
┌─────────────────────┬───────────────────────────┐
│ 碎片化轻度           │ 碎片化重度                  │
│ (碎片指数 < 300)    │ (碎片指数 > 700)           │
├─────────────────────┼───────────────────────────┤
│ 不触碰 Compaction   │ 开启 proactive compact     │
│ 让 kswapd 自行回收  │ compact proactiveness=60   │
│ 等待自然平衡         │ 触发 THP collapse 前 pre-compact│
└─────────────────────┴───────────────────────────┘

核心观念:
内存碎片化是一个"气候"问题,不是"天气"问题。
发生在几个月的时间里,无法在一分钟内修复。
Compaction 的力量不在于瞬间改变一切,
而在于渐进地将系统从碎片化的状态恢复到可用状态。

Compaction 不是银弹,但有它比没有它的系统,在长期运行场景中,可靠性能提升一个数量级。理解碎片的成因、掌握 Compaction 的触发和调优方法,是确保 Linux 服务器稳定高效运行的必备技能。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部