引言
云计算时代,虚拟化技术是整个基础设施的基石。KVM(Kernel-based Virtual Machine)作为 Linux 内核原生支持的虚拟化解决方案,将 Linux 内核转变为一个裸机hypervisor,让操作系统内核本身充当虚拟机的管理者。从 2007 年合并入 Linux 2.6.20 主线内核至今,KVM 已成长为公有云(AWS EC2、Google GCE、阿里云 ECS)的事实标准虚拟化技术栈。
本文将从硬件虚拟化扩展出发,深入讲解 KVM 的核心架构——VMX Root/Non-root 操作模式、VMCS 控制结构、EPT 二级地址翻译机制、vCPU 生命周期管理、QEMU/KVM 用户态/内核态协同、virtio 半虚拟化 I/O 框架,以及嵌套虚拟化、KVM/QEMU 事件通道等进阶专题。
1. 硬件虚拟化基础:VMX 与 SVM
1.1 Intel VMX 操作模式
Intel VT-x 引入了两种全新的处理器操作模式来解决经典的全虚拟化困境(敏感指令捕获问题):
| 模式 | 定义 | 权限 |
|---|---|---|
| VMX Root Operation | Hypervisor(VMM/KVM)运行模式 | 最高特权级,可执行所有指令包括 VMX 指令 |
| VMX Non-root Operation | Guest(虚拟机)运行模式 | 受限特权级,执行敏感指令会触发 VM Exit 陷入 Root 模式 |
// VMX 模式转换(世界切换)的基本流程
// Root → Non-root: VM Entry (vmlaunch/vmresume)
// Non-root → Root: VM Exit (敏感指令/I/O/中断)
┌─────────────────────────────────────────────────────────┐
│ Host (VMX Root) │
│ │
│ 用户态: QEMU / libvirt │
│ ┌──────────────────────────────────────────────┐ │
│ │ ioctl(KVM_RUN) │ │
│ └──────────────────┬───────────────────────────┘ │
│ ▼ │
│ 内核态: KVM 模块 │
│ ┌──────────────────────────────────────────────┐ │
│ │ kvm_vcpu_ioctl_run() │ │
│ │ ├─ vcpu_enter_guest() │ │
│ │ │ ├─ vmx_vcpu_run() │ │
│ │ │ │ ├─ VMRESUME → Guest 执行 │ │
│ │ │ │ │ ... (Guest 运行) │ │
│ │ │ │ ├─ 敏感指令 → VM Exit │ │
│ │ │ │ └─ vmx_handle_exit() │ │
│ │ │ │ ├─ handle_io() │ │
│ │ │ │ ├─ handle_external_interrupt() │ │
│ │ │ │ ├─ handle_ept_violation() │ │
│ │ │ │ └─ ... │ │
│ │ │ └─ 若需用户态处理 → 返回 ioctl │ │
│ │ └─ (QEMU 设备模拟) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 VMCS 控制结构
VM Control Structure(VMCS)是 VMX 的核心数据结构,每个 vCPU 对应一个 VMCS,保存在特定的内存页中(VMCS Region)。VMCS 分为四大区域:
| 区域 | 内容 | 使用方 |
|---|---|---|
| Guest State | CR0/CR3/CR4/RSP/RIP/段寄存器等 Guest 寄存器上下文 | VM Entry 时加载、VM Exit 时保存 |
| Host State | VMM 自身的寄存器上下文(RIP/RSP/CR3 等) | VM Exit 时恢复(从 VMCS 恢复到物理 CPU) |
| VM Execution Control | 控制 VM Exit 触发条件(异常位图、MSR 位图、I/O 位图、引脚/CPU 基于的控制) | VM Entry 前配置 |
| VM Exit Control | 控制 VM Exit 行为(主机地址空间大小、MSR 加载列表) | VM Exit 时应用 |
通过配置 VM Execution Control 中的 Exception Bitmap、I/O Bitmap、MSR Bitmap,KVM 可以精细化控制哪些敏感操作需要陷入内核,哪些可以在 Guest 中直接执行,从而减少 VM Exit 频率。
1.3 AMD SVM:VMRUN 与 VMCB
AMD-V(SVM)的思路与 VMX 类似但指令集不同:
- VMCB(Virtual Machine Control Block):等效于 VMCS,保存 Guest 状态和拦截控制
- VMRUN / #VMEXIT:等效于 VMRESUME / VM Exit
- Clean Bits:VMCB 中允许指定哪些状态字段在 VMRUN 时不需要加载,优化延迟
- ASID(Address Space Identifier):TLB 标签,避免 VM 切换时刷新 TLB
2. EPT:扩展页表与二级地址翻译
2.1 为什么需要 EPT
在硬件虚拟化扩展出现之前,KVM 使用 Shadow Page Table 方案:Guest OS 维护的页表只能将 Guest 虚拟地址映射到 Guest 物理地址,而 Hypervisor 需要维护一层 Shadow 页表将 Guest 物理地址翻译为 Host 物理地址。Guest 每次修改页表(上下文切换、mmap、swap)都会触发 VM Exit 来同步 Shadow 页表,开销极大。
EPT(Intel)/NPT(AMD)引入了第二级硬件页表翻译:
// 两级翻译流程(无 EPT 时需要 Shadow 页表)
// Guest Virtual Address → Guest Physical Address → Host Physical Address
// CPU 硬件自动完成的两步翻译:
//
// Step 1: CR3 → Guest Page Table
// GVA(Guest Virtual Address)通过 Guest 内部的页表
// 翻译为 GPA(Guest Physical Address)
//
// Step 2: EPT Base Pointer (EPTP) → EPT Page Table
// GPA 通过 EPT 页表翻译为 HPA(Host Physical Address)
//
// 整个过程由 CPU MMU 在硬件层面完成,无需软件干预
┌──────────────────────────────────────────────────────────┐
│ Guest OS 视角 │
│ CPU 寄存器 CR3 指向 Guest 页表 │
│ GVA ──────→ (Guest 页表) ──────→ GPA │
│ │
│ CPU 寄存器 EPTP 指向 EPT 页表 (由 KVM 配置) │
│ GPA ──────→ (EPT 页表) ──────→ HPA │
└──────────────────────────────────────────────────────────┘
2.2 EPT 页表结构与标志位
EPT 页表采用 4 级结构(PML4 → PDPT → PD → PT),每级 512 条目,覆盖 48 位物理地址空间:
// EPT 页表条目格式(每个条目 8 字节)
struct ept_entry {
u64 read : 1; // R 位
u64 write : 1; // W 位
u64 execute : 1; // X 位
u64 mem_type : 3; // 内存类型 (UC/WC/WT/WP/WB)
u64 pat : 1; // ignore PAT
u64 accessed : 1; // 由 CPU 硬件置位
u64 dirty : 1; // 由 CPU 硬件置位(仅最后一级)
u64 xu : 1; // User-accessible (SMEP/SMAP bypass)
u64 page_frame : 40; // 下一级页表/物理页基址
u64 reserved : 11;
u64 snoop : 1;
u64 suppress_ve: 1; // Suppress #VE (Virtualization Exception)
};
EPT 的 RWX 权限位精细控制了 Guest 物理内存的访问权限。当 Guest 访问违反 EPT 权限时,触发 EPT Violation VM Exit(KVM 处理),而非 Guest 内部的 Page Fault。
2.3 MMU Notifier 与动态内存
Guest 内核可能将某个 GPA 页面换出(balloon 或 hot-unplug),此时 EPT 条目无效。KVM 通过 MMU Notifier 机制监听 Host 内存压力事件:当 Host 需要回收某个共享页面(KSM 去重或内存超分配),KVM 会先将对应 EPT 条目标记无效,再回收 Host 页面。
3. vCPU 生命周期与调度模型
3.1 vCPU 创建:KVM_CREATE_VCPU
// 用户态 QEMU 创建 vCPU 的调用链
// open("/dev/kvm") → kvm_fd
// ioctl(kvm_fd, KVM_CREATE_VM, type) → vm_fd
// ioctl(vm_fd, KVM_CREATE_VCPU, vcpu_id) → vcpu_fd
//
// 每个 vCPU 在 Host 内核中对应一个 task_struct
// kvm->vcpus[0] 运行 vCPU 0 的调度任务
3.2 vCPU 调度:从 RUNNABLE 到 RUNNING
KVM 中每个 vCPU 对应一个 Host 内核线程。当 QEMU 调用 ioctl(KVM_RUN) 时:
- vCPU 线程标记 TASK_UNINTERRUPTIBLE 后调用 vcpu_enter_guest()
- 通过 vmx_vcpu_run() 在物理 CPU 上执行 VMRESUME,进入 Guest 模式
- Guest 运行直到触发 VM Exit
- vcpu_exit_handler() 处理退出原因
- 若退出原因是 KVM_EXIT_IO / KVM_EXIT_MMIO 等需要用户态处理的事件,返回至 QEMU 用户态
- QEMU 调用设备模拟函数后再次 ioctl(KVM_RUN),循环继续
3.3 VM Exit 分类与优化
| 退出类型 | 触发条件 | 处理方式 | 频度 |
|---|---|---|---|
| 外部中断 | 物理 CPU 收到 IRQ(时钟、网卡、磁盘) | KVM 注入虚拟中断到 Guest | 高频(时钟 ~1000次/s) |
| I/O 指令 | Guest 执行 in/out 指令 | 退出到 QEMU 设备模拟 | 中频(取决于 virtio 使用率) |
| MSR 读写 | Guest 读写模型特定寄存器 | KVM 直接处理(部分)或 QEMU(部分) | 中频 |
| EPT Violation | 违反 EPT 页表权限/未映射 | KVM 分配 Host 页面或触发 #PF | 低频(缺页异常) |
| CPUID | Guest 执行 CPUID | KVM 直接回复 (通常不退出,VMCS 控制) | 基本不退出 |
| HLT | Guest 执行 hlt 等待中断 | KVM 让出 CPU 直到中断事件到达 | 空闲时高频 |
高频 VM Exit 是 KVM 性能的主要杀手。优化的关键策略包括:EPT 大页减少 walk 深度、MSR bitmap 合理配置、时钟中断使用 kvm-clock (pvclock) 半虚拟化设备减少真实中断频率、virtio 替换完整设备模拟等。
4. QEMU/KVM 协同架构
4.1 角色分工
| 组件 | 运行级 | 职责 |
|---|---|---|
| QEMU | Host 用户态 | 设备模拟(网卡、磁盘、GPU 等)、用户接口(monitor、迁移)、Exec(TCG) |
| KVM | Host 内核 | CPU 虚拟化执行、内存虚拟化(EPT)、中断注入、时钟虚拟化、部分简单设备(LAPIC) |
| Guest | VMX Non-root | 运行客户操作系统(Linux/Windows...) |
4.2 事件通道与 irqfd/ioeventfd
QEMU 与 KVM 之间的高效事件通知机制:
// irqfd:QEMU → KVM 的事件通知(例如虚拟中断注入)
// ioeventfd:QEMU 设备完成 I/O 后通知 KVM/Guest
// virtio-blk 的 I/O 路径示例
// 1. Guest 将 I/O 请求写入 virtqueue(描述符表)
// 2. Guest 写 virtio PCI ISR register → VM Exit (PIO)
// 3. QEMU 处理: 通过 ioeventfd 注册, 退出用户态
// 4. QEMU 后端(ioeventfd)提交请求到 Host 设备 (aio / io_uring)
// 5. Host I/O 完成 → QEMU 通过 irqfd 注入中断到 Guest
// 2. Guest 写 virtio PCI ISR register → VM Exit (PIO)
// 3. QEMU 处理: 通过 ioeventfd 注册, 退出用户态
// 4. QEMU 后端(ioeventfd)提交请求到 Host 设备 (aio / io_uring)
// 5. Host I/O 完成 → QEMU 通过 irqfd 注入中断到 Guest
4.3 Memory Slot 管理
KVM 通过 memory slot 机制管理 Guest 物理内存映射:
// 添加内存段到 Guest 物理地址空间
struct kvm_userspace_memory_region region = {
.slot = 0,
.guest_phys_addr = 0,
.memory_size = 0x40000000, // 1GB
.userspace_addr = (u64)mmap_base,
};
ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion);
// 内部流程:
// 1. 建立 Host 虚拟地址 → Host 物理地址的映射
// 2. 遍历 EPT 树,将 GPA 到 HPA 的映射写入 EPT 页表
// 3. 更新 KVM 的 memslot 结构
// 4. 若对应 slot 之前有旧映射,先拆除旧 EPT 条目(TLB flush)
5. virtio:半虚拟化设备框架
5.1 为什么需要 virtio
完整的设备模拟(QEMU 模拟真实硬件如 e1000/rtl8139)存在严重的性能问题:每次 MMIO/PIO 访问都触发 VM Exit,模拟硬件寄存器的读写和事件处理导致大量 Host/Guest 上下文切换。
virtio 提出的解决方案是:让 Guest 知道自己运行在虚拟机上,使用精简的共享内存通信协议替代真实硬件寄存器。Guest 驱动(前端)与 QEMU 设备模拟(后端)通过 virtqueue 环形缓冲区交换数据:
// virtqueue 数据结构
struct vring_desc {
u64 addr; // 缓冲区 GPA
u32 len; // 缓冲区长度
u16 flags; // VRING_DESC_F_NEXT / VRING_DESC_F_WRITE / VRING_DESC_F_INDIRECT
u16 next; // 下一个描述符索引(链式)
};
struct vring_avail {
u16 flags;
u16 idx;
u16 ring[]; // 可用描述符索引队列
};
struct vring_used {
u16 flags;
u16 idx;
struct vring_used_elem ring[]; // 已处理完成项
// .id = 描述符索引, .len = 实际写入字节数
};
5.2 virtio-blk I/O 延迟分析
// 完整 virtio-blk 读路径延迟分解
//
// 0. Guest 内核读块 → 构造 bio → 提交 virtio-blk 驱动
// 1. 将请求放入 avail ring,准备 kick
// 2. Kick: 写 PCI ISR(MMIO/PIO)→ VM Exit
// ~1-3μs(无 IOEVENTFD) / ~0.3μs(有 IOEVENTFD)
// 3. QEMU 进程运行:
// a. 解析描述符链
// b. 将 GPA 翻译为 Host 虚拟地址(vhost 路径在 kernel 完成)
// c. 提交 Host I/O (io_uring 或 libaio)
// ~0.1μs(仅调度)
// 4. Host I/O 完成:
// a. 写 used ring,设置完成项
// b. 通过 irqfd 注入 MSI-X 中断
// c. write(eventfd) → 内核 → 注入中断
// ~0.2-0.5μs(无 IOEVENTFD) / ~0.05μs(有 IOEVENTFD)
// 5. Guest 中断处理 → 完成请求 → 唤醒等待进程
// ~0.3μs
//
// 总计:无优化 ~3-5μs,全优化 ~1μs
5.3 vhost:内核卸载
vhost 进一步将数据平面从 QEMU 卸载到 Host 内核:
// vhost-net 传统路径 vs vhost-user 现代路径
//
// vhost-net(内核后端):
// QEMU 通过 ioctl(VHOST_SET_VRING_ADDR) 告知 KVM 设备的 vring 地址
// 此后 I/O 数据路径完全不经过 QEMU 用户态
// vhost 内核线程直接处理 avail → used ring 的切换
// 仅控制平面(设备配置、DRIVER_OK、FEATURES 协商)仍通过 QEMU
//
// vhost-user(用户态后端):
// QEMU 与 SPDK/DPDK 间通过 Unix 域套接字通信
// 支持容器化后端、热升级、零切换开销
// 美团/阿里云等公有云的大规模部署方案
5.4 virtio-net 性能对比
| 模式 | 单核吞吐 (Gbps) | P99 延迟 (μs) | VM Exit (每秒) |
|---|---|---|---|
| e1000 完全模拟 | ~0.5 | ~1000 | ~500K+ |
| virtio-net(QEMU 后端) | ~1.5 | ~50 | ~50K-100K |
| virtio-net(vhost kernel) | ~3.0 | ~20 | ~5K-10K |
| virtio-net(vhost-user + DPDK) | ~10+ | ~8 | ~0 (轮询) |
6. KVM 中断与时钟虚拟化
6.1 APIC 虚拟化
Guest 的中断控制器(Local APIC)需要由 KVM 在软件中模拟。KVM 利用 VMCS 中的 "Virtual APIC" 和 "Posted Interrupt" 硬件加速:
- Virtual APIC Page:硬件自动维护 APIC 寄存器(EOI、ICR、TPR),无需 VM Exit
- Posted Interrupt:KVM 可直接将虚拟中断注入到目标 vCPU,无需 Host 中断重定向
- VMX Preemption Timer:硬件定时器,在指定 TSC 时刻自动 VM Exit(用于 Guest 调度)
6.2 kvm-clock:半虚拟化时钟
Guest 读取时钟(rdtsc/clock_gettime)若不经过处理会因 TSC offset 和 VM Exit 产生误差。kvm-clock 是virtio 时代替代 LAPIC timer 的方案:
// kvm-clock 工作原理
// 1. Host 内核暴露一个共享内存页给 Guest(MSR_KVM_WALL_CLOCK / SYSTEM_TIME)
// 2. Guest 读取共享页获取 Host TSC 频率和偏移
// 3. Guest 中计算当前时间:Host_base + (TSC_delta * tsc_to_nano)
// 4. 无需 VM Exit 即可获取准确时间(~10ns 开销 vs ~1μs per VM Exit)
// 时钟源注册示例
static pv_time_ops kvm_clock = {
.sched_clock = kvm_clock_read,
.steal_clock = kvm_steal_clock,
};
// 当探测到 kvm-clock PV 设备时,clocksource 切换为 kvm_clock
7. 嵌套虚拟化
在 VM 中运行 VM(例如:在 AWS EC2 实例中运行 KVM 测试)需要嵌套虚拟化支持:
// KVM 嵌套虚拟化(L0: Host KVM → L1: Guest KVM → L2: Nested Guest)
//
// L1 需要运行 VM,需要在 L0 的 VM Non-root 模式下模拟 VMX 操作
// Intel VMX 支持 "VMCS Shadowing"(VMCS 影子),让硬件加速 L1 → L2 的翻译
//
// 执行路径:
// 1. L1 执行 VMRESUME → 陷入 L0 模拟(VM Exit from L1)
// 2. L0 将 L1 VMCS 与 L0 VMCS 合并生成 "Shadow VMCS"
// 3. L0 VMRESUME 到 "Shadow VMCS",其中 EPT 链 = L0-EPT + L1-EPT concatenated
// 4. L2 执行,若触发 L2 的 EPT Violation → 先尝试由 L1 的 EPT 解决
// 5. 若确认需要 L0 → VM Exit 处理
//
// 当前限制:
// - 仅支持 Intel VMX(AMD SVM 嵌套支持较晚)
// - 复杂性高,调试困难
// - 性能相对非嵌套场景下降 10-30%
8. KVM 安全与机密计算
8.1 SEV:Secure Encrypted Virtualization
AMD 的 SEV 为 Guest 虚拟机提供内存加密保护,即使 Hypervisor 也无法读取 Guest 数据:
- SEV:VM 内存使用 VM 私有的 AES-128 密钥加密
- SEV-ES:加密扩展到 vCPU 寄存器状态(VM Exit 时自动加密)
- SEV-SNP:添加完整性保护页表(RMP),防止 Hypervisor 重放/别名攻击
- Intel TDX: Intel 的等效方案(Trust Domain Extensions)
8.2 安全加固最佳实践
// KVM 安全配置清单
// 1. 启动参数
// - cpu host,-pdpe1gb,+invtsc,+tsc-deadline
// - machine q35,kernel_irqchip=on
//
// 2. SELinux/sVirt 隔离
// 每个 VM 运行在不同 SELinux label 下
// 防止 QEMU 进程逃逸访问其他 VM 资源
//
// 3. seccomp 沙箱
// QEMU 启动时启用 seccomp 过滤器
// 限制可用系统调用集(仅 I/O 与控制相关)
//
// 4. 内存加密(SEV/TDX)
// 防止物理攻击和跨 VM 内存泄露
// 适用于公有云多租户环境
9. KVM 性能基准与优化
9.1 环境配置
物理机:AMD EPYC 9654 96-Core
内存: 512GB DDR5-4800
存储: Intel Optane P5800X (NVMe, ~1.5M IOPS)
网络: Solarflare X2522 (100GbE)
内核: Linux 6.5
Guest: 4 vCPU / 8GB RAM / CentOS Stream 9
9.2 CPU 性能损失
| 工作负载 | Native | KVM Guest | 差距 |
|---|---|---|---|
| C-Ray 渲染 | 100% | ~97-99% | <3% |
| 7-zip 压缩 | 100% | ~98-99% | 1-2% |
| 内存带宽 | 100% | ~92-95% | 5-8%(EPT walk开销) |
| 系统调用 (getppid) | 100% | ~95-98% | 2-5%(VM Exit 开销) |
9.3 存储性能
| 模式 | 4K 随机读 IOPS | 延迟 (P50/P99) |
|---|---|---|
| Native (io_uring) | ~1,500,000 | ~5μs / ~10μs |
| virtio-blk (QEMU) | ~800,000 | ~15μs / ~35μs |
| virtio-blk (vhost) | ~1,200,000 | ~8μs / ~18μs |
| vhost-user (SPDK) | ~1,400,000 | ~5μs / ~12μs |
9.4 网络性能
| 模式 | 64B 单核 QPS | 1460B 单核吞吐 |
|---|---|---|
| virtio-net (QEMU) | ~300K | ~3 Gbps |
| virtio-net (vhost-kernel) | ~600K | ~8 Gbps |
| virtio-net (vhost-user + DPDK) | ~2M+ | ~25 Gbps |
| SR-IOV (VF 直通) | ~3M+ | ~100 Gbps(线速) |
10. KVM 进阶:热迁移与实时优化
10.1 预拷贝热迁移(Pre-copy Live Migration)
- 迭代预拷贝:第一轮传输全部内存页,后续仅传输上一轮中被修改(dirty)的页面。重复直到脏页量小于阈值或达到最大迭代数
- 停机-拷贝:暂停 Guest VCPU,拷贝剩余的脏页和 CPU 状态,恢复远程 Guest
- 总迁移时间取决于内存变化速率(dirty rate)和网络带宽。典型场景下 8GB VM 可在 2-10 秒内完成迁移
10.2 多队列 virtio 与并发优化
现代 virtio 设备支持多队列(multi-queue),将网络/存储流量分发到不同 vCPU:
// 多队列 virtio-net 配置
// -device virtio-net-pci,mq=on,vectors=10
// -netdev tap,vhost=on,queues=4
//
// 内部流程:
// - 物理中断通过 RSS(接收端扩展)分流到不同 Host CPU
// - 每个 Host CPU 服务的 vCPU 处理对应的 virtqueue
// - 完全消除单队列的锁争用和 CPU 瓶颈
11. 总结与展望
KVM 代表了操作系统级虚拟化技术的顶峰——通过硬件虚拟化扩展、EPT 二级映射、virtio 半卸载、vhost 内核卸载等多层优化,将虚拟机性能推至接近裸机的水平。CPU 开销控制在 3% 以内,I/O 吞吐量可达线速水平,让 KVM 同时胜任高性能计算、数据库、金融交易等延迟敏感场景。
展望未来,三大趋势值得关注:机密计算(Intel TDX、AMD SEV-SNP)让虚拟化安全模型进入新纪元;DPU/IPU 卸载进一步将数据面从主机 CPU 剥离;用户态 hypervisor(Cloud Hypervisor、Firecracker)则在轻量化和快速启动方面开辟新路径。Linux 虚拟化技术栈仍在持续演进,为下一代云原生基础设施筑牢根基。
参考资料
- KVM 官方文档:https://www.linux-kvm.org/
- Linux 内核 KVM 源码:
arch/x86/kvm/ - QEMU 官方文档:QEMU System Emulation
- "KVM: Kernel-based Virtual Machine" — 原始论文 (Kivity et al., 2007)
- virtio 规范:OASIS virtio-v1.2
- Linux 内核文档:
Documentation/virt/kvm/

发表评论 取消回复