4. KVM架构与源码结构

KVM 并不是一个独立运行的虚拟机监控器,而是 Linux 内核中的一个虚拟化子系统。它把 Linux 内核本身变成 Hypervisor,向上通过字符设备 /dev/kvm 暴露统一的控制接口,向下依赖 CPU 提供的硬件虚拟化扩展。在 ARM64 平台上,这套扩展就是 EL2 异常级别、HCR_EL2 寄存器、VGIC 中断虚拟化以及 Stage-2 地址翻译。理解 KVM 的架构,核心是理解它如何用“一个内核模块 + 两类抽象对象 + 一组 ioctl 命令”把复杂的硬件虚拟化能力封装成用户态可编程的接口。

一、KVM 内核模块:从加载到 /dev/kvm

KVM 在源码中以模块形式组织。x86 上分为 kvm.ko 与 kvm-intel.ko,而 ARM64 上通常编译为 kvm.ko,其架构相关代码位于 arch/arm64/kvm/。模块初始化时会调用 kvm_init(),注册字符设备、初始化全局的 VM 与 vCPU 链表,并设置架构相关的回调函数表 kvm_arch_ops。加载成功后,用户态即可看到 /dev/kvm 设备节点。

# 查看 KVM 模块与设备节点
lsmod | grep kvm
ls -l /dev/kvm
dmesg | grep -i kvm

# 典型输出
kvm [ 1234.567] kvm: initialized
kvm [ 1234.568] kvm: KVM_CAP_ARM_VMID_BITS = 8

这个模块承担三类职责:第一,管理硬件虚拟化能力的探测与使能,例如检查 CPU 是否支持 EL2、是否支持 52 位物理地址;第二,提供 VM 与 vCPU 的生命周期管理;第三,处理用户态通过 ioctl 下发的所有控制命令。可以说,/dev/kvm 是用户态与 KVM 内核模块之间的唯一入口。

二、VM 抽象与 vCPU 抽象

KVM 用两个核心结构体描述虚拟机:struct kvm 表示一个 VM,struct kvm_vcpu 表示一个虚拟 CPU。一个 VM 可以包含多个 vCPU,每个 vCPU 在内核中对应一个线程上下文,并拥有独立的寄存器组、异常级别状态和 Stage-2 页表视图。

  • VM 抽象:持有内存槽(memory slot)、Stage-2 页表、虚拟中断控制器、VMID 等全局资源。用户态通过 KVM_CREATE_VM 创建,得到 VM 的文件描述符。
  • vCPU 抽象:持有通用寄存器、系统寄存器、PC、PSTATE、vGIC 的 CPU 接口状态。用户态通过 KVM_CREATE_VCPU 创建,得到 vCPU 的文件描述符。
  • 运行关系:vCPU 必须绑定到某个 VM,多个 vCPU 共享同一份 Stage-2 页表和虚拟中断控制器。

在 ARM64 上,vCPU 的运行入口是 kvm_arch_vcpu_ioctl_run(),它最终会执行 kvm_arm_vcpu_enter_exit(),通过 eret 指令切换到 EL2 或 EL1 执行 Guest 代码。当 Guest 触发敏感指令或外部中断时,CPU 会陷入 EL2,KVM 根据退出原因决定是继续运行还是返回用户态。

三、ioctl 接口:用户态控制 KVM 的通道

KVM 的所有控制都通过 ioctl 完成。用户态程序(如 QEMU、Firecracker、crosvm)打开 /dev/kvm 后,先创建 VM,再创建 vCPU,然后反复调用 KVM_RUN 驱动 vCPU 执行。下表列出 ARM64 上最常用的 ioctl 命令及其作用。

ioctl 命令作用对象说明
KVM_GET_API_VERSION/dev/kvm获取 KVM API 版本,通常为 12
KVM_CREATE_VM/dev/kvm创建一个 VM,返回 VM fd
KVM_CREATE_VCPUVM fd创建 vCPU,返回 vCPU fd
KVM_ARM_VCPU_INITvCPU fd初始化 vCPU 的复位状态与特性
KVM_SET_USER_MEMORY_REGIONVM fd注册用户态内存作为 Guest 物理内存
KVM_RUNvCPU fd运行 vCPU,直到发生 VM Exit
KVM_GET_REGS / KVM_SET_REGSvCPU fd读写通用寄存器
KVM_GET_ONE_REG / KVM_SET_ONE_REGvCPU fd读写单个系统寄存器

其中 KVM_RUN 是最关键的命令。它并不返回 Guest 的执行结果,而是返回一个退出原因 struct kvm_run,其中 exit_reason 字段告诉用户态“为什么退出”。常见的退出原因包括 KVM_EXIT_MMIO、KVM_EXIT_IO、KVM_EXIT_ARM_NISV 以及 KVM_EXIT_SYSTEM_EVENT。用户态必须根据退出原因进行模拟,然后再次调用 KVM_RUN。

典型控制循环:打开 /dev/kvm → KVM_CREATE_VM → KVM_CREATE_VCPU → KVM_ARM_VCPU_INIT → 注册内存 → 设置寄存器 → 循环 KVM_RUN → 处理 exit_reason → 再次 KVM_RUN。

