🎥 相关博文对应 :B站精讲视频
Linux 内存管理硬核揭秘:kswapd 与内存 compact 的本质剖析
内核内存紧张时,谁在幕后调度内存?分配大块内存失败怎么处理?
kswapd
与compact
有什么不同?本文为你一次讲透!
一、你遇到过这些问题吗?
- 什么是 kswapd?它和 direct reclaim 的关系是什么?
- kswapd 主要做什么?内存回收是主动还是被动?
- 什么是内存碎片?为什么需要 compact?
- compact 机制和 kswapd 机制的区别?它们能解决什么问题?
- 如果大块内存分配(order>0)失败,内核会如何补救?
- 怎么通过内核参数和调试工具监控和分析 kswapd/compact 行为?
- 在代码里 kswapd、compact 分别是怎么被调度、调用的?主流程是啥?
二、内核内存管理:宏观结构速览
内存分配相关的核心流程与“幕后角色”:
- Page Allocator(伙伴系统):核心内存分配器,管理物理页框,order 代表分配的连续页数量(2^order)。
- kswapd:内存回收“守护神”,负责定期回收不再使用的页,维护系统内存水位。
- Direct Reclaim:应用进程自身分配内存失败时,自己直接参与内存回收。
- Memory Compact(内存压缩):专门将内存碎片合并成大块,提升高阶页分配成功率。
- OOM Killer:最后防线,内存极度紧张时杀死进程释放内存。
三、kswapd:内存回收守护者
1. kswapd 的核心作用
- 监控内存水位线(如
/proc/meminfo
中的LowFree
等)。 - 自动回收不再被使用的页(如页面缓存、匿名页、slab 缓存等),以确保系统运行稳定。
- 只做回收,不做碎片整理。
- 唤醒机制: 只要某个 zone 的可用页低于 water mark(如
WMARK_MIN
),伙伴系统就唤醒 kswapd 线程。
2. kswapd 的主要流程
- 持续循环(线程),每次回收尝试满足各 zone 的水位要求。
- 典型路径(简化):
// mm/vmscan.c
int kswapd(void *p) {
...
for (;;) {
try_to_free_pages()
// 1. 选择 zone
// 2. 判断是否达到水位
// 3. 选中 victim page,回收
}
}
-
回收类型:
- 页面缓存(page cache)
- 匿名页(anonymous pages)
- slab 缓存
-
触发入口:
- 在伙伴系统中,
__alloc_pages_slowpath()
发现低于水位,wakeup_kswapd()
立即唤醒kswapd
。
- 在伙伴系统中,
3. kswapd 和 direct reclaim 区别
机制 | 触发场景 | 行为 | 作用对象 |
---|---|---|---|
kswapd | 内存低于水位线 | 后台异步回收 | 整个系统的内存 |
direct reclaim | 分配内存时直接失败 | 当前进程同步回收 | 只为当前进程服务 |
四、Memory Compact:内存碎片终结者
1. 为什么要有 compact?
-
碎片问题:随着系统运行,虽然有很多“零散”的空页,但无法满足高阶(order>0)连续页的分配需求,比如大页分配、设备 DMA、hugepage 等。
-
compact 的目的: 把分散的空闲页“合并”成更大块的连续空闲内存区域。
2. compact 的工作机制
-
主动合并:遍历内存区域,将未使用的物理页“向一边搬运”,生成连续空间。
-
调用时机:
- 高阶分配失败后自动触发(如分配 order=9 大页失败)。
- 系统后台也会定期尝试。
- 手动触发(如 echo 1 > /proc/sys/vm/compact_memory)。
3. 典型流程
// mm/compaction.c
int compact_zone(zone, ...) {
// 1. 从低地址开始,找到空闲页
// 2. 从高地址找出在用页
// 3. 把在用页迁移到空闲页上
// 4. 形成大块连续空闲空间
}
-
核心函数:
try_to_compact_pages()
compact_zone()
migrate_pages()
4. kcompactd 内核线程
- kcompactd 专门负责在后台做碎片整理,逻辑类似 kswapd,但目标是碎片合并而不是回收。
- 唤醒机制类似:伙伴系统分配高阶页失败时唤醒。
五、调用关系与联动机制
1. 内存分配主流程
- 应用或内核模块申请内存(如
kmalloc()
)。 - 伙伴系统先尝试快速分配(
__alloc_pages_fast()
)。 - 如果失败,则进入慢速分配路径(
__alloc_pages_slowpath()
)。 - 先唤醒 kswapd,试图回收;如仍然失败,再 direct reclaim(进程自己回收)。
- 如果发现碎片严重,尝试 compact,调用
try_to_compact_pages()
。 - 如果依然失败,触发 OOM Killer。
2. 代码调用链(高阶分配失败为例)
// mm/page_alloc.c
__alloc_pages_slowpath()
-> wakeup_kswapd()
-> try_to_free_pages()
-> try_to_compact_pages()
-> OOM_killer()
六、典型结构与数据流示意
1. 结构对比表
名称 | 类型 | 线程/任务 | 作用/定位 | 是否主动/被动 |
---|---|---|---|---|
kswapd | 内核线程 | 每个 zone | 自动回收内存,保持水位 | 主动(后台) |
kcompactd | 内核线程 | 每个 zone | 合并空闲页,消除碎片 | 主动(后台) |
direct reclaim | 回调/流程 | 无 | 当前进程回收,紧急场合 | 被动(前台) |
compact | 回调/流程 | 无 | 分配大块内存时合并碎片 | 被动/主动均有 |
2. 主流程图(简化)
应用/内核请求分配内存
|
-----------------------
| 伙伴系统快速分配 |
-----------------------
| 失败
v
__alloc_pages_slowpath
| | |
唤醒kswapd 回收页 尝试compact
| |
分配成功? 否
|
OOM killer
七、调试与监控方法
- /proc/buddyinfo 查看各阶空闲页分布,判断碎片。
- /proc/vmstat 关注
pgscan_kswapd*
、compact*
字段。 - tracepoint/ftrace 监控 mm_vmscan_、mm_compaction_ 事件。
- dmesg 或
printk
可见 kswapd、compact 相关日志。
八、常见面试/高频开发问题
1. 什么场景下只回收而不 compact?反之呢?
- 回收:普通分配或内存紧张,回收能立即释放就行。
- compact:分配高阶页(大块内存、hugepage、DMA buffer)时,只有碎片严重才 compact。
2. kswapd 会导致性能下降吗?如何优化?
- 频繁唤醒会消耗系统资源。合理调整水位线参数(
/proc/sys/vm/min_free_kbytes
),避免频繁回收。
3. 怎么判断是内存不足还是碎片问题?
- buddyinfo 各阶页面充足但高阶很少,且 free 总量充裕→碎片。
- 总量少所有阶都低→真正内存不足。
4. 分配大块内存一定会失败吗?
- 如果高阶页不够且 compact 也合并不出,则失败。否则只要物理内存有空闲,是有希望的。
5. 如何手动触发 compact?
echo 1 > /proc/sys/vm/compact_memory
九、总结提炼
- kswapd:专注后台回收,维持系统水位,是内存的“看门人”。
- compact/kcompactd:专注合并碎片,保障高阶页分配成功,是“大块内存”的“铺路者”。
- 两者各司其职,配合 direct reclaim 和 OOM Killer,构成了 Linux 高效而健壮的内存分配防线。
十、相关核心问题(附标准答案)
-
kswapd 的触发机制与主要作用?
- 答:当某 zone 低于水位线时被唤醒,负责回收页缓存、匿名页等,保障分配不阻塞。
-
什么是 compact,它和 kswapd 有什么区别?
- 答:compact 合并碎片,生成高阶连续页。kswapd 回收内存但不负责碎片合并。
-
高阶分配失败时的内核处理路径?
- 答:先回收(kswapd/direct reclaim),再 compact(kcompactd),都失败则 OOM。
-
怎么区分系统内存紧张和碎片化问题?
- 答:通过
/proc/buddyinfo
分析各阶空闲页分布,判断总量与连续性。
- 答:通过
-
如何手动触发 compact 或 kswapd?
- 答:compact:
echo 1 > /proc/sys/vm/compact_memory
;kswapd 通常自动管理。
- 答:compact:
🎥 相关博文对应 :B站精讲视频