KVM 虚拟化技术
KVM(Kernel-based Virtual Machine)是基于X86硬件虚拟化扩展(Inter VT 或 AMD-V)的全虚拟化解决方案,它包含一个可加载的内核模块kvm.ko,提供核心的虚拟化基础架构,还有一个处理器特定模块kvm-intel.ko 或 kvm-amd.ko。
注意:
全虚拟化解决方案:虚拟机与底层硬件之间由一个虚拟化逻辑层Hypervisor 来完成模拟底层硬件,上层虚拟机完成感知不到运行在虚拟硬件上。
优点:虚拟机操作系统内核不需要进行特殊配置,部署便捷、灵活、兼容性好。
缺点:虚拟机操作系统的内核不能直接管理底层硬件,内核通过Hypervisor 管理底层硬件,有转换性能开销。
半虚拟化解决方案:虚拟机操作系统内核需要经过修改,与宿主机操作系统内核共享底层硬件实现。
优点:半虚拟化的虚拟机操作系统内核能够直接管理底层硬件,性能表现比全虚拟化技术更好。
缺点:虚拟机操作系统内核需要事先进行修改,部署的便捷性、灵活性和兼容性差。
KVM 允许在一台主机节点上运行多个未经改动的虚拟镜像,包括Windows 和 Linux 。每台虚拟机都有独立的虚拟硬件,包括网卡、磁盘等。
KVM 是一个开源项目,其核心组件包含在Linux 更新主线中,用户空间组件包含在QEMU 更新主线中。
KVM 是在CPU 硬件支持基础之上的虚拟化技术,同 Hyper-V、Xen 一样依赖此项技术。没有CPU 硬件虚拟化的支持,KVM是无法工作。
KVM 是Linux的一个模块,可以用modprode 加载KVM 模块。只有在加载模块后,才能进一步通过其他工具创建虚拟机。但仅有 KVM 模块是远远不够的,因为用户无法直接控制内核模块去做事情,必须有一个用户空间的工具(开源QEMU工具)。QEMU 是个虚拟化软件,其特点是可虚拟不同的硬件(例如:X86 的CPU上可虚拟一个安腾的CPU)。KVM使用了QEMU的一部分,并稍微加改造,就变更了可控制 KVM 的工具。
注意:官方提供的 KVM 下载有两大部分三个文件,分别是 KVM 模块、QEMU 工具 及二者的合集。可以只升级 KVM 模块,也可以只升级 QEMU 工具。
KVM 进程拥有三种运行模式:内核模式、用户模式、客户模式。在 KVM 模型中,每个虚拟主机都是由 Linux 调度程序管理的标准进程,该进程调用 KVM 用户模式,执行应用程序。对于应用程序而言,用户模式默认模式,当需要一些来自内核的服务时,便切换到内核模式,如在磁盘上写入数据时。客户模式进程运行在虚拟机内,拥有自己的内核用户空间变量,在客户模式下可以使用正常的kill、ps命令。
KVM 虚拟机表现为一个正常的进程,可以像其他进程一样被杀掉。KVM 利用硬件虚拟技术模拟处理器的形态,虚拟机的内存管理由内核直接处理。内核模式在需要的时候,向 QEMU 进程发送处理信号。
KVM 技术只管理CPU和内存的访问调用,QEMU 工具仿真硬件资源(如硬盘、网卡、声卡等)。
KVM 的 API 是通过 /dev/kvm 设备访问的,/dev/kvm 是一个标准的字符设备,可以使用常用的open、close、isctl 接口操作,所有对 KVM 的操作都是通过 ioctl 接口操作的。KVM 提供给上层的 API 功能可分为3种类型(system指令、VM指令、VCPU指令)。
system 指令:针对虚拟化系统的全局性参数进行设置和控制,包括全局性的参数设置和虚拟机创建等。
VM 指令:针对虚拟机进行控制,大部分需要针对从KVM_CREATE_VM中返回的文件描述符(fd)进行操作,包括配置内存、配置虚拟CPU、运行虚拟主机等。
VCPU指令:针对具体的虚拟CPU进行参数设置,包括寄存器读/写、中断设置、内存设置、时钟管理、调试开关等。KVM 虚拟机运行时也可以进行相关设置。
对于 KVM 的操作都是从打开 /dev/kvm 设备文件开始的,打开后,会获取相应的文件描述符(fd),然后通过 ioctl 系统指令对该 fd 进行进一步的操作(如通过 KVM_CREATE_VM 指令可以创建一个虚拟机并返回虚拟机对应的文件描述符,接着根据该描述符来进一步控制虚拟机的行为,如通过KVM_CREATE_VCPU 指令来为该虚拟机创建 VCPU。)
KVM/QEMU 内存管理:
KVM/QEMU 运行在 Linux 系统中的一个程序运行,所以它分配内存是调用 malloc() 和 mmap() 函数进行的。当一个虚拟机申请 1GB 的物理内存时,KVM/QEMU 会执行malloc(1<<30) 操作,从宿主机上分配 1GB 的虚拟地址空间。所以它只调用 malloc() 函数,并没有进行实际的物理内存分配,而是当虚拟机第一次启动需要访问内存时,才会给虚拟机分配真正的物理内存。虚拟机操作系统启动运行,它可以识别通过 malloc() 函数分配的物理内存,接下来操作系统Kernel 开始访问已识别的物理内存地址,这时KVM/QEMU 进程会访问已识别的第一个内存页。
KVM/QEMU 虚拟机的任何内存变动都会关联到底层宿主机的变化,宿主机会确认该虚拟机变化在其整个内存分页表中是否有效、可用,不允许其访问任何不属于它的内存页,此内存运行访问机制有两种(1、影子分页表技术;2、VMX/AMD-V扩展技术)
1、影子分页表技术:虚拟机所使用的内存分页表与实际的内存分页表是独立的,不是同一张分页表。当虚拟机修改自己的内存分页表时,宿主机会检测到有修改动作,然后进行确认。之后才会修改真正的分区表,使由虚拟机发起的修改操作生效。虚拟机不能直接访问真正的内存分页表,而是访问影子分页表。
2、VMX/AMD-V扩展技术:VMX/AMD-V 扩展技术允许底层宿主机始终监控,以此获得虚拟机修改真正内存分页表的信息。这种内存运行访问机制实际且有效,但是它对性能有一些影响,完成一次访问最高可能消耗25页内存,代价非常大。
关于内存申请和回收理论:
申请内存:
1)QEMU 调用malloc() 函数为虚拟机分配虚拟内存页,但此时并没有申请的真正物理内存。
2)虚拟机开始访问该虚拟内存页,并且认为该虚拟内存页是真正的物理内存页,但是由于该内存页没有被真正分配,所以开始向宿主机申请。
3)宿主机内核发现有一个内存页错误,便会在已经被分配的 malloc()'d 区域调用 do_page_fault() 函数。如果一切顺利,没有打断,则宿主机开始响应虚拟机的操作。
4)宿主机内核创建 pte_t ,便 malloc()'d 虚拟地址连接到真正的物理内存地址,生产 rmap ,并把它们放到 LRU 中。
5)此时,mmu_notifier_change_pte() 被调用,其允许 KVM 为该内存页创建 NPT/EPT。
6)宿主机从该错误的内存页中返回标识,虚拟机得到内存后执行操作恢复。
内存回收
1)宿主机内核利用 rmap 结构寻找到需要回收的内存页被映射到哪个 VMA(vm_area_struct)。
2)宿主机内核查找该 VMA 所关联的 mm_struct,并遍历宿主机的内存分页表,查找到该内存页在物理硬件上的位置。
3)宿主机内核替换出该内存页并清空 pte_t。
4)宿主机内核接着调用 mmu_notifier invalidate_page() 函数,在 NPT/EPT 中查找到该页并删除。
5)现在,该页已经被释放,任何需要该页的访问都向宿主机申请。