2. 虚拟化基础
要理解 VirtIO 虚拟设备系统,必须先回到虚拟化的地基:谁在管理硬件、谁在运行客户机、CPU 在什么特权级执行、敏感指令如何被拦截、设备又是如何被“假装”出来的。本节把这些概念串成一条完整链路,为后续 VirtIO 队列、通知与驱动开发打基础。
一、Hypervisor:硬件与虚拟机之间的管理层
Hypervisor(虚拟机监控器,VMM)是运行在物理硬件之上、负责创建和调度虚拟机的软件层。它的核心职责包括:CPU 虚拟化、内存虚拟化、中断虚拟化、设备虚拟化以及虚拟机生命周期管理。按部署形态可分为两类:
- Type 1(裸机型):直接运行在硬件上,如 KVM(以内核模块形式)、Xen、VMware ESXi。性能好,常见于服务器与云平台。
- Type 2(宿主型):运行在宿主操作系统之上,如 QEMU 配合 KVM、VirtualBox。部署方便,常用于开发调试。
在 Linux 生态中,KVM 提供 CPU 与内存虚拟化能力,QEMU 提供设备模型与虚拟机管理,二者组合是最常见的开发环境。理解这一点很关键:VirtIO 设备的前端在客户机内核,后端通常就在 QEMU 进程里。
二、特权级与陷入模拟:虚拟化的核心机制
CPU 通过特权级(x86 的 Ring 0~3,ARM 的 EL0~EL3)隔离权限。客户机操作系统原本以为自己运行在最高特权级,实际上被降级运行在非特权模式,而 Hypervisor 占据真正的最高特权级。当客户机执行敏感指令(如修改页表基址、访问 I/O 端口、执行 HLT)时,CPU 触发异常,控制权被交给 Hypervisor,这个过程称为陷入(trap);Hypervisor 检查并模拟该操作后,再通过 VM-Exit / VM-Entry 返回客户机,称为模拟(emulate)。
这种“陷入—模拟—返回”的路径开销很大,因此现代 CPU 引入硬件辅助虚拟化(Intel VT-x、AMD-V、ARM VHE),把部分拦截与上下文切换交给硬件完成。但设备访问若每次都陷入,性能依然不可接受,这正是 VirtIO 要解决的问题。
三、设备虚拟化的三种基本方式
设备虚拟化要回答一个问题:客户机看到的“网卡”“磁盘”到底是什么?常见实现有三类:
| 方式 | 原理 | 性能 | 典型场景 |
|---|---|---|---|
| 全虚拟化(模拟) | Hypervisor 完全用软件模拟真实设备寄存器与行为 | 低,每次访问都陷入 | 兼容旧系统、无驱动改造 |
| 半虚拟化(VirtIO) | 客户机安装专用前端驱动,与后端通过共享内存队列协作 | 高,减少陷入次数 | Linux 云主机、KVM/QEMU |
| 设备直通(Passthrough) | 借助 IOMMU 把物理设备直接分配给虚拟机 | 接近原生 | 高性能网卡、GPU 直通 |
VirtIO 属于半虚拟化路线:它不模拟真实硬件寄存器,而是定义一套标准化、可发现、可协商的虚拟设备接口。客户机驱动知道自己在跟虚拟设备打交道,因此可以主动使用共享内存、批量提交、延迟通知等优化手段。
四、案例:一次磁盘写请求的完整路径
假设客户机应用调用 write() 写文件,最终落到 VirtIO 块设备上,路径大致如下:
应用 write()
→ 客户机文件系统 → 块层 → VirtIO-blk 前端驱动
→ 把请求描述符放入 virtqueue(共享内存)
→ 写 MMIO 寄存器通知后端(一次 trap / VM-Exit)
→ QEMU 中 VirtIO 后端取出描述符
→ 后端调用宿主 OS 的 aio/pwrite 写真实磁盘
→ 完成后写中断寄存器,注入虚拟中断给客户机
→ 客户机中断处理,回收描述符,唤醒应用
可以看到,真正昂贵的“陷入”只发生在通知环节,数据本身通过共享内存传递,不需要逐字节模拟。这就是 VirtIO 高性能的根本原因。
五、常见误区与复盘
- 误区一:客户机内核运行在 Ring 0,所以能直接操作硬件。实际上客户机内核运行在受限特权级,物理地址、I/O 端口、中断都受 Hypervisor 管控。
- 误区二:VirtIO 设备就是 QEMU 模拟出来的普通设备。VirtIO 设备有明确的规范(OASIS VirtIO 标准),前端驱动与后端实现必须遵循同一套队列、特性位与通知机制。
- 误区三:陷入越少越好,所以全部用直通。直通依赖 IOMMU、牺牲可迁移性与资源超分能力,VirtIO 在性能与灵活性之间取得了更好的平衡。
- 复盘要点:学习 VirtIO 开发时,要同时关注三条线——客户机前端驱动、virtqueue 共享内存布局、后端设备实现。任何一条线理解偏差,都会导致“设备识别不到”“队列卡死”“中断丢失”等典型问题。