内存管理-77-kernel-internals系列-Memory专题-Caching & Reclaim-7-DAMON
一、DAMON: Data Access Monitoring 章节翻译
注: 翻译自 https://kernel-internals.org/mm/damon/
DAMON:数据访问监控(Data Access Monitoring)一个轻量、准确、可扩展的内核框架,用于观察内存如何被访问 —— 并基于这些知识采取行动
1. 什么是 DAMON?
DAMON(Data Access MONitoring)是跟踪运行时每个内存区域被访问频繁程度的内核子系统。它本身不是一种回收策略,而是一个监控框架,产出访问频率数据,供其他子系统采取行动 —— 最重要的是 DAMOS(基于 DAMON 的操作方案,DAMON-based Operation Schemes)。
关键设计目标:
准确(Accuracy):按 region 而不是按页跟踪访问频率,但足够接近真实,足以驱动有效决策;
可扩展(Scalability):可用于任意大小的地址空间,没有 O(n) 扫页代价;
自适应(Adaptivity):region 自动放大缩小,把监控精力集中在重要的地方;
----------------------------------------------------------------------------------------------------------------------------------------
没有 DAMON 有 DAMON
----------------------------------------------------------------------------------------------------------------------------------------
内核只按 LRU 新老回收页面 —— 不知道哪些区域真的冷 内核长期观察访问模式,再经 DAMOS 精确打击冷区域
----------------------------------------------------------------------------------------------------------------------------------------
DAMON vs. DAMOS:DAMON 负责收集数据。DAMOS 用这些数据触发动作(换出冷内存、提升热内存等)。两者经 sysfs 接口一起配置。
2. 核心概念
2.1 Region:监控的单位
DAMON 把监控目标的地址空间划分成一串 struct damon_region 对象。每个 region 代表一段连续地址 [ar.start, ar.end),携带:
nr_accesses —— 当前聚合窗口内,有多少个采样间隔发现了访问; nr_accesses_bp —— 万分比(1/10,000)滑动平均版本,每个采样间隔更新一次,适合等不及完整聚合窗口的场景; age —— 该 region 访问频率保持大致不变经过了多少个聚合间隔;访问频率显著变化时清零; sampling_addr —— 下个间隔要检查访问情况的 region 内具体地址;
监控虚拟地址(DAMON_OPS_VADDR)时,DAMON 从进程的 VMA 初始化 region。物理地址监控(DAMON_OPS_PADDR)时,region 初始化为覆盖目标物理地址范围。
2.2 采样:抓住访问
每 sample_interval 微秒(字段 damon_attrs.sample_interval),kdamond 线程:
(1) 检查每个 region 内的 sampling_addr 自上次采样后是否被访问过(测试并清除页表项中的 accessed/young 位)
(2) 若发现访问,nr_accesses 加一
(3) 在该 region 内随机挑新的 sampling_addr 给下个间隔用
默认 sample_interval 是 5000 µs(5ms)。
2.3 聚合:形成全貌
每 aggr_interval 微秒(damon_attrs.aggr_interval),DAMON:
(1) 上报(或作用于)每个 region 累积的 nr_accesses 计数;
(2) nr_accesses 清零;
(3) 更新每个 region 的 age;
(4) 运行自适应 region 大小调整逻辑(split/merge);
默认 aggr_interval 是 100,000 µs(100ms),即每个上报的 nr_accesses 值代表过去 20 个采样间隔(100ms / 5ms)内观察到的访问。
2.4 自适应 Region 大小
DAMON 维护 damon_attrs.min_nr_regions 和 damon_attrs.max_nr_regions 上下界(默认 10 和 1000)。每次聚合后调整 region:
分裂(Split):访问不均匀的 region(子范围有热有冷)被拆成更小的 region,以刻画梯度;
合并(Merge):访问频率相近的相邻 region 合并,降低开销;
这样 region 总数保持在界内,同时把分辨率集中在访问模式变化的地方。
最小 region 大小: DAMON_MIN_REGION_SZ 为 PAGE_SIZE。任何 region 不能小于一页。字段 damon_ctx.min_region_sz 存这个下界。
2.6 间隔自动调优
DAMON 可自动一起调优 sample_interval 和 aggr_interval。目标用 struct damon_intervals_goal 表达:
---------------------------------------------------------------------------------------------------- 字段 含义 ---------------------------------------------------------------------------------------------------- access_bp 观测到的访问事件数与理论最大值的目标比,万分比(1/10,000) aggrs 在多少个聚合窗口上达成该比例 min_sample_us 调优后采样间隔下界 max_sample_us 调优后采样间隔上界 ----------------------------------------------------------------------------------------------------
aggrs 为零时关闭自动调优。经 sysfs,这些字段在 monitoring_attrs/intervals/intervals_goal/ 下。
3. 架构

