内存管理-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. 架构

7-1


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)    收藏  举报

导航