Skip to the content.

Windows 内存管理

Windows 的内存管理有不少 Linux 里没有的概念,比如“已提交(commit)”、分页/非分页池、 压缩内存,以及下面这个在虚拟化里非常烦人的行为。

[!NOTE] 参考神奇海螺的意见,有待验证

为什么 Windows 开机后会“踩”满所有物理内存

现象

Windows guest 跑在 QEMU/KVM 上时,host 侧 qemu 进程的 RSS 会在开机后很快涨到 guest 配置的全部内存。哪怕 guest 内部可用内存还很充裕。

同样的现象在别人的环境里也被反复确认过:

直接原因:zero page thread

Windows 的 memory manager 把物理页按状态挂在不同的 page list 上,free page list 里的页 表示“可以拿来用,但内容不保证是 0”。而 Windows 承诺:进程通过 VirtualAlloc 拿到的新页 内容一定是 0(出于安全,不能把上一个进程的残留数据泄露给下一个进程)。 为了不在分配路径上同步清零,Windows 用了一个后台系统线程 zero page thread: 把 free page list 上的页清零后挂到 zeroed page list,分配时直接从 zeroed list 取。

Microsoft Learn 的 Scheduling Priorities 原话是:

The zero-page thread is a system thread responsible for zeroing any free pages when there are no other threads that need to run.

也就是说它的优先级是 0,只有在没有任何其它线程需要运行时才会跑。开机阶段 guest 里几乎 全部物理内存都在 free list 上(内核和已加载模块只占一小部分),于是只要 CPU 一空闲, 这个线程就会把 free list 抽干,逐页写 0。在虚拟机里,每一页写 0 都会让 host 上对应的 匿名页发生第一次写 fault,RSS 随之上涨,直到接近 guest 配置的全部 RAM。

机制(对照 ReactOS ARM3 实现)

Windows 内部符号不公开,这里用 ReactOS 的同名实现来说明流程(命名与 Windows 内部一致):

所以 free list 上的页在被回收进 zeroed list 之前,一定会被物理写一遍。CPU 越空闲, 清得越快,这正好解释了“开机后很快踩满”。

这不是 bug

和 Linux 的对比

Linux 没有后台线程去逐页清零:

因此 Linux guest 不会在开机后主动 touch 全部 RAM,这是 Windows guest 特有的开销。

如何观察

Windows 侧:

Get-Counter '\Memory\Free & Zero Page List Bytes'

Host 侧:

# qemu 进程 RSS
ps -o pid,rss,cmd -p "$(pgrep -f 'qemu-system.*')"

# 更精确的 guest 统计
virsh dommemstat <domain>

# 直接看 RSS
awk '/VmRSS/ {print}' /proc/<qemu-pid>/status

缓解方法

  1. 在 guest 里装 virtio-balloon 驱动(virtio-win),这是最直接的办法。balloon 驱动把 guest 认为空闲的页交给 host,这些页会离开 guest 的 free list,zero page thread 不会再 碰它们,host 就能回收内存。
  2. Hyper-V Dynamic Memory:本质也是 balloon,Microsoft 官方的方案。
  3. VIRTIO_BALLOON_F_FREE_PAGE_HINT / free page reporting:guest 主动把空闲页报告给 host。 注意它和 zero page thread 有交互,被报告后 guest 不应再写这些页。
  4. 不要试图禁掉 zero page thread:Microsoft 明确不建议,也没有官方开关, 强行禁用会影响安全与稳定性。
  5. 给 guest 配小内存并开启动态内存,而不是一次性把全部内存分给 guest。

参考

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。