campact
简介
在出现内存碎片问题时,可以尝试进行内存规整(compact),移动可移动的页面,腾出更多连续空闲内存。
将一个页移动到另一个页的过程叫作页迁移(page migrate),事实上,页迁移是内存管理的独立逻辑, 内核封装了单独接口 migrate_pages()
。其中一个应用场景就是 compact,其他类似场景还有 NUMA Balance、Memory hotplug 和 CMA等。
策略场景
内存规整的范围?何时进行内存规整?
对于内存规整范围,内核通常选择以 zone 为单位进行规整,核心接口 compact_zone()
。
何时触发,属于触发策略和场景问题,内核当前有四种策略场景,这些场景最终都会通过 compact_zone()
进行内存规整,如下:
direct compact
kcompactd
/proc/sys/vm/compact_memory
/proc/sys/vm/compaction_proactiveness
如下所示,内存规整是基于内存迁移实现的功能,内核根据不同策略触发内存规整,用于缓解内存外部碎片问题, 可以分层分析内存规整。
direct compact
在 page 分配器进入慢速分配路径时,并且内存低于 min 水位线,会触发 direct compact 策略。
kcompactd
内核在启动过程中会调用 kcompactd_init()
,为每个 node 启动一个内核线程 kcompactd ,并且 kcompactd 线程会运行在与 node 相对应的 CPU 核上,在合适的时机 kcompactd 将会被唤醒进行内存规整。
本节主要从二个方面说明,分别是 唤醒条件、运行条件。
唤醒条件,内存规整模块向内核提供 wakeup_kcompactd()
用于唤醒 node 对应的 kcompactd 线程, 唤醒 kcompactd 与 kswapd 是强相关,如下场景会被唤醒:
内存分配失败时,会先唤醒 kswapd,
wakeup_kswapd()
将先判断当前内存无法分配的原因,如果 不是低内存导致,此时内存回收可能已经无法解决此问题,所以将会提前调用wakeup_kcompactd()
唤醒 kcompacted 线程进行内存规整。kswapd 线程在每次内存回收完毕后,将会调⽤
kswapd_try_to_sleep()
尝试休眠,随后调用wakeup_kcompactd()
尝试进行内存规整。
运行条件,当 kcompactd 线程运行时,将会调用 compactd_do_work()
遍历当前 node 的所有 zone 进行合法性判断,若符合条件,则调用 compact_zone()
针对 zone 进⾏内存规整。
/proc/sys/vm/compact_memory
通过对 compact_memory 节点进行写 1,触发当前所有 node 以及所有 zone 进行内存规整。
/proc/devices/system/node/node<id>/compact
只有存在 NUMA 系统上,可以只触发某一个 node 进行内存规整。
解析 compact_zone()
compact_zone()
针对单个 zone 内存区进行内存规整,这是内存规整的最小单元。 通过 migrate scanner 从低地址到高地址寻找迁移页, 通过 free scanner 从高地址到低地址寻找空闲页, 最终将迁移页迁移至空闲页,完成内存规整,如下所示:
更细节一些来说,内存规整开始后,先通过 migrate scanner 扫描,并且扫描单位为一个 pageblock, 将当前 pageblock 中可迁移的页隔离后放入到待迁移的链表。随后调用 free scanner 扫描, free scanner 依然以 pageblock为步长,但不再限制扫描一个pageblock,其扫描的目标是 找到大于等于当前迁移页数量的空闲页。上述扫描过程将会产生迁移页和空闲页,用于后续内存迁移,这样 就完成了内存规整的一轮操作,内存规整并非一次性扫描 zone,然后再迁移,而是以这种一步一步的方式 进行迁移,这能平摊内存规整对性能带来的风险,并且每轮处理后都有机会判断当前内存规整是否可以退出。
再次回到 compact_zone()
,核心代码逻辑如下:
compact_finished()
用于判断当前内存规整是否结束isolate_migratepages()
是 migrate scanner 实现,用于查找需要移动的页isolate_freepages()
是 free scanner 实现,用于查找空闲页migrate_pages()
是页迁移函数,将上述两个 scanner 扫描出来的页进行迁移处理,完成内存规整
总而言之,内存规整的核心逻辑在于 migrate scanner 和 free scanner 运作原理。
migrate scanner
主要是 isolate_migratepages()
,本质就是将 page 从 LRU 链表中删除。
以 pageblock 为步长,检查是否有合适的 pageblock?
如果当前 pageblock 不合适,继续查找下一个 pageblock,但是这里最多查找 32个 pageblock 后, 就需要主动 schedule 出去,睡眠一段时间再继续。
如果有合适的 pageblock,调用一次 isolate_migratepages_block()
,查找合适的迁移页。 如果找到,将此迁移页从 LRU 链表中删除,同时加入到 migrate page list。 完成一轮 pageblock 查找后,直接结束查找迁移页。
free scanner
主要是 isolate_freepages()
,本质就是将 page 从 page 分配器中取出,不再参与系统内存分配, 仅用于内存规整迁移。
以 pageblock 为步长,调用一次 isolate_freepages_block()
,查找合适的空闲页, 如果找到,将此空闲页从 page 分配器中删除,同时加入到 free page list。
完成一轮 pageblock 查找后,判断空闲页数是否大于等于迁移页数?true,结束查找空闲页。 false,继续查找下一个 pageblock,但是这里最多查找 32个 pageblock 后,就需要主动 schedule 出去,睡眠一段时间再继续。
Last updated
Was this helpful?