内存管理-77-kernel-internals系列-Memory专题-Advanced-4-Memory Compaction


一、Memory Compaction 翻译

注: 翻译自 https://kernel-internals.org/mm/compaction/

内存规整/内存压缩 (Memory Compaction)

为大块内存分配而对物理内存进行碎片整理。


1. 什么是规整?

内存规整通过移动页面,来创建出连续的空闲区域。随着时间推移,空闲内存会变得碎片化——以小碎片的形式散布在整个物理内存中。规整会把这些碎片合并起来。

规整前:

┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ U │ F │ U │ F │ U │ U │ F │ U │ F │ F │ U │ F │ U │ F │ U │ F │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
U = 已使用,F = 空闲(零散分布)

规整后:

┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ U │ U │ U │ U │ U │ U │ U │ U │ U │ F │ F │ F │ F │ F │ F │ F │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
创建出连续的空闲区域


2. 为什么需要规整?

2.1 碎片化问题

伙伴分配器 (buddy allocator) 需要物理上连续的页面,来满足高阶(high-order)分配:

-------------------------------------------------------------------------------------------------------------------------------------
阶(Order)    大小     用途
-------------------------------------------------------------------------------------------------------------------------------------
0            4KB      普通页面
1            8KB      一些内核分配
2            16KB     巨型帧 Jumbo frames
3+           32KB+    大页 Huge pages、DMA 缓冲区
-------------------------------------------------------------------------------------------------------------------------------------

系统运行几天/几周之后,可能空闲内存还很多,但已经没有连续区域可用于高阶分配了。


2.2 在规整出现之前

在有规整之前,高阶分配失败很常见:

THP 无法分配 2MB 页面
DMA 缓冲区分配失败
网络巨型帧不可用

唯一的解决办法是重启,或者在启动时预留内存。


3. 规整是如何工作的?

3.1 算法

规整使用两个相向移动的扫描器:

Zone start                                                   Zone end
│                                                               │
▼                                                               ▼
┌───────────────────────────────────────────────────────────────┐
│ Zone                                                          │
└───────────────────────────────────────────────────────────────┘
│                                                               ▼
▼                                                            空闲扫描器
迁移扫描器                                                   (寻找空闲页面)
(寻找可移动页面)                                                  │
│                                                              │
└──────────────► ◄─────────────────────────────────────────────┘
             在中间相遇

(1) 空闲扫描器 (Free scanner):从 zone 末尾开始,向后移动,寻找空闲页;
(2) 迁移扫描器 (Migration scanner):从 zone 起始开始,向前移动,寻找可移动页;
(3) 迁移 (Migration):把迁移扫描器找到的页面,搬到空闲扫描器找到的位置;
(4) 结果:空闲空间被合并到一端;


3.2 页面可移动性

不是所有页面都能移动:

-------------------------------------------------------------------------------------------------------------------------------------
迁移类型               是否可移动?      示例
-------------------------------------------------------------------------------------------------------------------------------------
MIGRATE_MOVABLE        是             用户页面、页面缓存 page cache
MIGRATE_RECLAIMABLE    有时可以        缓存(可以直接释放,而不移动)
MIGRATE_UNMOVABLE      否             内核分配、DMA
-------------------------------------------------------------------------------------------------------------------------------------

规整只对可移动页面有效。不可移动页面会造成永久性碎片。


4. 规整的触发方式

4.1 直接规整 Direct Compaction(同步)

当高阶分配失败时,执行分配的进程自己运行规整:

alloc_pages(order=9) /* 为 THP 分配 2MB */
  │
  ▼
分配失败
  │
  ▼
直接规整(进程阻塞等待)
  │
  ▼
重试分配


4.2 kcompactd(异步)

后台内核线程,主动进行规整:

# 每个 NUMA 节点一个 kcompactd
ps aux | grep kcompactd
# kcompactd0, kcompactd1, ...

在以下情况被唤醒:
(1) 水位线 (watermark) 显示存在碎片;
(2) 高阶分配正在失败;
(3) 被显式触发;


4.3 手动触发

# 在所有节点上触发规整,实测看起来是在echo线程上下文执行的
echo 1 > /proc/sys/vm/compact_memory

# 按节点触发,非NUMA没有此节点
echo 1 > /sys/devices/system/node/node0/compact


5. 配置

5.1 主动规整 Proactive Compaction (v5.9+, 5.4内核上没有)

在分配真正需要之前,就在后台提前规整:

# 开启主动规整(0-100,0=关闭)
cat /proc/sys/vm/compaction_proactiveness
echo 20 > /proc/sys/vm/compaction_proactiveness

# 数值越高 = 后台规整越激进
# 数值越低 = CPU 开销越小,但直接规整越多,实测默认是20


5.2 规整行为

# 碎片指数阈值
# 当 fragmentation_index > extfrag_threshold 时触发规整
# 指数 0 = 因缺内存而失败,1000 = 因碎片而失败
cat /proc/sys/vm/extfrag_threshold
# 500(默认值)- 越低越激进(在碎片程度较低时就规整)

