本文结合 DeepSeek DSec 论文中的内存回收方案,浅谈 DAMON 与 virtio-balloon 如何配合,让虚拟机里长期不用的文件缓存,不再一直占着宿主机的内存。

为便于理解,下面只讨论 Linux 虚拟机中的文件缓存回收。

What

先把最容易混淆的一点说清楚:

1
2
3
虚拟机里的文件缓存很久没有被使用
≠
这些内存已经还给了宿主机

要把这件事做好,需要两个环节:先在 Guest 中释放缓存,再通知 Host 回收对应的内存。

DAMON:找出哪些缓存很久没用了

DAMON(Data Access MONitoring) 是 Linux 内核的数据访问监测机制,可以通过采样,了解哪些内存经常被访问、哪些内存一段时间没有被访问。

可以简单理解为:

1
2
3
经常被访问的内存 → 热内存

一段时间没有被访问的内存 → 冷内存

在本文方案中,利用 DAMON 找出冷的文件缓存,再通过内核的回收机制,释放其中符合条件的缓存页。

注意,不是监测到“冷”,页面就自动变成了空闲;还需要执行回收。

virtio-balloon:告诉 Host 哪些页面已经空闲

这里使用的是virtio-balloon的 Free Page Reporting(空闲页上报) 功能,而不是传统的气球膨胀、收缩操作。

它让 Guest 可以告诉 Host:

这批页面我现在没有使用,你可以回收它们背后占用的宿主机内存。

因此,两者的分工可以概括为:

1
2
3
4
5
DAMON配合内核回收:
把符合条件的冷缓存释放成 Guest 空闲页

virtio-balloon空闲页上报:
把这些空闲页报告给 Host,由 Host 回收对应的内存

Why

在 DSec 所讨论的 Agent 工作负载中,文件可能只被读取一次,之后就不再访问,但内容仍然留在Guest的Page Cache中。

如果 Guest 自己没有明显的内存压力,这些缓存可能继续保留。

对单个 Guest 来说,保留缓存并不奇怪;但当一台 Host 上运行许多虚拟机时,每个Guest都留着一些长期不用的缓存,就可能占用大量宿主机内存。

Guest 觉得自己的内存还够用,不代表 Host 的内存也很宽裕。

仅有空闲页上报还不够:这些冷缓存仍然占着 Guest 的页面,尚未成为可以上报的空闲页。

因此,需要把两个动作接起来:

1
2
3
4
5
主动回收长期不用的文件缓存
↓
形成 Guest 空闲页
↓
上报给 Host,释放对应的宿主机内存

这套方案的目的可以概括为:

不让长期不用的缓存,一直占着本来可以交给其他工作负载使用的内存。

基础 Example

假设一个 Agent 在虚拟机里读取了一批源码文件,完成分析后,不再访问这些文件,但虚拟机仍然继续运行。

为简化示例,假设文件内容已经保存在磁盘上,下面只讨论可以回收的内存缓存副本。

只使用空闲页上报

1
2
3
4
5
6
7
读取源码文件
↓
文件内容进入 Guest 的 Page Cache
↓
后续不再访问,但缓存仍然保留
↓
这些页面还不是空闲页,不能直接作为空闲页上报

virtio-balloon 的空闲页上报并不会因为某个缓存“很久没用”,就直接把它当作空闲内存。

配合 DAMON 主动回收

1
2
3
4
5
6
7
8
9
DAMON 发现这些文件缓存一段时间没有被访问
↓
Guest 内核回收符合条件的缓存页
↓
页面进入 Guest 的空闲内存池
↓
virtio-balloon 上报满足条件的空闲页
↓
Host 释放对应的物理内存

区别就在于:

DAMON 配合回收机制,先把“还占着页面的冷缓存”,变成“可以上报的空闲页”。

本例中,被清理的是内存里的缓存副本,不是磁盘上的文件。以后再次读取这些文件时,可以重新读入数据。

How

只看三个动作。

1. 在 Guest 中识别并回收冷缓存

DAMON 通过按区域采样来观察内存访问情况,不需要逐次记录每一次访问。

在本文方案中,一段时间没有被访问的文件页会成为回收候选,再通过 Guest 内核的正常回收路径,释放符合条件的页面。

可以把它理解为:

先观察哪些缓存长期没用,再尝试把它们腾出来。

2. 将空闲页上报给 Host

释放出的页面会交回 Guest 的内存管理器。

内核的 buddy allocator(伙伴分配器) 可以按规则合并相邻的空闲内存块,让更多空闲内存满足上报条件。随后,virtio-balloon 将这些空闲页报告给 Host。

这里上报的是哪些页面已经空闲,不是把文件内容搬到 Host。

3. 在 Host 中释放对应的物理内存

在 DSec 的方案中,Host 上的虚拟机监控器 Firecracker 接收这些报告,并通过:

1
madvise(..., MADV_DONTNEED)

释放相应的宿主机内存。

到这一步,才完成了从“Guest 不再使用”,到“Host 可以重新利用这部分内存”的衔接。

空闲页上报完成后,这些页面仍会回到 Guest 的空闲内存池,后续可以再次分配使用,并不是永久拿走了这部分 Guest 内存。

记忆图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Guest:虚拟机内部

长期不用的文件缓存
│
│ DAMON 识别 + 内核回收
▼
Guest 空闲页
│
│ virtio-balloon 空闲页上报
▼
──────────────────────────────────────
Host:宿主机

Firecracker 收到空闲页报告
│
│ MADV_DONTNEED
▼
释放对应的 Host 物理内存

可以简单记成:

1
2
3
4
5
DAMON + 内核回收:先腾出页面

virtio-balloon:再报告空闲

Host:最后回收内存

一句话概括:

先让虚拟机腾出长期不用的缓存,再让宿主机把对应的内存利用起来。


参考资料:

  1. DeepSeek Elastic Compute (DSec): A Sandbox Infrastructure for Effective Agentic Training at Scale
  2. [FYI] DAMON and virtio-balloon in DeepSeek’s DSec paper
  3. DAMON和virtio-balloon对deepseek DSec的基石支撑
  4. Linux Kernel Documentation: DAMON
  5. Linux Kernel Documentation: DAMON Design
  6. Linux Kernel Documentation: DAMON-based Reclamation
  7. Linux Kernel Documentation: Free Page Reporting