logo NodeSeekbeta

Hyper-V 动态内存导致 MemTotal 缩水,8G 的机器 NQ 只测出 2G 问题排查

此贴含AI生成内容

我购买的 ResidentialVPS Pro,商家交付的系统是 Win10 Pro,我自己 DD 成了 Debian 12。跑 NQ 时内存项显示 2G 左右,和实际的 8G 对不上。查了之后发现原因和动态内存有关。

这台机器运行在 Hyper-V 上,宿主机启用了动态内存(Dynamic Memory),目的是让物理内存能被多台虚拟机共用。虚拟机内的 hv_balloon 驱动会配合宿主机把内存交还回去,交还的部分会从内核的受管内存计数里扣掉,而 MemTotal 正是从该计数算出来的,所以只剩被扣完后的数额,物理内存并没有消失。

现象

lscpu 显示 Hypervisor vendor 是 Microsoft,8 vCPU。当时 free -m:

               total        used        free      shared  buff/cache   available
Mem:             411         302         199           0          79         109

同时 /sys/devices/system/memory/ 下 64 个 128MB 内存块全部为 online,合计正好 8G。

dmesg | grep hv_balloon:

[    2.636706] hv_balloon: Using Dynamic Memory protocol version 2.0
[   50.734813] hv_balloon: Max. dynamic memory size: 8192 MB
[  518.759474] hv_balloon: Balloon request will be partially fulfilled. Balloon floor reached.
[ 47672.711562] hv_balloon: Balloon request will be partially fulfilled. Not enough memory.

宿主机声明的动态内存上限是 8192MB。从日志看,开机约 519 秒后就开始出现回收请求未能完全满足的提示,此后持续运行约 13 小时,期间一直有相关记录。

原理

动态内存必须由虚拟机配合才能完成。宿主机不能直接从虚拟机手里把内存抢走,虚拟机内的系统并不知道某些物理页已经被划归他人,会继续往上面写,后果是数据损坏甚至崩溃。所以 Hyper-V 采用协商的方式:宿主机通过 VMBus 向虚拟机发送气球请求,由虚拟机内部的驱动腾出对应数量的页面,再把页的物理地址报告给宿主机。这个驱动就是 hv_balloon,机制名为 balloon(气球吹大表示从虚拟机抽走的内存变多)。这一步完全由宿主机的请求驱动,虚拟机侧只是执行。

交接完成后,驱动对交出去的每个页做两件事:

__SetPageOffline(pg + j);
adjust_managed_page_count(pg + j, -1);

前者把页面标记为 offline,后者把内核的受管内存计数减一。/proc/meminfo 里的 MemTotal 正是取自这个计数,free、NQ 以及各类探针读的都是它。所以交出去的页在账面上消失了。

另一件事是上报。驱动每秒向宿主机汇报一次可用内存与已提交内存,宿主机据此决定继续回收还是追加分配。已提交内存的算法是:

static unsigned long get_pages_committed(struct hv_dynmem_device *dm)
{
        return vm_memory_committed() +
                dm->num_pages_ballooned + ...
}

它把已经交出去的页(num_pages_ballooned)也算进了"已提交"。注释里说明了原因:若不这样算,宿主机已经遗忘这些页的归属,会反复要求虚拟机再交一遍同一批内存。代价是宿主机收到的报告里,这台虚拟机始终接近满负荷。我这边实际只剩 411M,宿主机那边看到的是已提交 7.57G。它没有理由追加分配,而虚拟机侧也没有接口可以主动索回。

最后是下限。驱动会拒绝把内存压到某个下限以下,但下限按当前总内存计算:

static unsigned long compute_balloon_floor(void)
{
        unsigned long nr_pages = totalram_pages();
        ...
}

注释给出的分档是:总内存 8192M 时下限 744M,2048M 时 360M,512M 时 168M。内存被收得越多,底线越低,宿主机下一轮还能接着收。这台机器先后出现过 2G 和 411M,与这个过程一致。

/sys/kernel/debug/hv-balloon 的计数器:

pages_added           : 0
pages_onlined         : 0
pages_ballooned       : 1930473
total_pages_committed : 1983955
max_dynamic_page_count: 2097152

1930473 页 × 4K = 7.37G 已交出,2097152 页正好对应 8G 上限。pages_added 为 0,说明宿主机从未对这台机器做过热添加,开机时就分配了完整的 8G,之后只有回收。13 小时的记录里这个值一直是 0。

处理

既然开机就能拿到完整 8G,让驱动不再配合即可:

echo "blacklist hv_balloon" > /etc/modprobe.d/blacklist-hv_balloon.conf
update-initramfs -u
reboot

重启后:

               total        used        free      shared  buff/cache   available
Mem:           7.8Gi       300Mi       7.6Gi       524Ki        77Mi       7.5Gi
MemTotal:        8143252 kB

hv_balloon 不再加载,dmesg 中没有任何 balloon 记录。黑名单会被 initramfs-tools 在后续内核升级时带入新的 initrd。还原方式是删掉该文件、重新生成 initramfs 并重启。

屏蔽 hv_balloon 会一并失去内存热添加能力,因为 hot_add 是该驱动的模块参数。这台机器 pages_added 为 0、开机即上限,所以没有影响;但如果机器是靠热添加才长到上限的,屏蔽后可能停留在开机时的内存量,动手前先确认这个值。

另外这是绕过而不是根治,宿主机侧的动态内存配置没有改变。要彻底处理需要商家关闭动态内存,或把 Startup RAM 与 Minimum RAM 都设为 8192MB。

  • 这和气球回收有关系吗?

  • 只买KVM和杜甫就对了,其他Hyper-V,VMware, LXC, OPENVZ都不要碰,除非已经了解所有坑

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有72980位seeker

🎉欢迎新用户🎉