首页 > 技术文章 > KVM 核心功能:内存虚拟化

xingzheanan 2020-08-02 01:49 原文


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 内存热插拔

内存虚拟化的另一个特性是内存热插拔,看这里了解内存热插拔特性。
 
 
 

推荐阅读