qemu-kvm 作为开源虚拟化方案的底层软件技术有着广泛的应用,内核模块 KVM 负责 cpu 虚拟化以及quest-host 内存映射,用户态 QEMU 负责其余部分的虚拟化。相比 qemu 的纯软件模拟方案,虚拟机性能极大提升。本文主要聚焦cpu 虚拟化部分,描述了笔者自己理解的qemu 与kvm 配合的机理。
在qemu 启动虚机的流程中,与kvm 交互的步骤主要包括:
- 创建kvm 虚机 kvm_ioctl(s, KVM_CREATE_VM, type)
- 映射qemu 分配给vm 的内存到 kvm, kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION2, &mem);
- 虚机对应的 vcpu 创建 ioctl(vmfd, KVM_CREATE_VCPU, 0)
虚机创建后可以通过KVM_RUN 去运行vm,kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
创建vm
create_vm
在qemu 中create vm 会对应执行调用 kvm 去创建一个kvm 层面的虚拟机对象,这个对象所有后续操作的基础,内存、VCPU 等都必须属于某个 VM。
static int do_kvm_create_vm(KVMState *s, int type)
{
int ret;
do {
ret = kvm_ioctl(s, KVM_CREATE_VM, type);
} while (ret == -EINTR);
...
return ret;
}
set user memory region
在完成 kvm 层面的虚机创建之后,将 qemu 分配给虚机的内存段和上面创建的kvm虚机对应 ,注册 Guest 物理地址(GPA)与主机虚拟地址(HVA)映射,支持更多内存属性(如只读、共享、加密等)。
kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem)
if (slot->memory_size && !new && (mem.flags ^ slot->old_flags) & KVM_MEM_READONLY) {
/* Set the slot size to 0 before setting the slot to the desired
* value. This is needed based on KVM commit 75d61fbc. */
mem.memory_size = 0;
if (kvm_guest_memfd_supported) {
ret = kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION2, &mem);
} else {
ret = kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem);
}
if (ret < 0) {
goto err;
}
}
create vcpu
对应虚拟机的每一个cpu ,qemu 会对应启动一个线程,每个线程中创建一个vcpu,具体通过 kvm_vm_ioctl(s, KVM_CREATE_VCPU, vcpu_id)创建。向内核的 KVM 子系统申请“创建一个 vCPU 对象”,内核为该 vCPU 分配并初始化内核端数据结构。qemu后续通过这个 vcpu fd 发起后续与该 vCPU 的所有 ioctls。
/**
* kvm_create_vcpu - Gets a parked KVM vCPU or creates a KVM vCPU
* @cpu: QOM CPUState object for which KVM vCPU has to be fetched/created.
*
* @returns: 0 when success, errno (<0) when failed.
*/
static int kvm_create_vcpu(CPUState *cpu)
{
...
if (kvm_fd < 0) {
/* vCPU not parked: create a new KVM vCPU */
kvm_fd = kvm_vm_ioctl(s, KVM_CREATE_VCPU, vcpu_id);
if (kvm_fd < 0) {
error_report("KVM_CREATE_VCPU IOCTL failed for vCPU %lu", vcpu_id);
return kvm_fd;
}
}
...
return 0;
}
运行vm
虚机运行中,qemu是用户态进程,guest的内存等资源在通过qemu 中占用。guest 可以通过ioctl 命令陷入内核和kvm 进行交互。调用KVM 模块,通过非根模式的cpu执行。当 guest 进行IO请求或遇到kvm 无法处理的情况则会回到用户态的qemu中处理。
经典代码如下:
|
|
在intel 场景下,KVM 利用物理 CPU 的虚拟化扩展(如 VT-x),将cpu 切换到非根模式(ring 0),从 VMCS 结构中获取此vcpu 相关的信息(如寄存器值、内存映射表指针)。进行vcpu 相关寄存器的切换,根据 vcpu 寄存器中的值执行指令。上面提到的内存映射表即扩展页表(EPT,Extended Page Tables)将客户机物理地址转换为主机物理地址。这些映射由内核管理,并在发生更改时同步,确保安全且一致的内存访问。
参考
https://www.cnblogs.com/LoyenWang/p/13796537.html