KVM 核心功能:内存虚拟化
1 内存虚拟化简介
QEMU-KVM 提供内存的虚拟化,从虚拟机角度看其自身拥有的内存就是真实的物理内存。实际上,虚拟机是 host 上的一个 qemu 进程,在为虚拟机指定内存时,host 上并没有分配该内存给虚拟机(qemu 进程),而是需要使用内存时,由 qemu-kvm 分配内存给它。
看这里了解 QEMU-KVM 内存虚拟化机制。
2 内存虚拟化配置
传统的内存虚拟化通过影子页表实现,但是影子页表实现复杂,而且内存开销很大,每个 qemu 进程都需要维护自己的影子页表。
基于硬件支持的内存虚拟化技术能较好的解决影子页表带来的问题,Intel CPU 提供 EPT 技术支持内存虚拟化,AMD 提供 NPT 技术支持内存虚拟化。
在 host 上查看硬件是否支持内存虚拟化:
[demo@lianhua ~]$ cat /proc/cpuinfo | grep "model name" model name : Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz [demo@lianhua ~]$ cat /proc/cpuinfo | grep -E "ept|vpid" fpu_exception : yes flags : ... vmx smx aes tpr_shadow vnmi flexpriority ept vpid fsgsbase
可以看出,host 上使用的是 Intel 的 CPU,该 CPU 已经支持了 ept 和 vpid。
kvm_intel 模块在加载时会默认开启 ept 和 vpid,从而 KVM 可以使用它们:
[demo@lianhua ~]$ cat /sys/module/kvm_intel/parameters/ept Y [demo@lianhua ~]$ cat /sys/module/kvm_intel/parameters/vpid Y
在创建虚拟机时,通过 qemu-kvm 的 -m 选项指定虚拟机的内存:
[root@lianhua ~]# free -h total used free shared buff/cache available Mem: 251G 85G 11G 56M 155G 159G Swap: 0B 0B 0B [root@lianhua ~]# /usr/libexec/qemu-kvm -m 5G -smp 2 demo.qcow2 -monitor stdio QEMU 2.6.0 monitor - type 'help' for more information (qemu) VNC server running on '::1;5900' (qemu) [root@lianhua ~]# free -h total used free shared buff/cache available Mem: 251G 85G 11G 56M 155G 159G Swap: 0B 0B 0B
如上所示,指定虚拟机的内存为 5G,在创建虚拟机之后, host 上并没有分配 5G 内存给虚拟机。
3 内存虚拟化特性
类似 vCPU 虚拟化特性,内存虚拟化也有几大特性:
3.1 内存过载(over-commit)
顾名思义,内存过载即 host 上总的虚拟机内存可以大于 host 内存。在 host 上不同虚拟机可能实现不同的功能,不同功能使用的内存不尽相同,一般不会保证内存 100% 被使用到。因此,可以对内存进行过载分配。
内存过载分配主要有三种方式:
-
内存交换(swapping):通过 host 上的 swap 交换分区分配过载的内存。
-
内存气球(ballooning):通过半虚拟化的 ballooning 机制实现内存过载分配。
-
页共享(page sharing):通过 KSM(Kernel Samepage Merging) 合并多个相同内存页实现内存过载分配。
3.1.1 内存交换
内存交换是最常用的内存过载分配方式。它是在虚拟机内存需要过载分配时,将 swap 交换分区的内存分配给虚拟机,从而实现内存的过载分配。
使用内存交换进行内存过载分配时,用户不需要显示的指定其它配置,但是要保证 swap 交换分区有足够多的空间可供分配。比如,host 上有 10 台虚拟机,每台虚拟机指定的内存为 1G,而 host 上的物理内存有 6G,那么,不算操作系统自己占用内存的情况下,还需要至少 4G 的内存才能实现虚拟机内存的过载,这至少 4G 的内存就要从 swap 交换分区中分配。
注意:swap 交换分区是使用磁盘存储内存数据的分区,相比于直接使用内存条存储数据要慢的多。如果虚拟机对读写性能有要求的话,那么,在使用内存交换进行内存过载分配之前还需要评估性能是否受影响。
3.1.2 内存气球
3.1.2.1 内存气球简介
内存气球是通过半虚拟化机制的 ballooning 方式实现的内存过载分配。如下图所示:
内存气球是在虚拟机内部引入气球的概念,气球内的内存不能被虚拟机使用。当 hypervisor 请求扩大内存时,虚拟机内部的气球就会膨胀,内存页就会释放。当虚拟机需要扩大内存时,虚拟机内部的气球就会缩小,可用的内存增多。引入内存气球的机制可以动态调整虚拟机的内存。
ballooning 方式是在半虚拟化机制下工作的(半虚拟化原理可看这里),在 host 上配置的 virtio 后端驱动是 virtio_balloon,在虚拟机中配置的 virtio 前端驱动是 virtio-pci:
[root@lianhua ~]# find /lib/modules/3.10.0-514.6.2.el7.x86_64/ -name virtio*.ko ... /lib/modules/3.10.0-514.6.2.el7.x86_64/kernel/drivers/virtio/virtio_balloon.ko [root@vm:/sys/module] # ls /sys/module/virtio* /sys/module/virtio_blk: parameters uevent /sys/module/virtio_net: parameters uevent /sys/module/virtio_pci: drivers parameters uevent version
(驱动加载位置因操作系统及配置而异,并不是所有机器的驱动模块都在这个路径下)
结合 virtio 的驱动及内存气球工作原理,再次介绍内存气球的工作流程:
1) hypervisor 发送请求给虚拟机,请求虚拟机释放内存。
2) 虚拟机的 virtio-pci 驱动接收到该请求,并且使虚拟机内部的内存气球膨胀。
3) 内存气球膨胀之后,虚拟机可用的内存减少,虚拟机通知 hypervisor 内存已释放。
4) hypervisor 将释放的内存分配到需要内存的地方。
3.1.2.2 内存气球配置
配置内存气球,首先需要在 host 上加载 virtio_ballon 模块。然后,通过 qemu-kvm 的 -device 选项指定虚拟机的内存驱动为 virtio-balloon-pci:
[root@lianhua hxia]# /usr/libexec/qemu-kvm -enable-kvm -smp 2 -m 2G -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x9 -msg timestamp=on demo.qcow2 -monitor stdio QEMU 2.6.0 monitor - type 'help' for more information (qemu) VNC server running on '::1;5900'
如上所示,指定虚拟机的内存为 2G,且内存分配方式为 virtio ballooning 分配,balloon 设备的 pci 号为 00:09:0。
进入创建的虚拟机,查看 pci 编号及 balloon 设备所使用的驱动:
[root@vm:/home/robot] # lspci ... 00:09.0 Unclassified device [00ff]: Red Hat, Inc. Virtio memory balloon [root@vm:/sys/module] # lspci -s 00:09.0 -vvv 00:09.0 Unclassified device [00ff]: Red Hat, Inc. Virtio memory balloon Subsystem: Red Hat, Inc. Device 0005 Physical Slot: 9 Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx- Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx- Latency: 0 Interrupt: pin A routed to IRQ 10 Region 0: I/O ports at c0c0 [size=32] Region 4: Memory at fe010000 (64-bit, prefetchable) [size=16K] Capabilities: [84] Vendor Specific Information: VirtIO: <unknown> BAR=0 offset=00000000 size=00000000 Capabilities: [70] Vendor Specific Information: VirtIO: Notify BAR=4 offset=00003000 size=00001000 multiplier=00000004 Capabilities: [60] Vendor Specific Information: VirtIO: DeviceCfg BAR=4 offset=00002000 size=00001000 Capabilities: [50] Vendor Specific Information: VirtIO: ISR BAR=4 offset=00001000 size=00001000 Capabilities: [40] Vendor Specific Information: VirtIO: CommonCfg BAR=4 offset=00000000 size=00001000 Kernel driver in use: virtio-pci # balloon 所用的驱动为 virtio-pci
除了 qemu-kvm 指定 balloon 设备创建虚拟机的方式外,还可以在 libvirt 的 XML 文件中指定 balloon 设备来创建虚拟机:
<memballoon model='virtio'> <stats period='10'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/> </memballoon>
3.1.2.3 内存气球测试
内存气球配置好以后,进入 qemu 的 monitor 通过执行 balloon 命令手动分配虚拟机的内存(注意 balloon 机制都是通过调用命令行的方式调整虚拟机的内存的,不是内核自发完成的):
[root@lianhua hxia]# /usr/libexec/qemu-kvm -enable-kvm -smp 2 -m 512 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x9 -msg timestamp=on demo.qcow2 -monitor stdio QEMU 2.6.0 monitor - type 'help' for more information (qemu) VNC server running on '::1;5900' (qemu) info balloon balloon: actual=512 (qemu) balloon 256 (qemu) info balloon balloon: actual=256 (qemu) balloon 512 (qemu) info balloon balloon: actual=512 (qemu) balloon 666 (qemu) info balloon balloon: actual=512
如上所示,在 monitor 中通过调用 balloon 命令可以动态调节虚拟机内存大小,在虚拟机中查看内存确实随之而变化。另一方面可以看出,当试图调整 balloon 到超过内存上限 512M 时,实际的内存大小还是 512M,balloon 并没有变化。
3.1.3 页共享
3.1.3.1 页共享简介
页共享是基于 KSM 实现的内存过载分配方式。KSM(Kernel Samepage Merging)内核同页合并会合并内存中的相同内存页,减少内存使用量,从而实现内存过载分配。
应用程序会标记可合并的内存页,KSM 扫描到这些可合并内存页,然后对其进行合并,通常合并是没有风险的。如果应用程序需要修改合并的内存页,则内核会通过“写时复制(copy-on-write,cow)”技术复制一份内存页,然后对复制的内存页进行改写,从而保证了原内存页的安全。
RedHat 系列系统默认安装了 ksm 和 ksmtuned 服务程序,使用 ps 命令查看 host 上是否运行 ksm 和 ksmtuned:
[root@lianhua home]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 7.3 (Maipo) [root@lianhua home]# ps -elf | grep ksm | grep -v grep 1 S root 306 2 0 85 5 - 0 ksm_sc Apr30 ? 01:47:01 [ksmd] 1 S root 1158 1 0 80 0 - 29694 wait Apr30 ? 00:09:34 /bin/bash /usr/sbin/ksmtuned
可见,确实安装了 ksm 和 ksmtuned 服务程序,并且程序的进程正在“运行”中。其中,ksm 是主要的合并相同内存页的服务程序,ksmtuned 是对 ksm 的参数配置进行微调的服务程序,因为 ksm 的配置一旦修改了,系统默认不会再去修改它的值,这样对于扫描,合并内存页不够灵活。而 ksmtuned 可以实时动态调节 ksm 的行为。
ksmtuned 的配置参数有:
[root@lianhua home]# cat /etc/ksmtuned.conf # Configuration file for ksmtuned. # How long ksmtuned should sleep between tuning adjustments # KSM_MONITOR_INTERVAL=60 # Millisecond sleep between ksm scans for 16Gb server. # Smaller servers sleep more, bigger sleep less. # KSM_SLEEP_MSEC=10 # KSM_NPAGES_BOOST=300 # KSM_NPAGES_DECAY=-50 # KSM_NPAGES_MIN=64 # KSM_NPAGES_MAX=1250 # KSM_THRES_COEF=20 # KSM_THRES_CONST=2048 # uncomment the following if you want ksmtuned debug info # LOGFILE=/var/log/ksmtuned # DEBUG=1
(看这里了解 ksmtuned 配置参数)
3.1.2.2 页共享配置
使用 qemu-kvm 创建虚拟机时,可指定 -machine 选项的 mem-merge 开关控制内存页共享,mem-merge 为 on 即表示打开内存页共享,off 即表示关闭内存页共享:
[root@lianhua qemu-kvm]# /usr/libexec/qemu-kvm -m 500M -smp 2 lianhua.raw --machine mem-merge=on -monitor stdio WARNING: Image format was not specified for 'lianhua.raw' and probing guessed raw. Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted. Specify the 'raw' format explicitly to remove the restrictions. QEMU 2.6.0 monitor - type 'help' for more information (qemu) VNC server running on '::1;5900' (qemu)
在 /sys/kernel/mm/ksm/ 目录下查看 ksm 合并的内存页文件:
[root@lianhua home]# ll /sys/kernel/mm/ksm/ total 0 -r--r--r--. 1 root root 4096 Jul 31 13:06 full_scans -rw-r--r--. 1 root root 4096 Jul 31 13:06 max_page_sharing -rw-r--r--. 1 root root 4096 Jul 31 13:06 merge_across_nodes -r--r--r--. 1 root root 4096 Jul 11 17:37 pages_shared -r--r--r--. 1 root root 4096 Jul 11 17:37 pages_sharing -rw-r--r--. 1 root root 4096 Jul 12 00:46 pages_to_scan -r--r--r--. 1 root root 4096 Jul 31 13:06 pages_unshared -r--r--r--. 1 root root 4096 Jul 31 13:06 pages_volatile -rw-r--r--. 1 root root 4096 Aug 2 01:05 run -rw-r--r--. 1 root root 4096 Jul 12 00:46 sleep_millisecs -r--r--r--. 1 root root 4096 Jul 31 13:06 stable_node_chains -rw-r--r--. 1 root root 4096 Jul 31 13:06 stable_node_chains_prune_millisecs -r--r--r--. 1 root root 4096 Jul 31 13:06 stable_node_dups [root@lianhua home]# cat /sys/kernel/mm/ksm/run 0
主要文件有:
-
merge_across_nodes:是否允许跨 NUMA 节点合并相同内存页。
-
pages_shared:标记已经在用的内存页数量。
-
pages_sharing:标记合并的内存页数量。
-
pages_to_scan:标记 ksmd 休眠之前扫描的内存页数量。
-
run:标记 ksm 是否运行,有多个标志位,标志位 0 表示停止运行 ksmd 进程但保存已合并的内存页;1 表示运行 ksmd 进程,2 表示停止 ksmd 进程。
从上述文件可知,pages_sharing 除以 pages size 即为共享的内存页大小。
在 host 上创建 2 个虚拟机,且都打开内存页共享,查看 host 上共享的内存页大小:
[root@lianhua home]# echo "$(( $(cat /sys/kernel/mm/ksm/pages_sharing) * $(getconf PAGESIZE) / 1024 / 1024))MB" 5MB
3.2 内存热插拔
内存虚拟化的另一个特性是内存热插拔,看这里了解内存热插拔特性。
芝兰生于空谷,不以无人而不芳。