KSM优化关键在于精准控制扫描对象、频率和范围。需显式启用内存可合并性,调优pages_to_scan与sleep_millisecs,禁用跨NUMA合并,并警惕MADV_UNMERGEABLE触发OOM。

KSM 能显著降低多虚拟机场景下的内存占用,但默认配置容易导致 CPU 扫描过载、NUMA 跨节点合并、或对写密集型负载产生 COW 频繁抖动。优化关键不在“开或不开”,而在“扫谁、怎么扫、在哪扫”。
确认 KSM 已启用且作用于目标虚拟机内存
KSM 不会自动扫描所有匿名页,必须由用户空间显式标记——KVM 虚拟机需通过 qemu-kvm 启动参数或 libvirt 配置主动启用内存可合并性。常见遗漏点:
-
madvise(MADV_MERGEABLE)必须在虚拟机内存映射后调用;libvirt 默认不开启,需在 domain XML 中添加<memorybacking><mergepages></mergepages></memorybacking> - 若使用
virt-install创建 VM,需加--memory-backing merge-pages=on - 检查是否生效:进入虚拟机宿主进程(如
qemu-system-x86_64)的/proc/PID/status,搜索MMU相关字段无意义;应查其/proc/PID/smaps中是否有大量MMU标记页 —— 更直接的方式是运行grep -i "ksm" /proc/PID/smaps,有输出才说明该进程内存已被 KSM 注册
调整 pages_to_scan 和 sleep_millisecs 避免 CPU 毛刺
KSM 默认每 20ms 扫 100 页,看似温和,但在上百个虚拟机共存时,ksmd 可能持续占用 5%~15% 的单核 CPU,尤其在内存压力初期。这不是 bug,而是扫描节奏与负载不匹配:
- 高密度轻量 VM(如容器化 KVM):设
echo 50 | sudo tee /sys/kernel/mm/ksm/pages_to_scan+echo 100 | sudo tee /sys/kernel/mm/ksm/sleep_millisecs(降低扫描频率,让 ksmd 更“懒”) - 少量重型 VM(如数据库 VM):设
echo 300 | sudo tee /sys/kernel/mm/ksm/pages_to_scan+echo 50 | sudo tee /sys/kernel/mm/ksm/sleep_millisecs(加快收敛,但需监控pages_sharing增速是否远超pages_unshared下降) - 绝对不要设
pages_to_scan > 1000:红黑树查找开销呈非线性增长,实测 >1500 会导致 ksmd 单次扫描卡顿超 10ms,影响调度延迟敏感型负载
禁用跨 NUMA 节点合并防止内存访问延迟升高
默认 KSM 允许合并不同 NUMA 节点上的相同页,看似省内存,实则让某节点的 CPU 访问远端内存,延迟翻倍。生产环境务必关闭:
- 执行
echo 0 | sudo tee /sys/kernel/mm/ksm/merge_across_nodes(注意:该文件仅在支持 NUMA 的内核中存在) - 验证是否生效:观察
/sys/kernel/mm/ksm/pages_shared在重启 ksmd 后是否明显下降(因限制了候选集),同时numastat -p $(pgrep qemu)中远端内存访问比例应不再随 KSM 运行而上升 - 若你只有一块 NUMA node(
numactl --hardware显示 node 0 only),此设置无效,无需操作
警惕 MADV_UNMERGEABLE 触发 OOM 的反直觉行为
当某个虚拟机退出或内存回收时,libvirt 或 QEMU 可能调用 madvise(..., MADV_UNMERGEABLE) 解除 KSM 标记。这本身不危险,但若此时系统已无足够空闲页框,解除合并会强制为每个原共享页分配独立物理页 —— 瞬间触发内存分配风暴:
- 现象:
dmesg出现Out of memory: Kill process ... (qemu) score ... or sacrifice child,且紧邻ksm: unmerging page日志 - 缓解方式不是禁用 unmerge,而是预留缓冲:确保
/proc/sys/vm/swappiness≤ 10(减少 swap 倾向),并保持至少 5%~8% 内存空闲(用MemAvailable判断,非Free) - 更稳妥的做法:在 VM 关机前,先用
echo 0 | sudo tee /sys/kernel/mm/ksm/run暂停 ksmd,等 VM 完全退出后再恢复,避免并发 unmerge
真正难调的不是参数值,而是判断“何时该扫、扫多少”——它取决于你的 VM 内存内容稳定性。比如全跑 Ubuntu 24.04 的 VM,启动后 10 分钟内 pages_sharing 就趋于平稳;而动态加载插件的 Java 应用 VM,可能要等数小时才收敛。别迷信固定数值,盯住 pages_sharing 和 pages_unshared 的比值变化率,比任何文档都准。


