# 查看每个 order 的碎片指数
cat /sys/kernel/debug/extfrag/extfrag_index


6. 监控

6.1 规整统计

cat /proc/vmstat | grep compact
# compact_migrate_scanned - 为迁移而扫描的页面数
# compact_free_scanned - 为找空闲空间而扫描的页面数
# compact_isolated - 为迁移而隔离出来的页面数
# compact_stall - 直接规整导致的停顿次数
# compact_fail - 规整失败次数
# compact_success - 规整成功次数


6.2 碎片指数

# 每个 order 的碎片情况(0.0 = 无碎片,1.0 = 严重碎片)
cat /sys/kernel/debug/extfrag/extfrag_index

# 示例:
# Node 0, zone Normal
# order 0   1   2   3   4   5   6   7   8   9   10
# index 0.0 0.0 0.0 0.0 0.1 0.2 0.4 0.6 0.8 0.9 1.0


6.3 追踪 Tracing

# 追踪规整事件
echo 1 > /sys/kernel/debug/tracing/events/compaction/mm_compaction_begin/enable
echo 1 > /sys/kernel/debug/tracing/events/compaction/mm_compaction_end/enable
cat /sys/kernel/debug/tracing/trace_pipe


7. 演进历史

引入 (v2.6.35, 2010)
提交:748446bb6b5a ("mm: compaction: memory compaction core")
作者:Mel Gorman

最初的规整实现,用于支持 THP,减少高阶分配失败。

kcompactd (v4.6, 2016)
提交:698b1b30642f ("mm, compaction: introduce kcompactd")
作者:Vlastimil Babka

后台规整守护线程,类似于负责回收的 kswapd。

主动规整 (v5.9, 2020)
提交:facdaa917c4d ("mm: proactive compaction")
作者:Nitin Gupta

基于碎片程度主动规整,减少直接规整导致的停顿。


8. 规整 vs 回收

两者都在内存压力下运行,但目的不同:

-------------------------------------------------------------------------------------------------------------------------------------
方面        回收 Reclaim      规整 Compaction
-------------------------------------------------------------------------------------------------------------------------------------
目标        释放出空闲内存     创建连续区域
时机        空闲页面不足时     高阶分配失败时
动作        驱逐/换出页面      移动页面
守护线程     kswapd          kcompactd
-------------------------------------------------------------------------------------------------------------------------------------

它们经常协同工作:回收负责释放页面,规整负责把页面排列整齐。


9. 常见问题

9.1 直接规整停顿

进程阻塞等待规整,导致卡住。
症状:延迟毛刺、compact_stall 计数很高
解决办法:开启主动规整; 调优 compaction_proactiveness; 减少高阶分配


9.2 规整失败

无法创建出连续区域。
症状:compact_fail 很高、THP 分配失败
原因:不可移动页面太多; 碎片太严重
解决办法:减少内核内存使用; 增大可移动 zone 的大小; 考虑用内存热插拔来做隔离; kcompactd CPU 占用过高;


9.3 后台规整消耗过多 CPU。

排查:top、perf top
解决办法:降低 compaction_proactiveness; 接受更多的直接规整.


10. 参考资料

9.1 关键代码

-------------------------------------------------------------------------------------------------------------------------------------
文件               说明
-------------------------------------------------------------------------------------------------------------------------------------
mm/compaction.c    规整的实现
mm/page_alloc.c    规整的触发点
-------------------------------------------------------------------------------------------------------------------------------------

page-allocator - 伙伴系统、迁移类型
thp - 规整的主要消费者
reclaim - 与规整协同工作
contiguous-memory - 为什么连续分配会失败及解决方案


11. 进一步阅读

page-allocator.md — 伙伴分配器的迁移类型(MIGRATE_MOVABLE、MIGRATE_UNMOVABLE),决定了 compaction 可以移动哪些页面
thp.md — 透明大页:compaction 的主要驱动因素;需要 order-9 的连续物理页
reclaim.md — kswapd 和直接回收;compaction 与回收在相同的水位线逻辑下协同工作
cma.md — 连续内存分配器;使用基于迁移的 compaction 来回收预留的 CMA 区域
mm/compaction.c — 完整的 compaction 实现;compact_zone() 运行双扫描算法
mm/page_alloc.c — try_to_compact_pages() 是在分配失败时触发直接 compaction 的位置
LWN: Memory compaction — Mel Gorman 对双扫描算法和碎片化指数的介绍
LWN: Proactive compaction — Nitin Gupta 在 v5.9 中加入的主动式 compaction,以及 compaction_proactiveness 可调参数
748446bb6b5a — Mel Gorman 在 v2.6.35 中提交的 compaction 核心原始提交
facdaa917c4d — 在 v5.9 中加入的主动式 compaction,用于减少直接 compaction 造成的停顿
Documentation/admin-guide/mm/transhuge.rst — 涵盖 THP 分配、compaction 调优和 compact_memory sysctl

 

posted on 2026-10-06 15:07  Hello-World3  阅读(4)  评论(0)    收藏  举报

导航