KVM虚拟化之VM Exit/Entry

Posted by min on September 10, 2018

KVM虚拟化之VM Exit/Entry

X86处理有4个特权级别Ring0~Ring3,只有运行在 Ring0 ~ 2级时,处理器才可以访问特权资源或执行特权指令。 运行在 Ring 0 级时,处理器可以访问所有的特权状态。Linux只使用Ring 0和Ring 3这两个级别, 操作系统运行在Ring 0级,用户进程运行在Ring 3级。

Intel硬件虚拟化技术VT-x。VT-x为处理器增加了两种操作模式:VMX root operation 和 VMX non-root operation。 VMM 自己运行在VMX root operation 模式,VMX non-root operation 模式则由Guest OS 使用。 两种操作模式都支持Ring 0 ~ Ring 3 这 4 个特权级,因此VMM和 Guest OS 都可以自由选择它们所期望的运行级别。

VMX(Virtual Machine Extension)通过引入根运行模式(VMX root operation) 和非根模式(VMX non-root operation),直接让vCPU运行在逻辑CPU上,在软件上省去了对vCPU运行的模拟,大大提升了性能。 剩下的就是对vCPU状态的记录了,为此Intel引入了VMCS(Virtual Machine Control Structure)功能。

VMCS

VMM 和 Guest OS 共享底层的处理器资源,因此硬件需要一个物理内存区域来自动保存或恢复彼此执行的上下文。 这个区域称为虚拟机控制块(VMCS),包括客户机状态区(Guest State Area),主机状态区(Host State Area)和执行控制区。

VM entry

qemu-kvm进程调用kvm_exec_cpu函数初始化vcpu,最终通过kvm_vcpu_ioctl调用进入host kvm.mo内核进行处理 进入kvm.ko后的处理流程入下:


kvm_vcpu_ioctl(kvm_main.c)-->kvm_arch_vcpu_ioctl_run(kvm/x86.c)
    -->vcpu_run(kvm/x86.c)-->vcpu_enter_guest(kvm/x86.c)
                                1. kvm_x86_ops->prepare_guest_switch(vcpu);
                                2. kvm_load_guest_xcr0(vcpu);
                                3. kvm_x86_ops->run(vcpu); 
                                4. 调用 -->vmx_vcpu_run(vmx.c) (store host stat & Enter guest mode)

第一次启动Guest,通过VMLAUNCH指令加载Guest,这时候一切都是新的,比如说起始的rip寄存器等。 后续Guest exit后再entry,是通过VMRESUME指令,此指令会将VMCS所指向的内容加载到当前Guest的上下文, 以便Guest继续执行。

##VM exit

Guest OS运行在VMX non-root模式中,当执行默写特权执行时,则会引发VM Exit。Guest Exit Reason保存在MSR寄存器中。 arch/x86/kvm/vmx.c:kvm_vmx_exit_handlers 定义了guest Exit的内核处理函数。 例如 [EXIT_REASON_CR_ACCESS] = handle_cr,, 当Guest OS访问cr寄存器时,即回产生 VM Exit,将控制权交予kvm,调用handle_cr处理。对应到KVM就是vCPU->kvm_run->exit_reason。KVM根据exit_reason做相应的处理。 VM Exit退出的代码是vmx.c:vmx_vcpu_run函数后半段开始执行,执行路径与vm entry相反,一层层回调处理到qemu-kvm中的kvm_exec_cpu

由此可以见,当Guest OS需要执行root特权指令时,引发的VM Exist,其实时从用户态(qemu) -> 内核态(kvm)-> 用户态(qemu) 过程

官网的Architecture 对整个流程描述的非常清晰

arch

KVM Makefile

kvm kernel moduler Makefile,由此可见host kernel的kvm包含两部分代码virt/kvm/arch/x86/kvm 不同的硬件虚拟化对应不同的mode,如kvm-intel.ko 包含vmx.cpmu_intel.c,而amd硬件虚拟化则是 svm.cpmu_amd.c

