[Mark] KVM 虚拟化基本原理

X86 操作系统是设计在直接运行在裸硬件设备上的,因此它们自动认为它们完全占有计算机硬件。x86 架构提供四个特权级别给操作系统和应用程序来访问硬件。 Ring 是指 CPU 的运行级别,Ring 0是最高级别,Ring1次之,Ring2更次之…… 就 Linux+x86 来说, 
  • 操作系统(内核)需要直接访问硬件和内存,因此它的代码需要运行在最高运行级别  Ring0上,这样它可以使用特权指令,控制中断、修改页表、访问设备等等。 
  • 应用程序的代码运行在最低运行级别上ring3上,不能做受控操作。如果要做,比如要访问磁盘,写文件,那就要通过执行系统调用(函数),执行系统调用的时候,CPU的运行级别会发生从ring3到ring0的切换,并跳转到系统调用对应的内核代码位置执行,这样内核就为你完成了设备访问,完成之后再从ring0返回ring3。这个过程也称作用户态和内核态的切换。
 
 
那么,虚拟化在这里就遇到了一个难题,因为宿主操作系统是工作在 ring0 的,客户操作系统就不能也在 ring0 了,但是它不知道这一点,以前执行什么指令,现在还是执行什么指令,但是没有执行权限是会出错的。所以这时候虚拟机管理程序(VMM)需要避免这件事情发生。 虚机怎么通过 VMM 实现 Guest CPU 对硬件的访问,根据其原理不同有三种实现技术:
1. 全虚拟化
2. 半虚拟化
3. 硬件辅助的虚拟化 
 
 

1.1 基于二进制翻译的全虚拟化(Full Virtualization with Binary Translation)

 
客户操作系统运行在 Ring 1,它在执行特权指令时,会触发异常(CPU的机制,没权限的指令会触发异常),然后 VMM 捕获这个异常,在异常里面做翻译,模拟,最后返回到客户操作系统内,客户操作系统认为自己的特权指令工作正常,继续运行。但是这个性能损耗,就非常的大,简单的一条指令,执行完,了事,现在却要通过复杂的异常处理过程。
 
异常 “捕获(trap)-翻译(handle)-模拟(emulate)” 过程:
 

1.2. 超虚拟化(或者半虚拟化/操作系统辅助虚拟化 Paravirtualization) 

  半虚拟化的思想就是,修改操作系统内核,替换掉不能虚拟化的指令,通过超级调用(hypercall)直接和底层的虚拟化层hypervisor来通讯,hypervisor 同时也提供了超级调用接口来满足其他关键内核操作,比如内存管理、中断和时间保持。
  这种做法省去了全虚拟化中的捕获和模拟,大大提高了效率。所以像XEN这种半虚拟化技术,客户机操作系统都是有一个专门的定制内核版本,和x86、mips、arm这些内核版本等价。这样以来,就不会有捕获异常、翻译、模拟的过程了,性能损耗非常低。这就是XEN这种半虚拟化架构的优势。这也是为什么XEN只支持虚拟化Linux,无法虚拟化windows原因,微软不改代码啊。
 

1.3. 硬件辅助的全虚拟化 

    2005年后,CPU厂商Intel 和 AMD 开始支持虚拟化了。 Intel 引入了 Intel-VT (Virtualization Technology)技术。 这种 CPU,有 VMX root operation 和 VMX non-root operation两种模式,两种模式都支持Ring 0 ~ Ring 3 共 4 个运行级别。这样,VMM 可以运行在 VMX root operation模式下,客户 OS 运行在VMX non-root operation模式下。
 
 
 
  而且两种操作模式可以互相转换。运行在 VMX root operation 模式下的 VMM 通过显式调用 VMLAUNCH 或 VMRESUME 指令切换到 VMX non-root operation 模式,硬件自动加载 Guest OS 的上下文,于是 Guest OS 获得运行,这种转换称为 VM entry。Guest OS 运行过程中遇到需要 VMM 处理的事件,例如外部中断或缺页异常,或者主动调用 VMCALL 指令调用 VMM 的服务的时候(与系统调用类似),硬件自动挂起 Guest OS,切换到 VMX root operation 模式,恢复 VMM 的运行,这种转换称为 VM exit。VMX root operation 模式下软件的行为与在没有 VT-x 技术的处理器上的行为基本一致;而VMX non-root operation 模式则有很大不同,最主要的区别是此时运行某些指令或遇到某些事件时,发生 VM exit。
 
