此贴含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都不要碰,除非已经了解所有坑