# SPDX-License-Identifier: GPL-2.0

ccflags-y += -Iarch/x86/kvm

CFLAGS_x86.o := -I.
CFLAGS_svm.o := -I.
CFLAGS_vmx.o := -I.

KVM := ../../../virt/kvm

kvm-y			+= $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o \
				$(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o
kvm-$(CONFIG_KVM_ASYNC_PF)	+= $(KVM)/async_pf.o

kvm-y			+= x86.o mmu.o emulate.o i8259.o irq.o lapic.o \
			   i8254.o ioapic.o irq_comm.o cpuid.o pmu.o mtrr.o \
			   hyperv.o page_track.o debugfs.o

kvm-intel-y		+= vmx.o pmu_intel.o
kvm-amd-y		+= svm.o pmu_amd.o

obj-$(CONFIG_KVM)	+= kvm.o
obj-$(CONFIG_KVM_INTEL)	+= kvm-intel.o
obj-$(CONFIG_KVM_AMD)	+= kvm-amd.o

qemu与kvm交互

每创建一个虚拟机会有一个qemu进程与之对应,众所周知,qemu项目提供了完整的软件虚拟化的实现,但是纯软件的实现意味着需要 捕捉guest OS特权指令转化,性能很差。所以qemu-kvm项目为qemu与内核kvm模块,而kvm模块实现了硬件虚拟化。

qemu-kvm与kvm的交互是通过kvm module注册的设备及ioctl调用实现的。

kvm模块

kvm模块初始化创建了/dev/kvm的micsdevice的设备,从而可以使得用户态的程序通过/dev/kvm设备与kvm模块交互。 kvm_dev_ioctl函数即为kvm提供的ioctl调用接口。

static struct file_operations kvm_chardev_ops = {
	.unlocked_ioctl = kvm_dev_ioctl,
	.llseek		= noop_llseek,
	KVM_COMPAT(kvm_dev_ioctl),
};

static struct miscdevice kvm_dev = {
	KVM_MINOR,
	"kvm",
	&kvm_chardev_ops,
};

static struct file_operations kvm_vm_fops = {
	.release        = kvm_vm_release,
	.unlocked_ioctl = kvm_vm_ioctl,
	.llseek		= noop_llseek,
	KVM_COMPAT(kvm_vm_compat_ioctl),
};

# ioctl KVM_CREATE_VM 调用kvm_dev_ioctl_create_vm,为vm注册fop
# 即每个vm都对应一个fd,操作通过kvm_vm_fops陷入内核
file = anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR);

static struct file_operations kvm_vcpu_fops = {
	.release        = kvm_vcpu_release,
	.unlocked_ioctl = kvm_vcpu_ioctl,
	.mmap           = kvm_vcpu_mmap,
	.llseek		= noop_llseek,
	KVM_COMPAT(kvm_vcpu_compat_ioctl),
};

# KVM_CREATE_VCPU -> kvm_vm_ioctl_create_vcpu -> create_vcpu_fd 
# 每个vcpu都对应一个fd,操作通过kvm_vcpu_ioctl陷入内核
anon_inode_getfd(name, &kvm_vcpu_fops, vcpu, O_RDWR | O_CLOEXEC);

qemu-kvm/kvm_all.c:kvm_init qemu-kvm用户态初始化通过

s->fd = qemu_open("/dev/kvm", O_RDWR)

即建立了qemu和kvm.ko之间的联系。

###

MMU虚拟化

GVA - Guest Virtual Address,虚拟机的虚拟地址
GPA - Guest Physical Address,虚拟机的虚拟地址
GFN - Guest Frame Number,虚拟机的页框号
HVA - Host Virtual Address,宿主机虚拟地址
HPA - Host Physical Address,宿主机物理地址
HFN - Host Frame Number,宿主机的页框号

•GVA -> GPA (guest page table) •GPA -> HVA (memslot) •HVA -> HPA (QEMU page table) •GPA -> HPA (nested page table)