也就说,硬件这层就做了些区分,这样全虚拟化下,那些靠“捕获异常-翻译-模拟”的实现就不需要了。而且CPU厂商,支持虚拟化的力度越来越大,靠硬件辅助的全虚拟化技术的性能逐渐逼近半虚拟化,再加上全虚拟化不需要修改客户操作系统这一优势,全虚拟化技术应该是未来的发展趋势。
 
 
 

(1)一个 KVM 虚机即一个Linux qemu-kvm 进程,与其他 Linux 进程一样被Linux 进程调度器调度。

(2)KVM 虚机包括虚拟内存、虚拟CPU和虚机 I/O设备,其中,内存和 CPU 的虚拟化由 KVM 内核模块负责实现,I/O 设备的虚拟化由 QEMU 负责实现。

(3)KVM户机系统的内存是 qumu-kvm 进程的地址空间的一部分。

(4)KVM 虚机的 vCPU 作为 线程运行在 qemu-kvm 进程的上下文中。

 

支持虚拟化的 CPU 中都增加了新的功能。以 Intel VT 技术为例,它增加了两种运行模式:VMX root 模式和 VMX nonroot 模式。通常来讲,主机操作系统和 VMM 运行在 VMX root 模式中,客户机操作系统及其应用运行在 VMX nonroot 模式中。因为两个模式都支持所有的 ring,因此,客户机可以运行在它所需要的 ring 中(OS 运行在 ring 0 中,应用运行在 ring 3 中),VMM 也运行在其需要的 ring 中 (对 KVM 来说,QEMU 运行在 ring 3,KVM 运行在 ring 0)。CPU 在两种模式之间的切换称为 VMX 切换。从 root mode 进入 nonroot mode,称为 VM entry;从 nonroot mode 进入 root mode,称为 VM exit。可见,CPU 受控制地在两种模式之间切换,轮流执行 VMM 代码和 Guest OS 代码。

  对 KVM 虚机来说,运行在 VMX Root Mode 下的 VMM 在需要执行 Guest OS 指令时执行 VMLAUNCH 指令将 CPU 转换到 VMX non-root mode,开始执行客户机代码,即 VM entry 过程;在 Guest OS 需要退出该 mode 时,CPU 自动切换到 VMX Root mode,即 VM exit 过程。可见,KVM 客户机代码是受 VMM 控制直接运行在物理 CPU 上的。QEMU 只是通过 KVM 控制虚机的代码被 CPU 执行,但是它们本身并不执行其代码。也就是说,CPU 并没有真正的被虚级化成虚拟的 CPU 给客户机使用。

 

主机 Linux 将一个虚拟视作一个 QEMU 进程,该进程包括下面几种线程:

  • I/O 线程用于管理模拟设备
  • vCPU 线程用于运行 Guest 代码
  • 其它线程,比如处理 event loop,offloaded tasks 等的线程

在我的测试环境中(RedHata Linux 作 Hypervisor):

smp 设置的值 线程数 线程
4 8

1 个主线程(I/O 线程)、4 个 vCPU 线程、3 个其它线程

6 10 1 个主线程(I/O 线程)、6 个 vCPU 线程、3 个其它线程

 

要将客户机内的线程调度到某个物理 CPU,需要经历两个过程:

  1. 客户机线程调度到客户机物理CPU 即 KVM vCPU,该调度由客户机操作系统负责,每个客户机操作系统的实现方式不同。在 KVM 上,vCPU 在客户机系统看起来就像是物理 CPU,因此其调度方法也没有什么不同。
  2. vCPU 线程调度到物理 CPU 即主机物理 CPU,该调度由 Hypervisor 即 Linux 负责。

    KVM 使用标准的 Linux 进程调度方法来调度 vCPU 进程。Linux 系统中,线程和进程的区别是 进程有独立的内核空间,线程是代码的执行单位,也就是调度的基本单位。Linux 中,线程是就是轻量级的进程,也就是共享了部分资源(地址空间、文件句柄、信号量等等)的进程,所以线程也按照进程的调度方式来进行调度。


