基本逻辑
damon是Linux内核里的一个内存管理的子系统,这个子系统对用户暴露了一组sysfs接口出
来,用户通过配置这些接口可以定制内存回收、内存冷热页管理等策略。damon子系统会根
据用户定制的策略,使用专门的内核线程进行管理。
damon子系统的代码在内核mm/damon目录下。内核中有对应的使用文档,路径在Documentation/
mm/damon,Documentation/admin-guide/mm/damon。
总体上看damon子系统有三个大的使用方式,第一个是做内存监控,第二个是做内存回收,
第三个是做LRU链表上冷热页的调整。
做内存监控的sysfs接口在/sys/kernel/mm/damon目录,用户可以配置监控的目标以及策略,
也可以配置某些事件发生时要做的处理。
内存回收和LRU冷热页调整的sysfs接口在/sys/module/damon_reclaim和/sys/module/damon_lru_sort。
用户通过这些接口定义内存回收和LRU冷热页调整的规则。
damon还是使用PTE的access bit来判断CPU对物理页的访问情况,damon核心的创新是它把
若干page放到一起,只用其中一个page的access bit做为这一堆page访问情况的度量,当
这一堆page(一个区域)的访问情况相仿时,这个优化是合理的。在此基础上,damon会做区
域的合并和拆分,相邻的不活跃的区域会合并成一个大区域,活跃的区域会拆分成多个较
小的区域。
damon还提供一个用户态工具:https://github.com/awslabs/damo,这个工具是damon sysfs
接口的封装,方便用户直接使用。
实际使用
如下是damon做内存监控实际使用时的一个sysfs的文件树,我们把注释直接写到里面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| `-- admin `-- kdamonds <-- 创建了两个kdamond线程,只看0 |-- 0 | |-- contexts <-- 目前一个kdamond只支持一个context | | |-- 0 | | | |-- avail_operations | | | |-- monitoring_attrs | | | | |-- intervals | | | | | |-- aggr_us | | | | | |-- sample_us | | | | | `-- update_us | | | | `-- nr_regions | | | | |-- max | | | | `-- min | | | |-- operations | | | |-- schemes | | | | `-- nr_schemes <-- 这里可以展开,配置context的监控策略 | | | `-- targets <-- 一个context下创建了3个target, | | | |-- 0 每个target可以跟踪不同进程 | | | | |-- pid_target | | | | `-- regions <-- target0下创建了4个人region | | | | |-- 0 | | | | | |-- end | | | | | `-- start | | | | |-- 1 | | | | | |-- end | | | | | `-- start | | | | |-- 2 | | | | | |-- end | | | | | `-- start | | | | |-- 3 | | | | | |-- end | | | | | `-- start | | | | `-- nr_regions | | | |-- 1 | | | | |-- pid_target | | | | `-- regions | | | | `-- nr_regions | | | |-- 2 | | | | |-- pid_target | | | | `-- regions | | | | `-- nr_regions | | | `-- nr_targets | | `-- nr_contexts | |-- pid | `-- state |-- 1 | |-- contexts | | `-- nr_contexts | |-- pid | `-- state `-- nr_kdamonds
|
如下是damon做内存回收的sysfs接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # pwd /sys/module/damon_reclaim/parameters # ls aggr_interval nr_reclaim_tried_regions bytes_reclaim_tried_regions nr_reclaimed_regions bytes_reclaimed_regions quota_ms commit_inputs quota_reset_interval_ms enabled quota_sz kdamond_pid sample_interval max_nr_regions skip_anon min_age wmarks_high min_nr_regions wmarks_interval monitor_region_end wmarks_low monitor_region_start wmarks_mid nr_quota_exceeds
|
如下是damon做LRU冷热页移动的sysfs接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| # pwd /sys/module/damon_lru_sort/parameters # ls aggr_interval nr_cold_quota_exceeds bytes_lru_sort_tried_cold_regions nr_hot_quota_exceeds bytes_lru_sort_tried_hot_regions nr_lru_sort_tried_cold_regions bytes_lru_sorted_cold_regions nr_lru_sort_tried_hot_regions bytes_lru_sorted_hot_regions nr_lru_sorted_cold_regions cold_min_age nr_lru_sorted_hot_regions commit_inputs quota_ms enabled quota_reset_interval_ms hot_thres_access_freq sample_interval kdamond_pid wmarks_high max_nr_regions wmarks_interval min_nr_regions wmarks_low monitor_region_end wmarks_mid monitor_region_start
|
代码分析
damon子系统用一堆__init修饰的初始化函数在内核启动的时候做damon相关的初始化。damon
子系统的数据结构和运行大逻辑比较简单,复杂度在和业务相关的参数上。
damon的内部数据结构基本和内存检测sysfs接口展示出来的一致,也是context/target/region
的基本数据结构,context中抽象出了具体业务在整个检测流程里的操作函数,这一堆操作
函数统一放到struct damon_operations里。
如上的三种业务,每种业务运行的时候都对应一个kdamond的内核线程。每个kdamon线程都
在一个循环里执行:检测内存访问,对应内存操作,调整检测区域的步骤,具体业务在
damon_operations的各个回调函数里执行。
下面用damon reclaim串联下如上的逻辑。damon reclaim的代码在mm/damon/reclaim,现在
是一个只能编译进内核的内核模块。
1 2 3 4 5 6 7
| damon_reclaim_init /* * 生成context和target(全局变量), 为context添加上damon_operations,其中 * damon_operations是DAMON_OPS_PADDR,为context生成对应的scheme,可见这里 * 同样有scheme的概念,只不过没有对外接口,直接写死了scheme的对应参数。 */ +-> damon_modules_new_paddr_ctx_target
|
用户对/sys/module/damon_reclaim/enabled写1触发damon收拢相关参数,然后拉起kdamond
内核线程。
1 2 3 4 5
| damon_reclaim_enabled_store +-> damon_reclaim_turn +-> damon_reclaim_apply_parameters +-> damon_start ...
|
reclaim场景下kdamond的基本逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| kdamond_fn while (!kdamond_need_stop(ctx)) { +-> prepare_access_checks // damon_pa_prepare_access_checks | /* | * 对于每个region都随机挑选一个物理页做预处理,所谓预处理就是对这个物 | * 理页做逆向映射查找(rmap_walk),对于找到的每个虚拟页都清除页表上对应 | * 的访问标记(access bit)。 | */ | +-> damon_pa_mkold | | /* 等待sample_interval us */ +-> kdamond_usleep(sample_interval) | +-> check_accesses // damon_pa_check_accesses | /* | * 如上相同的逻辑,rmap_walk检查所有虚拟页上的访问标记是否有被写过, | * 由此得出相应物理页在这期间有没有被访问过,并记录得到的统计数据。 | * | * 这个函数返回相应物理页有没有被访问过。 | */ | +-> damon_pa_young | /* 更新统计结果 */ | +-> damon_update_region_access_rate | +-> kdamond_apply_schemes /* * 根据scheme->action里的配置做对应的处理,如上初始化scheme时给reclaim * scheme->action的参数是DAMOS_PAGEOUT,所以这里进入对应的流程。 * * 针对每个target、每个region都做这样的处理。 */ +-> ctx->ops.apply_schemes // damon_pa_apply_schemes /* 没有看懂这里的逻辑,是否做reclaim的依据是什么?*/ +-> damon_pa_pageout +-> reclaim_pages }
|