qemu-kvm 原理初探

qemu-kvm 作为开源虚拟化方案的底层软件技术有着广泛的应用,内核模块 KVM 负责 cpu 虚拟化以及quest-host 内存映射,用户态 QEMU 负责其余部分的虚拟化。相比 qemu 的纯软件模拟方案,虚拟机性能极大提升。本文主要聚焦cpu 虚拟化部分,描述了笔者自己理解的qemu 与kvm 配合的机理。

在qemu 启动虚机的流程中,与kvm 交互的步骤主要包括:

  1. 创建kvm 虚机 kvm_ioctl(s, KVM_CREATE_VM, type)
  2. 映射qemu 分配给vm 的内存到 kvm, kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION2, &mem);
  3. 虚机对应的 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中处理。

经典代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int kvm_cpu_exec(CPUState *cpu)
{
    //...
    do {
        // kvm_vcpu_ioctl(cpu, KVM_RUN, 0)
        // 从这里进入kvm内核阶段,开始运行虚拟机
        run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
        //...
        // 根据退出原因,分发处理
        switch (run->exit_reason) {
        case KVM_EXIT_IO:
            DPRINTF("handle_io\n");
            /* Called outside BQL */
            kvm_handle_io(run->io.port, attrs,
                          (uint8_t *)run + run->io.data_offset,
                          run->io.direction,
                          run->io.size,
                          run->io.count);
            ret = 0;
            break;
        default:
            DPRINTF("kvm_arch_handle_exit\n");
            ret = kvm_arch_handle_exit(cpu, run);
            break;
        }
    } while (ret == 0);
    cpu_exec_end(cpu);
    //...
    qatomic_set(&cpu->exit_request, 0);
    return ret;
}

在intel 场景下,KVM 利用物理 CPU 的虚拟化扩展(如 VT-x),将cpu 切换到非根模式(ring 0),从 VMCS 结构中获取此vcpu 相关的信息(如寄存器值、内存映射表指针)。进行vcpu 相关寄存器的切换,根据 vcpu 寄存器中的值执行指令。上面提到的内存映射表即扩展页表(EPT,Extended Page Tables)将客户机物理地址转换为主机物理地址。这些映射由内核管理,并在发生更改时同步,确保安全且一致的内存访问。

参考

https://www.cnblogs.com/LoyenWang/p/13796537.html

https://remimin.github.io/2018/09/10/kvm-vmx/

https://cppguide.cn/pages/the-kernel-in-the-mind-ch29/

updatedupdated2025-11-202025-11-20