vCPU如何设置

我们来假设一个主机有 2 个socket,每个 socket 有 4 个core。主频2.4G MHZ 那么一共可用的资源是 2*4*2.4G= 19.2G MHZ。假设主机上运行了三个VM,VM1和VM2设置为1socket*1core,VM3设置为1socket*2core。那么VM1和VM2分别有1个vCPU,而VM3有2个vCPU。假设其他设置为缺省设置。

那么三个VM获得该主机CPU资源分配如下:VM1:25%; VM2:25%; VM3:50%

 假设运行在VM3上的应用支持多线程,那么该应用可以充分利用到所非配的CPU资源。2vCPU的设置是合适的。假设运行在VM3上的应用不支持多线程,该应用根本无法同时使用利用2个vCPU. 与此同时,VMkernal层的CPU Scheduler必须等待物理层中两个空闲的pCPU,才开始资源调配来满足2个vCPU的需要。在仅有2vCPU的情况下,对该VM的性能不会有太大负面影响。但如果分配4vCPU或者更多,这种资源调度上的负担有可能会对该VM上运行的应用有很大负面影响。

确定 vCPU 数目的步骤。假如我们要创建一个VM,以下几步可以帮助确定合适的vCPU数目

1 了解应用并设置初始值

    该应用是否是关键应用,是否有Service Level Agreement。一定要对运行在虚拟机上的应用是否支持多线程深入了解。咨询应用的提供商是否支持多线程和SMP(Symmetricmulti-processing)。参考该应用在物理服务器上运行时所需要的CPU个数。如果没有参照信息,可设置1vCPU作为初始值,然后密切观测资源使用情况。

2 观测资源使用情况

    确定一个时间段,观测该虚拟机的资源使用情况。时间段取决于应用的特点和要求,可以是数天,甚至数周。不仅观测该VM的CPU使用率,而且观测在操作系统内该应用对CPU的占用率。特别要区分CPU使用率平均值和CPU使用率峰值。

     假如分配有4个vCPU,如果在该VM上的应用的CPU

  • 使用峰值等于25%, 也就是仅仅能最多使用25%的全部CPU资源,说明该应用是单线程的,仅能够使用一个vCPU (4 * 25% = 1 )
  • 平均值小于38%,而峰值小于45%,考虑减少 vCPU 数目
  • 平均值大于75%,而峰值大于90%,考虑增加 vCPU 数目

3 更改vCPU数目并观测结果

每次的改动尽量少,如果可能需要4vCPU,先设置2vCPU在观测性能是否可以接受。

 
 
KVM 实现客户机内存的方式是,利用mmap系统调用,在QEMU主线程的虚拟地址空间中申明一段连续的大小的空间用于客户机物理内存映射。
 
 
KVM 中,虚机的物理内存即为 qemu-kvm 进程所占用的内存空间。KVM 使用 CPU 辅助的内存虚拟化方式。在 Intel 和 AMD 平台,其内存虚拟化的实现方式分别为:
  • AMD 平台上的 NPT (Nested Page Tables) 技术
  • Intel 平台上的 EPT (Extended Page Tables)技术

EPT 和 NPT采用类似的原理,都是作为 CPU 中新的一层,用来将客户机的物理地址翻译为主机的物理地址。

 
 
KSM 作为内核中的守护进程(称为 ksmd)存在,它定期执行页面扫描,识别副本页面并合并副本,释放这些页面以供它用。因此,在多个进程中,Linux将内核相似的内存页合并成一个内存页。这个特性,被KVM用来减少多个相似的虚拟机的内存占用,提高内存的使用效率。由于内存是共享的,所以多个虚拟机使用的内存减少了。这个特性,对于虚拟机使用相同镜像和操作系统时,效果更加明显。但是,事情总是有代价的,使用这个特性,都要增加内核开销,用时间换空间。所以为了提高效率,可以将这个特性关闭。
 
其好处是,在运行类似的客户机操作系统时,通过 KSM,可以节约大量的内存,从而可以实现更多的内存超分,运行更多的虚机。 
 

(1)初始状态:

(2)合并后:

(3)Guest 1 写内存后:

 