3.1 damon_ctx 结构
每个监控会话锚定在一个 struct damon_ctx:
struct damon_ctx { //include/linux/damon.h
struct damon_attrs attrs; /* sample/aggr/update 间隔、nr_regions */
struct damon_operations ops; /* 可插拔 ops 集(vaddr/paddr) */
unsigned long addr_unit; /* 核心到 ops 的地址换算系数 */
unsigned long min_region_sz; /* 最小 region 大小(DAMON_MIN_REGION_SZ) */
struct list_head adaptive_targets; /* damon_target 链表 */
struct list_head schemes; /* damos 链表 */
struct task_struct *kdamond; /* 监控线程 */
/* ... 私有字段 ... */
};
3.2 kdamond 线程
每个 damon_ctx 恰有一个内核线程 —— kdamond。主循环(在 mm/damon/core.c)大致:
while (!should_stop):
ops.prepare_access_checks(ctx) # mark pages as old (clear accessed bits)
sleep(sample_interval)
ops.check_accesses(ctx) # re-read accessed bits → nr_accesses
if aggregation_interval_elapsed:
report / apply DAMOS schemes
adaptive_merge_regions()
adaptive_split_regions()
reset nr_accesses
if ops_update_interval_elapsed:
ops.update(ctx) # refresh VMAs, etc.
线程由 damon_start() 启动、damon_stop() 停止。运行时,外部访问 damon_ctx 内部必须经 damon_call() 或 damos_walk(),与 kdamond 循环串行化。
3.3 操作集(damon_operations)
DAMON 经 struct damon_operations 把监控算法与地址空间细节解耦:
struct damon_operations {
enum damon_ops_id id; /* DAMON_OPS_VADDR / _FVADDR / _PADDR */
void (*init)(struct damon_ctx *context);
void (*update)(struct damon_ctx *context);
void (*prepare_access_checks)(struct damon_ctx *context);
unsigned int (*check_accesses)(struct damon_ctx *context);
int (*get_scheme_score)(struct damon_ctx *context, struct damon_target *t, struct damon_region *r, struct damos *scheme);
unsigned long (*apply_scheme)(struct damon_ctx *context, struct damon_target *t, struct damon_region *r, struct damos *scheme, unsigned long *sz_filter_passed);
bool (*target_valid)(struct damon_target *t);
void (*cleanup_target)(struct damon_target *t);
};
id 成员:
---------------------------------------------------------------------------------------------------------------------------- damon_ops_id sysfs 名 说明 ---------------------------------------------------------------------------------------------------------------------------- DAMON_OPS_VADDR vaddr 监控进程虚拟地址空间;从 VMA 初始化 region;需 CONFIG_DAMON_VADDR DAMON_OPS_FVADDR fvaddr 类似 vaddr,但只监控用户指定的固定地址范围,而非全部 VMA DAMON_OPS_PADDR paddr 监控全系统物理地址空间;需 CONFIG_DAMON_PADDR;DAMON_RECLAIM 和 DAMON_LRU_SORT 用它 ----------------------------------------------------------------------------------------------------------------------------
vaddr 和 paddr 都用页面 idle 标志(PAGE_IDLE_FLAG)检测访问,而不碰页表 dirty 位。
4. DAMOS:基于数据访问监控的操作方案
DAMOS 是叠在 DAMON 监控数据之上的策略引擎。一个 DAMOS scheme(struct damos)指定:
(1) 对哪些 region 动手(访问模式过滤器);
(2) 做什么动作;
(3) 多激进(quota);
(4) 什么系统条件下激活(watermark);
4.1 访问模式
struct damos_access_pattern 选中指标全落在以下范围内的 region:
----------------------------------------------------------------------------------------------------------------------------- 字段 含义 ----------------------------------------------------------------------------------------------------------------------------- min_sz_region / max_sz_region Region 字节大小范围 min_nr_accesses / max_nr_accesses 上个聚合窗口内观测到的访问数 min_age_region / max_age_region 该模式稳定持续了多少个聚合间隔 -----------------------------------------------------------------------------------------------------------------------------
DAMON 启动后,从未被访问的冷 region 会是 nr_accesses == 0 且 age 很大。频繁访问的热 region nr_accesses 高、age 经常重置。
4.2 动作
enum damos_action 取值及效果:
----------------------------------------------------------------------------------------------------------------------------- 动作 效果 ----------------------------------------------------------------------------------------------------------------------------- DAMOS_WILLNEED madvise(MADV_WILLNEED) —— 预取该区域 DAMOS_COLD madvise(MADV_COLD) —— 去活页面(往 LRU 尾部赶) DAMOS_PAGEOUT 回收(换出)该区域 DAMOS_HUGEPAGE madvise(MADV_HUGEPAGE) —— 提升为 THP DAMOS_NOHUGEPAGE madvise(MADV_NOHUGEPAGE) —— 从 THP 降级 DAMOS_LRU_PRIO 在 LRU 上提权(往 active 赶) DAMOS_LRU_DEPRIO 在 LRU 上降权(往 inactive 赶) DAMOS_MIGRATE_HOT 迁移到更快的 NUMA 节点(先迁更热的) DAMOS_MIGRATE_COLD 迁移到更慢的 NUMA 节点(先迁更冷的) DAMOS_STAT 无动作 —— 只更新统计计数 -----------------------------------------------------------------------------------------------------------------------------
DAMOS_PAGEOUT 不做 demote:DAMOS_PAGEOUT 是把页回收到 swap,不是降级到慢速 NUMA 层。分层内存降级请用 DAMOS_MIGRATE_COLD。
4.3 配额(Quota)
不限量的话,DAMOS 每秒可能对几个 GB 内存动手。struct damos_quota 做速率限制:
----------------------------------------------------------------------------------------------------------------------------- 字段 含义 ----------------------------------------------------------------------------------------------------------------------------- reset_interval quota 记账窗口,毫秒 ms 每 reset_interval 该 scheme 最多花多少 CPU 时间(毫秒) sz 每 reset_interval 最多对多少字节动手 esz 有效大小 quota(由 ms、sz 及 goals 内部算出) weight_sz region 大小的优先级权重 weight_nr_accesses 访问频率的优先级权重 weight_age region age 的优先级权重 -----------------------------------------------------------------------------------------------------------------------------
Quota 用完后,DAMON 在本 reset_interval 剩余时间停手,damos_stat.qt_exceeds 加一。
4.3.1 用 Goal 做 Quota 自动调优
除了(或代替)硬性的 ms / sz 上限,DAMOS 可用反馈环自动调优有效 quota。Goal 用 struct damos_quota_goal 对象表达。可用 goal 指标(enum damos_quota_goal_metric):
----------------------------------------------------------------------------------------------------------------------------- sysfs 名 含义 ----------------------------------------------------------------------------------------------------------------------------- user_input 手工给的反馈值(用户写 current_value) some_mem_psi_us 每 reset 间隔内系统级 “some” 内存 PSI,µs node_mem_used_bp 某 NUMA 节点内存已用比,万分比 node_mem_free_bp 某 NUMA 节点内存空闲比,万分比 node_memcg_used_bp 某 NUMA 节点上某 cgroup 的内存已用比 node_memcg_free_bp 某 NUMA 节点上某 cgroup 的内存空闲比 active_mem_bp active / 总 LRU 内存比,万分比 inactive_mem_bp inactive / 总 LRU 内存比,万分比 -----------------------------------------------------------------------------------------------------------------------------
DAMOS 增减有效 quota,使观测指标收敛到 target_value。
4.4 Watermark
struct damos_watermarks 按系统级指标决定 scheme 是否激活:
----------------------------------------------------------------------------------------------------------------------------- 字段 含义 ----------------------------------------------------------------------------------------------------------------------------- metric none(总激活)或 free_mem_rate interval 多久检查一次指标,µs high 指标高于此值时停用 scheme mid 指标在 mid 和 low 之间时激活 scheme low 指标低于此值时停用 scheme -----------------------------------------------------------------------------------------------------------------------------
当某 context 的所有 scheme 都不活跃(watermark 未满足),kdamond 停止监控、只轮询 watermark —— 大幅降低开销。
4.5 DAMOS 过滤器
对 region 动手前,DAMOS 可按页用过滤器(struct damos_filter)再筛一遍。过滤器类型(enum damos_filter_type):
----------------------------------------------------------------------------------------------------------------------------- sysfs 名 类型 层 ----------------------------------------------------------------------------------------------------------------------------- anon 匿名页 ops 层 active Active LRU 页 core 层 memcg 属于某 cgroup 的页 ops 层 young 最近访问过(young)的页 core 层 hugepage_size 属于 huge page 的页 core 层 unmapped 未映射的页 core 层 addr 特定地址范围 core 层 target 特定 DAMON target 索引 core 层 -----------------------------------------------------------------------------------------------------------------------------
每个过滤器有 matching 布尔(作用于匹配页 vs. 不匹配页)和 allow 布尔(包含还是排除匹配页)。
过滤器分层:“ops 层”(anon、memcg)由 damon_operations 实现,计入 sz_tried 统计。core 层过滤器在 sz_tried 加一之前求值。
5. sysfs 接口
CONFIG_DAMON_SYSFS 把完整 DAMON API 暴露在 /sys/kernel/mm/damon/admin/ 下。在该层级下读写文件即控制所有监控。
5.1 目录树
/sys/kernel/mm/damon/admin/
└── kdamonds/
├── nr_kdamonds # 写 N 创建 N 个 kdamond 目录
└── 0/
├── state # 写:on|off|commit|update_schemes_stats|...
├── pid # 读:kdamond 线程 PID
├── refresh_ms # sysfs 读的自动刷新间隔
└── contexts/
├── nr_contexts # 目前每 kdamond 只支持 1 个 context
└── 0/
├── avail_operations # 读:可用 ops(vaddr fvaddr paddr)
├── operations # 写:vaddr | fvaddr | paddr
├── addr_unit # 地址值缩放系数
├── monitoring_attrs/
│ ├── intervals/
│ │ ├── sample_us # 采样间隔 µs(默认 5000)
│ │ ├── aggr_us # 聚合间隔 µs(默认 100000)
│ │ ├── update_us # ops 更新间隔 µs(默认 60000000)
│ │ └── intervals_goal/
│ │ ├── access_bp
│ │ ├── aggrs
│ │ ├── min_sample_us
│ │ └── max_sample_us
│ └── nr_regions/
│ ├── min # 最小 region 数(默认 10)
│ └── max # 最大 region 数(默认 1000)
├── targets/
│ ├── nr_targets
│ └── 0/
│ ├── pid_target # 要监控的 PID(仅 vaddr/fvaddr)
│ ├── obsolete_target
│ └── regions/
│ ├── nr_regions # fvaddr:固定 region 数
│ └── 0/
│ ├── start # region 起始地址
│ └── end # region 结束地址
└── schemes/
├── nr_schemes
└── 0/
├── action # willneed|cold|pageout|hugepage|nohugepage|
│ # lru_prio|lru_deprio|migrate_hot|migrate_cold|stat
├── target_nid # migrate 动作的目标 NUMA 节点
├── apply_interval_us # 该 scheme 覆盖 aggr_interval
├── access_pattern/
│ ├── sz/min,max
│ ├── nr_accesses/min,max
│ └── age/min,max
├── quotas/
│ ├── ms
│ ├── bytes
│ ├── reset_interval_ms
│ ├── effective_bytes # 只读:当前有效 quota
│ ├── weights/sz_permil,nr_accesses_permil,age_permil
│ └── goals/nr_goals,0/target_metric,target_value,current_value,nid,path
├── watermarks/metric,interval_us,high,mid,low
├── filters/nr_filters,0/type,matching,allow,memcg_path,
│ addr_start,addr_end,min,max,damon_target_idx
├── dests/ # migrate_hot/cold 用
│ ├── nr_dests
│ └── 0/id,weight
├── stats/ # 只读计数
│ # nr_tried,sz_tried,nr_applied,sz_applied,
│ # sz_ops_filter_passed,qt_exceeds,nr_snapshots,max_nr_snapshots
└── tried_regions/
├── total_bytes
└── 0/start,end,nr_accesses,age,sz_filter_passed
5.2 kdamond 状态命令
往 kdamonds/0/state 写以下字符串之一:
----------------------------------------------------------------------------------------------------------------------------- 命令 效果 ----------------------------------------------------------------------------------------------------------------------------- on 启动 kdamond 线程 off 停止 kdamond 线程 commit 把更新后的参数应用到运行中的 kdamond commit_schemes_quota_goals 把更新后的 quota goal current_value 推给内核 update_schemes_stats 从内核刷新 stats/ 文件 update_schemes_tried_bytes 刷新 tried_regions/total_bytes update_schemes_tried_regions 用当前命中的 region 填充 tried_regions/ clear_schemes_tried_regions 清空 tried_regions/ update_schemes_effective_quotas 刷新 quotas/effective_bytes update_tuned_intervals 用自动调优后的值刷新 intervals/ -----------------------------------------------------------------------------------------------------------------------------
6. 实战例子
注: 5.4 内核没有这个机制,6.1内核中有。
6.1 主动回收:换出冷页
本例搭建一个物理地址 DAMOS scheme,换出约 120 秒未被访问的内存区域。效果与内核内置 CONFIG_DAMON_RECLAIM 类似。
#!/bin/bash ADMIN=/sys/kernel/mm/damon/admin # Create one kdamond with one context echo 1 > $ADMIN/kdamonds/nr_kdamonds echo 1 > $ADMIN/kdamonds/0/contexts/nr_contexts # Use the physical address space ops (system-wide, no PID needed) echo paddr > $ADMIN/kdamonds/0/contexts/0/operations # Tune monitoring intervals echo 5000 > $ADMIN/kdamonds/0/contexts/0/monitoring_attrs/intervals/sample_us echo 100000 > $ADMIN/kdamonds/0/contexts/0/monitoring_attrs/intervals/aggr_us # One target (paddr needs exactly one target; no pid_target needed) echo 1 > $ADMIN/kdamonds/0/contexts/0/targets/nr_targets # Create one DAMOS scheme echo 1 > $ADMIN/kdamonds/0/contexts/0/schemes/nr_schemes SCHEME=$ADMIN/kdamonds/0/contexts/0/schemes/0 # Action: page out echo pageout > $SCHEME/action # Access pattern: size [4KB, unlimited], nr_accesses [0,0], age [24000,max] # age 24000 = 24000 aggr intervals × 100ms = 2,400,000ms ≈ 2400s ... too long # Better: age in aggr intervals. 120s / 100ms = 1200 aggregation intervals echo 4096 > $SCHEME/access_pattern/sz/min echo 18446744073709551615 > $SCHEME/access_pattern/sz/max echo 0 > $SCHEME/access_pattern/nr_accesses/min echo 0 > $SCHEME/access_pattern/nr_accesses/max echo 1200 > $SCHEME/access_pattern/age/min echo 18446744073709551615 > $SCHEME/access_pattern/age/max # Quota: use at most 10ms CPU, page out at most 128MiB per second echo 10 > $SCHEME/quotas/ms echo 134217728 > $SCHEME/quotas/bytes # 128 MiB echo 1000 > $SCHEME/quotas/reset_interval_ms # Prioritize older (colder) regions first echo 0 > $SCHEME/quotas/weights/sz_permil echo 0 > $SCHEME/quotas/weights/nr_accesses_permil echo 1000 > $SCHEME/quotas/weights/age_permil # Watermarks: only activate when free memory is between 20% and 50% echo free_mem_rate > $SCHEME/watermarks/metric echo 5000000 > $SCHEME/watermarks/interval_us # check every 5s echo 500 > $SCHEME/watermarks/high # deactivate above 50% echo 400 > $SCHEME/watermarks/mid # activate at 40% echo 200 > $SCHEME/watermarks/low # deactivate below 20% # Start monitoring echo on > $ADMIN/kdamonds/0/state # 实测,6.1内核上只有这步失败了,且没有任何失败log打印出来
6.1.1 最后一步失败AI分析:
原因找到了:这棵 GKI 内核没有编译 CONFIG_DAMON_PADDR,echo paddr > operations 这步其实只是“写进去了”,真正的注册校验推迟到 echo on 时才做,于是在那里静默返回 -EINVAL。
完整因果链:
(1) 配置里没有 paddr ops。arch/arm64/configs/gki_defconfig:134-136 只有:
# 实测 zcat /proc/config.gz | grep DAMON 的确只使能了这三个,userdebug版本默认也只使能了这三个 CONFIG_DAMON=y CONFIG_DAMON_VADDR=y CONFIG_DAMON_SYSFS=y
没有 CONFIG_DAMON_PADDR。mm/damon/Makefile:5 里 paddr.o 是 obj-$(CONFIG_DAMON_PADDR),所以物理地址 ops 集(damon_paddr)从未通过 damon_register_ops() 注册进内核。
(2) 前面所有步骤都只是纯解析、不校验。operations_store()(sysfs.c:769-783)只做字符串到枚举的映射("paddr" 在 damon_sysfs_ops_strs[] 表里,sysfs.c:626-630),把 ops_id 存下来就返回成功,根本不检查该 ops 是否已注册。intervals、access_pattern、quotas、watermarks 同理,都是 kstrtoul 存值而已。所以脚本一路绿灯。
(3) echo on > state 才触发真实校验。路径是:
state_store → damon_sysfs_turn_damon_on()(sysfs.c:1459)→ damon_sysfs_build_ctx()(sysfs.c:1438)→ damon_sysfs_apply_inputs()(sysfs.c:1319)→ 第一行就是 damon_select_ops(ctx, ops_id)(sysfs.c:1324)→ core.c:99-113 发现 __damon_is_registered_ops(DAMON_OPS_PADDR) 为假,返回 -EINVAL。
(4) 静默无 log 是设计如此。damon_sysfs_build_ctx 收到错误后 damon_destroy_ctx() 销毁刚建的 context,state_store 原样把 -EINVAL 返回给 write()。这条路径上一个 printk/pr_err 都没有,所以 dmesk 里什么都看不到;shell 层面最多只有 write error: Invalid argument。
上机一步验证:
cat /sys/kernel/mm/damon/admin/kdamonds/0/contexts/0/avail_operations //实测只打印出来了 vaddr fvaddr
avail_operations_show()(sysfs.c:745-758)只列已注册的 ops。这颗内核上预计输出 vaddr 和 fvaddr(两者都在 vaddr.c:711-714 注册),没有 paddr。也可以 zcat /proc/config.gz | grep DAMON 直接看配置。
修复:
根治:给内核开 CONFIG_DAMON_PADDR=y(Kconfig 依赖只有 DAMON && MMU,arm64 满足)重编。GKI 场景下加在你们项目的 vendor config fragment 里,别直接改 gki_defconfig,避免和 GKI 升级冲突。
临时绕过:改用 vaddr 或 fvaddr + 写 targets/0/pid_target 监控指定进程——但这只能做进程级,做不了你要的系统级冷页回收。
顺带提醒:DAMON_RECLAIM、DAMON_LRU_SORT 都 depends on DAMON_PADDR,不开这个配置它们也一并不可用。
几秒后看效果:
# Trigger stats refresh echo update_schemes_stats > $ADMIN/kdamonds/0/state STATS=$ADMIN/kdamonds/0/contexts/0/schemes/0/stats echo "Regions tried: $(cat $STATS/nr_tried)" echo "Bytes tried: $(cat $STATS/sz_tried)" echo "Regions applied: $(cat $STATS/nr_applied)" echo "Bytes applied: $(cat $STATS/sz_applied)" echo "Quota exceeded: $(cat $STATS/qt_exceeds)"
6.2 工作集估算:按 Region 统计访问
用 DAMOS_STAT 只观察哪些 region 热,不做任何动作:
ADMIN=/sys/kernel/mm/damon/admin
echo 1 > $ADMIN/kdamonds/nr_kdamonds
echo 1 > $ADMIN/kdamonds/0/contexts/nr_contexts
echo vaddr > $ADMIN/kdamonds/0/contexts/0/operations
# Monitor a specific process
echo 1 > $ADMIN/kdamonds/0/contexts/0/targets/nr_targets
echo $(pidof myapp) > $ADMIN/kdamonds/0/contexts/0/targets/0/pid_target
# Scheme: stat action, capture all regions
echo 1 > $ADMIN/kdamonds/0/contexts/0/schemes/nr_schemes
SCHEME=$ADMIN/kdamonds/0/contexts/0/schemes/0
echo stat > $SCHEME/action
echo 0 > $SCHEME/access_pattern/sz/min
echo 18446744073709551615 > $SCHEME/access_pattern/sz/max
echo 0 > $SCHEME/access_pattern/nr_accesses/min
echo 18446744073709551615 > $SCHEME/access_pattern/nr_accesses/max
echo 0 > $SCHEME/access_pattern/age/min
echo 18446744073709551615 > $SCHEME/access_pattern/age/max
echo on > $ADMIN/kdamonds/0/state
# After a monitoring window, snapshot matched regions
echo update_schemes_tried_regions > $ADMIN/kdamonds/0/state
# Read which regions matched
# 实测最后打印报错没有这些文件
for dir in $SCHEME/tried_regions/[0-9]*/; do
start=$(cat $dir/start)
end=$(cat $dir/end)
acc=$(cat $dir/nr_accesses)
age=$(cat $dir/age)
printf "0x%x - 0x%x accesses=%d age=%d\n" $start $end $acc $age
done
注: 修复最后一步报错后,实测上面脚本执行后 监控 Active(anon) 和 Inactive(anon) 恒没有变化!且第二个脚本的所有成员恒为0,没发现啥实际价值!
3. 内存分层:冷页降级到慢速 NUMA 内存
在有 CXL 附加内存或慢速 NUMA 节点的系统上,用 DAMOS_MIGRATE_COLD 降级冷页:
ADMIN=/sys/kernel/mm/damon/admin echo 1 > $ADMIN/kdamonds/nr_kdamonds echo 1 > $ADMIN/kdamonds/0/contexts/nr_contexts echo paddr > $ADMIN/kdamonds/0/contexts/0/operations echo 1 > $ADMIN/kdamonds/0/contexts/0/targets/nr_targets echo 1 > $ADMIN/kdamonds/0/contexts/0/schemes/nr_schemes SCHEME=$ADMIN/kdamonds/0/contexts/0/schemes/0 # Demote cold, large regions to NUMA node 2 (slow tier) echo migrate_cold > $SCHEME/action echo 2 > $SCHEME/target_nid # Pattern: any size, zero accesses, aged at least 60 aggr intervals (6s at 100ms) echo 0 > $SCHEME/access_pattern/sz/min echo 18446744073709551615 > $SCHEME/access_pattern/sz/max echo 0 > $SCHEME/access_pattern/nr_accesses/min echo 0 > $SCHEME/access_pattern/nr_accesses/max echo 60 > $SCHEME/access_pattern/age/min echo 18446744073709551615 > $SCHEME/access_pattern/age/max # Demote at most 256MiB per second echo 0 > $SCHEME/quotas/ms echo 268435456 > $SCHEME/quotas/bytes echo 1000 > $SCHEME/quotas/reset_interval_ms echo on > $ADMIN/kdamonds/0/state
多迁移目标:要在多层之间按权重分布,用 scheme 下的 dests/ 子目录代替 target_nid。建 nr_dests 个条目,每个设 id(NUMA 节点)和 weight。
4. 内置 DAMOS 模块
4.1 CONFIG_DAMON_RECLAIM
DAMON_RECLAIM(mm/damon/reclaim.c)是预制内核模块,对物理地址空间跑 DAMOS_PAGEOUT scheme。它做轻量主动回收,适合温和内存压力,是 kswapd 被动扫描的补充。
内核配置:CONFIG_DAMON_RECLAIM(依赖 CONFIG_DAMON_PADDR)
模块参数(经 /sys/module/damon_reclaim/parameters/ 读写,只有使能了此配置才有这些文件节点):
-----------------------------------------------------------------------------------------------------------------------------
参数 默认 说明
-----------------------------------------------------------------------------------------------------------------------------
enabled N 运行时开/关 DAMON_RECLAIM
commit_inputs N 置 Y 则不重启重读所有参数
min_age 120,000,000 µs(120s) 冷 region 被回收前的最小 age
quota_mem_pressure_us 0(关闭) 自动调 quota 以达成该 PSI 水平(每 reset 间隔 µs)
quota_autotune_feedback 0(关闭) 自动调优的手工反馈值(目标 10,000)
skip_anon N Y 则跳过匿名页(只收文件页)
monitor_region_start 0 监控的物理地址范围起点
monitor_region_end 0 监控的物理地址范围终点(0 = 最大 System RAM 区域)
-----------------------------------------------------------------------------------------------------------------------------
默认 quota:每 1 秒窗口最多 10ms CPU、128MiB 字节。默认 watermark:空闲内存在 20%~50% 之间激活。
# Enable DAMON_RECLAIM with more aggressive settings echo Y > /sys/module/damon_reclaim/parameters/enabled # Reclaim pages cold for 60 seconds instead of 120 echo 60000000 > /sys/module/damon_reclaim/parameters/min_age # Apply changes without restarting echo Y > /sys/module/damon_reclaim/parameters/commit_inputs
何时用 DAMON_RECLAIM vs. 直接 sysfs 配置:DAMON_RECLAIM 是全系统主动回收的粗粒度开关。如需细粒度控制 —— 按 cgroup 定向、自定义 watermark、多 scheme、或迁移而非换出 —— 直接经 sysfs 配 DAMON。
4.2 CONFIG_DAMON_LRU_SORT
DAMON_LRU_SORT(mm/damon/lru_sort.c)用两个 DAMOS scheme 在 kswapd 看到页面之前先影响 LRU:热页提权(DAMOS_LRU_PRIO)、冷页降权(DAMOS_LRU_DEPRIO)。让后续 kswapd 回收更精准,又不绕过 LRU 机制。
内核配置:CONFIG_DAMON_LRU_SORT(依赖 CONFIG_DAMON_PADDR)
关键模块参数(/sys/module/damon_lru_sort/parameters/):
----------------------------------------------------------------------------------------------------------------------------- 参数 默认 说明 ----------------------------------------------------------------------------------------------------------------------------- enabled N 开/关 active_mem_bp 0(关闭) 自动调优以达成该 active/总 LRU 比 autotune_monitoring_intervals N 自动调优 sample/aggr 间隔 -----------------------------------------------------------------------------------------------------------------------------
4.3 CONFIG_DAMON_STAT
DAMON_STAT(mm/damon/stat.c)对物理地址空间跑 DAMON,把聚合访问统计暴露成简单指标,只观测、不做内存管理动作,适合可观测性。
内核配置:CONFIG_DAMON_STAT
CONFIG_DAMON_STAT_ENABLED_DEFAULT 控制开机是否自动启动。
5. DAMON 与 MGLRU
DAMON 和多代 LRU(MGLRU)都跟踪内存访问模式,但粒度和目的不同:
-----------------------------------------------------------------------------------------------------------------------------
DAMON MGLRU
-----------------------------------------------------------------------------------------------------------------------------
粒度 damon_region(页到 MB,自适应大小) 按页(folio)代
机制 每间隔每 region 采样一个随机地址 走页表、更新 folio 代
输出 每 region nr_accesses、age 每 folio 代号(0=最老)
作用于 DAMOS scheme(策略无关) kswapd 按代驱逐
开销 经间隔可配;典型 <1% 分摊到页表遍历
用例 自定义策略、分层、预取、工作集 sizing 通用回收排序
-----------------------------------------------------------------------------------------------------------------------------
两者互补:MGLRU 在 LRU 内高效排序供回收,DAMON 提供 region 级访问数据,能表达 MGLRU 表达不了的动作 —— 主动降级到 CXL 内存、THP 提升决策、容量规划用的工作集报告。
无冲突:DAMON 和 MGLRU 可同时跑。DAMON_LRU_SORT 就是专门与 MGLRU 协作的,在按代驱逐之前先摆弄 LRU 优先级。
6. 性能开销
DAMON 开销主要由三个参数决定:
overhead ≈ (nr_regions × cost_per_region) / sample_interval ----------------------------------------------------------------------------------------------------------------------------- 参数 对开销的影响 对精度的影响 ----------------------------------------------------------------------------------------------------------------------------- sample_interval(↑) kdamond CPU 时间减少 采样变稀 → 可能漏掉短命访问 aggr_interval(↑) 无直接开销影响 窗口变大,平滑掉瞬时尖刺 max_nr_regions(↑) region 越多,每采样页表查找越多 空间分辨率更细 -----------------------------------------------------------------------------------------------------------------------------
典型配置 CPU 开销远低于 1%。DAMON_RECLAIM 默认(5ms 采样、100ms 聚合、最多 1000 region)就是为生产环境近零影响设计的。
开销调优指南:
(1) 后台可观测(工作集 sizing)优先长间隔:sample_us=50000,aggr_us=1000000.
(2) reactive 策略(压力下主动回收)用默认,或开 intervals_goal 自动调优让 DAMON 动态调.
7. 内核配置
----------------------------------------------------------------------------------------------------------------------------- 配置项 说明 依赖 ----------------------------------------------------------------------------------------------------------------------------- CONFIG_DAMON DAMON 核心框架 — CONFIG_DAMON_VADDR 虚拟地址 ops(vaddr、fvaddr) DAMON、MMU,选中 PAGE_IDLE_FLAG CONFIG_DAMON_PADDR 物理地址 ops(paddr) DAMON、MMU,选中 PAGE_IDLE_FLAG CONFIG_DAMON_SYSFS /sys/kernel/mm/damon/ 下 sysfs 接口 DAMON、SYSFS CONFIG_DAMON_RECLAIM 内置主动回收模块 DAMON_PADDR CONFIG_DAMON_LRU_SORT 内置 LRU 排序模块 DAMON_PADDR CONFIG_DAMON_STAT 内置访问统计模块 DAMON_PADDR CONFIG_DAMON_STAT_ENABLED_DEFAULT 开机启用 DAMON_STAT DAMON_STAT CONFIG_DAMON_KUNIT_TEST DAMON 核心 KUnit 测试 DAMON、KUNIT=y CONFIG_DAMON_VADDR_KUNIT_TEST vaddr ops KUnit 测试 DAMON_VADDR、KUNIT=y CONFIG_DAMON_SYSFS_KUNIT_TEST sysfs 接口 KUnit 测试 DAMON_SYSFS、KUNIT=y -----------------------------------------------------------------------------------------------------------------------------
8. 关键源文件
include/linux/damon.h: 所有公开结构与枚举:damon_region、damon_target、damon_ctx、damon_attrs、damon_operations、damos、damos_access_pattern、damos_quota、damos_watermarks、damos_filter、damos_stat
mm/damon/core.c: kdamond 主循环、region split/merge、DAMOS 引擎、damon_new_ctx()、damon_start()、damon_stop()、damon_register_ops()、damon_select_ops()
mm/damon/vaddr.c: DAMON_OPS_VADDR 与 DAMON_OPS_FVADDR 实现:基于 VMA 的 region 初始化、走页表检查访问
mm/damon/paddr.c: DAMON_OPS_PADDR 实现:物理地址 region 管理、经 damon_pa_mkold() 按 folio 检查访问
mm/damon/ops-common.c: 共享 helper:damon_get_folio()、damon_ptep_mkold()、damon_folio_mkold()
mm/damon/sysfs.c: sysfs 目录树:kdamond、context、target、region、monitoring_attrs
mm/damon/sysfs-schemes.c: scheme 的 sysfs:access_pattern、quota、watermark、filter、stat、tried_regions
mm/damon/sysfs-common.c: 共享 sysfs helper:min/max 对的 damon_sysfs_ul_range
mm/damon/reclaim.c: DAMON_RECLAIM 内置模块:模块参数、DAMOS_PAGEOUT scheme 配置
mm/damon/lru_sort.c: DAMON_LRU_SORT 内置模块:DAMOS_LRU_PRIO 与 DAMOS_LRU_DEPRIO scheme
mm/damon/stat.c: DAMON_STAT 内置模块:只观测的 DAMOS_STAT scheme
mm/damon/modules-common.c: 内置模块共享 helper
mm/damon/Kconfig: 所有 CONFIG_DAMON_* 选项
9. Further reading
9.1 内核文档:
Documentation/admin-guide/mm/damon/ 管理员指南(DAMON 概念、sysfs 接口、DAMOS scheme 配置、内置模块);
Documentation/mm/damon/design.rst 设计文档(自适应按 region 采样算法、监控 ops 抽象、DAMOS 引擎)。
mm/damon/ —— 完整的 DAMON 实现:核心采样循环、虚拟地址与物理地址操作集、sysfs 接口,以及内置方案模块
9.2 LWN 文章
《Memory-management optimization with DAMON》—— Jonathan Corbet 对 DAMON 设计与访问监控方法的概述(2020 年) //https://lwn.net/Articles/812707/
《Using DAMON for proactive reclaim》—— DAMON_RECLAIM 如何开发、以及与仅用 kswapd 回收对比评估的过程(2021 年) //https://lwn.net/Articles/863753/
9.3 相关文档
reclaim.md —— 页面回收;DAMON_RECLAIM 针对 DAMON 识别出的冷页,而不是仅依赖 LRU 新老程度
psi.md —— 压力停滞信息;DAMON_RECLAIM 可以用 PSI 内存压力作为反馈来自动调优其 quota
mglru.md —— 多代 LRU;在 LRU 链表内部按页粒度做回收排序,与 DAMON 互补
10. 补充
10.1 vaddr 和 fvaddr 的区别
在 DAMON 里 vaddr 和 fvaddr 都是监控进程虚拟地址的 ops,代码都在 mm/damon/vaddr.c,采样原理一样,区别只在监控范围怎么定:
(1) vaddr:监控整个进程
对应 DAMON_OPS_VADDR,sysfs 里写 operations = vaddr。初始 region 从目标进程的全部 VMA 划分出来。ops.update() 会定期刷新 VMA,跟随 mmap/munmap/mremap 变化,新增的 VMA 会自动纳入,释放的会自动剔除。用法:只需给 targets/0/pid_target,不用填地址。适合:看整个应用的工作集、冷热分布。
(2) fvaddr:只监控你指定的固定地址段
对应 DAMON_OPS_FVADDR,sysfs 里写 operations = fvaddr,即 Fixed VADDR。不看全部 VMA,只看你在 sysfs 里手工填的:targets/0/regions/nr_regions + regions/0/start,end、regions/1/start,end... DAMON 就限定在这几个 [start,end) 内做自适应 split/merge,不会扩展到其他 VMA。用法:既要给 pid_target,还要给具体的起止地址。适合:只关心堆、某个大 buffer、某个库映射,想减少开销和噪声;长期监控已知地址段。
简单记:vaddr = 全进程自动跟随 VMA,fvaddr = 指定固定范围精准监控。
posted on 2026-10-08 20:38 Hello-World3 阅读(2) 评论(0) 收藏 举报
浙公网安备 33010602011771号