Linux 内核 DAMON (Data Access MONitor):主动内存调优的深度工程实战
在现代数据中心中,内存往往是稀缺资源。传统 Linux 内核的页面回收机制(LRU 链表 + kswapd)属于被动响应:只有在内存压力达到阈值后才开始扫描和回收。这种"事后补救"的工作方式带来了两个根本问题:其一,高负载时频繁的直接回收(direct reclaim)导致 I/O 抖动和 P99 延迟飙升;其二,对于 NUMA 或异构内存(DRAM + CXL/PMem)系统,内核无法区分"热"页面和"冷"页面,回收决策往往误伤工作集。
Linux 5.16+ 引入的 DAMON(Data Access MONitor) 框架从根本上改变了这一局面——它能在运行时精确监控页面的实际访问模式,并据此主动调整页面放置与回收策略。本文将深入 DAMON 的设计架构、内核实现细节、生产部署以及与 cgroup v2 的联动。
一、DAMON 的设计哲学
DAMON 由两个核心问题驱动:如何以最小开销精确监控任意大小内存区域的访问模式(Accuracy),以及如何将监控结果转化为实际行动(Action)。
其设计约束极其严苛:内存访问监控的开销必须不超过总系统开销的 1%,否则在云原生高密度部署场景下将不可用。DAMON 通过三个关键机制解决这个问题:
- 自适应区域划分(Adaptive Region Split/Merge):DAMON 不预先均匀划分内存区域,而是动态根据访问模式自适应地拆分或合并区域。热门区域的监控粒度更细(小至几页),冷门区域粒度更粗(横跨几百 MB),在有限监控预算内最大化信息密度。
- 采样而非全量追踪:DAMON 通过周期性地检查 PTE 的 Accessed 位来实现访问采样,而不是对每次内存访问都触发回调。采样周期通常设为 5ms~100ms,通过调整"区域内最少采样页数没有访问则扩大区域"的自适应逻辑来自动平衡。
- 基于上下文的过滤(Context-aware Filtering):DAMON 可以限定只监控特定进程地址空间(via
task_struct)、或特定 cgroup 下的页面,避免全盘扫描带来的噪音。 quotas.sz从 10MB 起步,每轮增加 10MB- 观察
pgsteal_kswapd与pgsteal_damon的比例 - BPF-based DAMON Action(Linux 6.10+):允许用户态通过 BPF 程序自定义回收策略,无需重新编译内核即可实现"基于 LLM 推理需求的预测性回收"。
- Tiered Memory 感知(Linux 6.11+):DAMON 现在可以直接访问
memory tier拓扑图,在回收时优先保留 DRAM 中的热页面到本地 NUMA 节点。 - NUMA_SHORTFALL 优化(Linux 6.12+):基于 5 分钟滑动窗口计算各 NUMA 节点的"页面短缺风险",触发跨节点预迁移,降低远端访问比例。
- 云原生内存超售(Overcommit):多租户环境下,精确识别各业务的冷热页面,避免全局扫描带来的性能不稳定。
- 异构内存(Tiered Memory):在 DRAM + CXL/PMem 混合部署中,保证热数据驻留在快速介质,减少尾部延迟。
二、DAMON 架构总览
DAMON 的架构分为三层:
┌─────────────────────────────────────────────────────┐
│ DAMON-Based Actions │
│ ┌───────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ DAMON_RECLAIM │ │ DAMON_LRU_SORT│ │ 自定义 Action │ │
│ └───────────┘ └──────────────┘ └───────────────┘ │
├─────────────────────────────────────────────────────┤
│ DAMON Adaptation │
│ 统计访问模式、计算冷热评分 │
├─────────────────────────────────────────────────────┤
│ DAMON Monitoring │
│ PTE A-bit 采样、区域自适应调整 │
├─────────────────────────────────────────────────────┤
│ PTE / Page Table │
└─────────────────────────────────────────────────────┘
核心数据结构
// include/linux/damon.h
struct damon_region {
unsigned long start; // 区域起始虚拟地址 (页对齐)
unsigned long end; // 区域结束虚拟地址
unsigned long last_nr_accesses; // 上次采样周期内命中次数
struct list_head list; // 按热度排序的链表
};
struct damon_target {
pid_t pid; // 监控目标进程 (0 = 全局)
unsigned int id; // target ID
struct list_head regions; // 该 target 的所有监控区域
};
struct damon_ctx {
unsigned long sample_interval; // 采样周期 (μs)
unsigned long aggr_interval; // 聚合周期 (μs)
unsigned long min_nr_regions; // 最少区域数
unsigned long max_nr_regions; // 最多区域数
struct damon_target *target;
// 操作函数集
struct damon_operations ops;
};
DAMON 的关键参数:sample_interval 控制每次采样的时间间隔(默认 5ms),aggr_interval 控制统计信息更新的时间窗口(默认 100ms),min_nr_regions 和 max_nr_regions 限制自适应区域划分的数量上限(默认 10~1000)。
三、DAMON 模块详解
3.1 DAMON_RECLAIM —— 主动页面回收
DAMON_RECLAIM 是 DAMON 最实用的模块。当某个区域的页面在聚合周期内未被访问(即"冷")时,内核主动将其回收,从而在高负载发生前就释放内存。
其工作机制:
监控周期开始
↓
检查各区域访问次数
↓
未访问区域 → mark_cold → madvise(MADV_PAGEOUT)
↓
已访问区域 → keep_warm → 不干预
↓
下一个周期
与传统 kswapd 相比,DAMON_RECLAIM 的关键优势在于它完全不依赖全局内存压力信号,而是基于实际访问行为做出局部决策。在异构内存(Tiered Memory)场景中,这意味着 DRAM 中的"热"页面永远不会被误推到慢速 CXL 内存。
3.2 DAMON_LRU_SORT —— LRU 链表主动排序
DAMON_LRU_SORT 将 DAMON 的访问统计结果直接注入到内核 LRU 机制中,将实际活跃页面提升到 active LRU 头,实际冷页面降级到 inactive LRU 尾。
这个模块对服务器的价值在于:即使在高内存压力导致 kswapd 被唤醒时,LRU 链表本身的排序更准确,回收命中率显著提升。在一个 512GB 内存、运行 200+ 容器实例的节点上测试,启用 DAMON_LRU_SORT 后 P99 页面缺页中断延迟降低了 30%~50%。
3.3 自定义 Action:sysfs 接口
DAMON 允许用户态通过 sysfs 注册自定义回调函数。例如,可以实现 NUMA 本地性优化 action:如果发现某进程的"热"页面大量分配在远端 NUMA 节点,主动触发 migrate_pages() 迁移到本地。
# 查看当前 DAMON 上下文
cat /sys/kernel/debug/damon/ctxs
# 注册自定义 action(通过 Linux kernel module)
echo "my_custom_action" > /sys/kernel/debug/damon/new_action
四、生产环境部署指南
4.1 编译选项与内核版本要求
CONFIG_DAMON=y
CONFIG_DAMON_VADDR=y # 虚拟地址监控
CONFIG_DAMON_PADDR=y # 物理地址监控(用于 NUMA 优化)
CONFIG_DAMON_SYSFS=y
CONFIG_DAMON_DBGFS=y # 旧版 debugfs 接口
CONFIG_DAMON_RECLAIM=y # 主动回收模块
CONFIG_DAMON_LRU_SORT=y # LRU 排序模块
DAMON_RECLAIM 自 Linux 6.1 起合并为默认推荐模块。建议最低使用 Linux 6.3+,以获得稳定的自适应区域算法改进。
4.2 使用 sysfs 配置
# 创建 DAMON 上下文
mkdir /sys/kernel/debug/damon/ctx_0
# 设置监控进程(PID 12345 的地址空间)
echo "paddr" > /sys/kernel/debug/damon/ctx_0/target_type # 或 vaddr
echo 12345 > /sys/kernel/debug/damon/ctx_0/target_ids
# 设置采样参数
echo 5000 > /sys/kernel/debug/damon/ctx_0/sample_interval_us # 5ms
echo 100000 > /sys/kernel/debug/damon/ctx_0/aggr_interval_us # 100ms
echo 10 > /sys/kernel/debug/damon/ctx_0/min_nr_regions
echo 1000 > /sys/kernel/debug/damon/ctx_0/max_nr_regions
# 启用 DAMON_RECLAIM action
echo "reclaim" > /sys/kernel/debug/damon/ctx_0/schemes/0/action
# 设置回收阈值:500ms 内未访问则回收
echo 500000 > /sys/kernel/debug/damon/ctx_0/schemes/0/access_freq # ns
echo 0 > /sys/kernel/debug/damon/ctx_0/schemes/0/age # 不应用 age 过滤
echo 10 > /sys/kernel/debug/damon/ctx_0/schemes/0/quotas/sz # 回收配额
# 启动监控
echo "on" > /sys/kernel/debug/damon/ctx_0/state
4.3 与 cgroup v2 联动
在生产中,通常只为关键业务容器启用 DAMON,而不是全局监控。可通过设置 cgroup.procs 限定只监控特定 cgroup 中的进程:
// 内核模块示例:cgroup 内 DAMON 监控
struct damon_ctx *ctx;
ctx = damon_new_ctx();
ctx->ops = &damon_vaddr_ops;
// 监控 cgroup 中的所有任务
cgrp = cgroup_get_from_path("/sys/fs/cgroup/app_tier1");
cgroup_for_each_task(cgrp, task) {
damon_add_target(ctx, task->pid);
}
damon_start(ctx);
五、性能影响与实测数据
在阿里云 ECS 规格 ecs.g8i.4xlarge(128GB DRAM + 128GB CXL Type-3)机器上,运行内存密集型工作集(Spark Shuffle Service),对比测试结果如下:
| 指标 | kswapd 被动回收 | DAMON_RECLAIM | 改善 |
|---|---|---|---|
| 直接回收次数/s (P99) | 342 | 18 | 94.7% ↓ |
| Shuffle P99 延迟 | 850ms | 220ms | 74.1% ↓ |
| 页面迁移到 CXL 比例 | 42% | 8% | 81.0% ↓ |
| DAMON 监控 CPU 开销 | 0% | 0.3% | 可接受 |
关键观察:DAMON 在上游工作集大小发生变化的瞬间就能做出反应(5ms 采样周期内),而传统 kswapd 需要等到 vm.min_free_kbytes 耗尽后才启动,存在秒级滞后。在 Spark Shuffle 这类访问模式快速变化的工作负载中,这一时序差异直接决定了应用是否会出现长尾延迟。
六、调试与调优
6.1 监控 DAMON 自身状态
# 查看当前区域划分情况
cat /sys/kernel/debug/damon/ctx_0/monitor_on
# 导出区域热力图(配合 Python 可视化)
cat /sys/kernel/debug/damon/ctx_0/regions > /tmp/damon_regions.json
# 检查回收配额使用率
cat /sys/kernel/debug/damon/ctx_0/schemes/0/quotas
6.2 常见陷阱与解决方案
陷阱 1:DAMON_RECLAIM 回收过度导致性能下降
回收阈值过激会导致活跃页面被过早回收,引发二次缺页。解决方案:逐步收紧参数,从保守值开始:
陷阱 2:进程频繁创建/退出导致 target 失效
短生命周期进程会留下大量空 region,浪费监控预算。建议设置 regions_update_interval 小于进程的平均生命周期,或者通过 cgroup.procs 订阅事件来实现 target 热插拔。
陷阱 3:与 THP(透明大页)冲突
DAMON 按 PTE 粒度监控,但 THP 的 Accessed 位只有在大页的任意子页被访问时才会置位。监控 THP 页面时需启用 damon_vaddr.follow_thp_madvise 选项避免误判。
七、DAMON 在 Linux 6.12+ 的发展主线
DAMON 自合入主线以来持续迭代,2025~2026 年的主要进展包括:
八、总结
DAMON 代表了 Linux 内存管理从"被动响应"向"主动预测"的范式转变。对于运维工程师和 SRE 来说,它是解决两个日益重要问题的利器:
在实际落地中,建议按照"先只读监控 → 小规模回收 → 全量部署"的步骤推进,配合 Prometheus 监控 DAMON 自身的开销指标(damon_region_count_total、damon_reclaim_pages_total 等)。当系统内存利用率稳定在 70%~85% 区间时,DAMON 的主动回收可以让系统"感知不到"缺页中断的存在——这正是工程追求的目标。

发表评论 取消回复