Intel 的 x86 CPU 通常使用4Kb内存页,当是经过配置,也能够使用巨页(huge page): (4MB on x86_32, 2MB on x86_64 and x86_32 PAE)

使用巨页,KVM的虚拟机的页表将使用更少的内存,并且将提高CPU的效率。最高情况下,可以提高20%的效率!

 

大页面和透明大页面(THP)

x86 CPU 通常会在 4kB 页面中处理内存,但可以使用更大的 2MB 或 1GB 页面,即 huge page(大页面)。大页面内存可以支持 KVM 客机部署,通过增加点击转换后备缓冲器(TLB)的 CPU 缓存以改善性能。
kernel 功能将在 Red Hat Enterprise Linux 7 中默认启用,大页面可以大幅提高性能,尤其是对于较大的内存和内存密集型的负载。Red Hat Enterprise Linux 7 可以通过使用大页面增加页面大小,以便有效管理大量内存。

过程 7.1. 为客机启用 1GB 大页面

  1. Red Hat Enterprise Linux 7.1 系统支持 2MB 或 1GB 大页面,分配将在启动或运行时进行。页面大小均可以在运行时被释放。例如,在启动时分配 4 个 1GB 的大页面和 1,024 个 2MB 的大页面,请使用以下命令行:
    'default_hugepagesz=1G hugepagesz=1G hugepages=4 hugepagesz=2M hugepages=1024'
    此外,大页面还可以在运行时分配。运行时分配允许系统管理员选择从何种 NUMA 模式分配页面。然而由于内存碎片的存在,运行时的页面分配会比启动时分配更容易造成分配失败。以下运行时的分配示例显示了从 node1 分配 4 个 1GB 的大页面以及从 node3 分配 1,024 个 2MB 的大页面:
    # echo 4 > /sys/devices/system/node/node1/hugepages/hugepages-1048576kB/nr_hugepages
    # echo 1024 > /sys/devices/system/node/node3/hugepages/hugepages-2048kB/nr_hugepages
  2. 接下来,将 2MB 和 1GB 的大页面挂载到主机:
    # mkdir /dev/hugepages1G
    # mount -t hugetlbfs -o pagesize=1G none /dev/hugepages1G
    # mkdir /dev/hugepages2M
    # mount -t hugetlbfs -o pagesize=2M none /dev/hugepages2M
默认1GB 大页面现在对客机不可用。客户机中要想使用1G大内存页,需要如下配置:
 
在以下示例中,客机 NUMA 节点 0-5(不包括 NUMA 节点 4)将会使用 1 GB 的大页面,客机 NUMA 节点 4 将使用 2 MB 的大页面,无论客机 NUMA 节点在主机的任何位置。
<memoryBacking>
        <hugepages/>
          <page size="1" unit="G" nodeset="0-3,5"/>
          <page size="2" unit="M" nodeset="4"/>
        </hugepages>
</memoryBacking>
 
透明大页面(THP,transparent huge page)将为性能自动优化系统设置。通过允许所有的空余内存被用作缓存以提高性能。
一旦 /sys/kernel/mm/transparent_hugepage/enabled 被设置为 always,透明大页面将被默认使用。运行以下命令禁用透明大页面:
# echo never > /sys/kernel/mm/transparent_hugepage/enabled
透明大页面支持不能阻止 hugetlbfs 的使用。但在 hugetlbfs 未使用时,KVM 将使用透明大页面来替代常规的 4KB 页面大小

 

例子:

使用方法,需要三部:

mkdir /dev/hugepages
mount -t hugetlbfs hugetlbfs /dev/hugepages
#保留一些内存给巨页
sysctl vm.nr_hugepages=2048 (使用 x86_64 系统时,这相当于从物理内存中保留了2048 x 2M = 4GB 的空间来给虚拟机使用)
#给 kvm 传递参数 hugepages
qemu-kvm - qemu-kvm -mem-path /dev/hugepages

也可以在配置文件里加入:

<memoryBacking>
<hugepages/>
</memoryBacking>

验证方式,当虚拟机正常启动以后,在物理机里查看:

cat /proc/meminfo |grep -i hugepages
posted @ 2016-10-12 11:51 于杨 阅读(...) 评论(...) 编辑 收藏