四、ARM64 KVM 目录组织

ARM64 的 KVM 源码分布在多个目录中,理解目录结构有助于快速定位问题。通用 KVM 代码位于 virt/kvm/,ARM64 架构相关代码位于 arch/arm64/kvm/,而 ARM 共用的虚拟化代码位于 arch/arm64/kvm/ 与 virt/kvm/arm/ 之间。下表给出主要目录与职责。

路径职责
virt/kvm/kvm_main.c通用 VM/vCPU 管理、ioctl 分发、内存槽管理
arch/arm64/kvm/ARM64 架构初始化、vCPU 进入退出、系统寄存器处理
arch/arm64/kvm/vgic/虚拟 GIC 中断控制器实现
arch/arm64/kvm/sys_regs.c系统寄存器访问陷入与模拟
arch/arm64/kvm/mmu.cStage-2 页表管理与内存映射
arch/arm64/kvm/hyp/EL2 异常级别下的底层运行代码
include/uapi/linux/kvm.h用户态可见的 ioctl 命令与结构体定义

其中 arch/arm64/kvm/hyp/ 是 ARM64 特有的关键目录,它包含在 EL2 下运行的代码,例如 vhe/ 与 nvhe/ 两种模式。VHE(Virtualization Host Extensions)允许 KVM 直接复用 EL1 的代码路径,减少上下文切换开销;而 nVHE 则把 Host 与 Hypervisor 严格分离,适合安全隔离场景。

五、案例:一次 KVM_RUN 的完整路径

假设用户态 QEMU 已经创建好 VM 与 vCPU,并注册了内存。当 QEMU 调用 KVM_RUN 时,内核路径大致如下:

  1. 用户态执行 ioctl(vcpu_fd, KVM_RUN, 0)。
  2. 内核进入 kvm_vcpu_ioctl(),分发到 kvm_arch_vcpu_ioctl_run()。
  3. ARM64 代码保存 Host 上下文,配置 HCR_EL2、VTTBR_EL2 等寄存器。
  4. 执行 eret 进入 Guest,Guest 在 EL1/EL0 运行。
  5. Guest 访问 MMIO 或执行 WFI,触发异常陷入 EL2。
  6. KVM 填充 struct kvm_run 的 exit_reason,返回用户态。
  7. QEMU 读取退出原因,模拟设备或处理中断,再次调用 KVM_RUN。

这个循环是 KVM 虚拟化的基本节拍。每一次 KVM_RUN 都可能对应多次 VM Exit,而用户态模拟的效率直接决定了虚拟机的整体性能。

调试 KVM 时,可以打开 /sys/module/kvm/parameters/ 下的参数,或使用 trace-cmd 跟踪 kvm_exit 事件,快速定位退出原因分布。

六、常见误区与复盘

  • 误区一:把 KVM 当成完整 Hypervisor。 KVM 只提供内核态虚拟化能力,设备模拟、固件加载、虚拟机生命周期管理都由用户态完成。
  • 误区二:认为 vCPU 就是物理 CPU。 vCPU 是内核线程,多个 vCPU 可以调度到同一个物理 CPU 上,也可以迁移。
  • 误区三:忽略 ioctl 的返回值。 KVM_RUN 返回 0 并不代表 Guest 执行成功,必须检查 kvm_run.exit_reason。
  • 误区四:混淆 Stage-2 与 Stage-1 页表。 Stage-1 由 Guest 内核管理,Stage-2 由 KVM 管理,两者共同完成 Guest 虚拟地址到物理地址的翻译。

复盘时建议从三个维度检查:第一,/dev/kvm 是否可访问、KVM 模块是否加载;第二,VM 与 vCPU 的创建顺序是否正确,内存区域是否覆盖 Guest 的物理地址空间;第三,KVM_RUN 的退出原因是否被正确处理。掌握这三层,就掌握了 KVM 架构的主干。

KVM 架构与 ARM64 源码组织 用户态 (QEMU / Firecracker / crosvm) ioctl 控制循环 设备模拟 / 内存管理 KVM_RUN 循环 exit_reason 处理 /dev/kvm ioctl KVM 内核模块 (kvm.ko) VM 抽象 struct kvm Stage-2 页表 / 内存槽 VMID / vGIC 全局状态 vCPU 抽象 struct kvm_vcpu 通用寄存器 / 系统寄存器 PC / PSTATE / vGIC CPU 接口 ioctl 分发层 kvm_vcpu_ioctl() kvm_arch_vcpu_ioctl_run() eret / EL2 陷入 ARM64 硬件虚拟化扩展 EL2 异常级别 HCR_EL2 / VTTBR_EL2 Stage-2 地址翻译 IPA → PA 虚拟中断 vGIC IRQ / FIQ 注入 系统寄存器 陷入与模拟 源码目录: virt/kvm/kvm_main.c arch/arm64/kvm/ arch/arm64/kvm/hyp/ include/uapi/linux/kvm.h