IO设备虚拟化介绍
主流的IO设备虚拟化方案包括,
1. 纯软件模拟,完全利用软件模拟出一些设备给虚拟机用,如纯QEMU的解决方案
2. 半虚拟化,主要是一种frontend-backend模型,虚拟机中Guest OS的设备驱动程序作为frontend,Hypervisor中暴露backend接口,这种解决方案需要修改修改Guest OS,或提供半虚拟化的设备驱动
3. 硬件虚拟化,主流方案包括SR-IOV、VT-D等可以把整个设备直接透传给虚拟机,或者设备支持SR-IOV,就可以把设备的VF(Virtual Function)分配给虚拟机
纯软件模拟,Hypervisor必须完全模拟硬件设备,虚拟机内部设备驱动完全不知道⾃⼰处在虚拟化环境中,所以I/O操作会完整的⾛虚拟机内核栈->QEMU->宿主机 内核栈,这种模拟很彻底,但也复杂,效率低下
相比于纯软件模拟,半虚拟化方案中,虚拟机内部设备驱动知道⾃⼰处在虚拟化环境中,并且需要和Hypervisor共同协作
virtio
virtio就是IO虚拟化中一种非常优秀的半虚拟化方案,Guest OS中运行virtio设备的驱动程序,通过virtio设备和后端的Hypervisor或用于加速的vhost进行交互
在QEMU中,virtio设备是QEMU为Guest OS虚拟的PCI设备或PCIe设备,虚拟设备遵循PCI规范,可以具有配置空间、中断配置等功能。如虚拟的存储设备virtio-blk、virtio-scsi
virtio在QEMU中的实现分三层,frontend是虚拟设备层,位于Guest OS内;中间层是virtqueue,负责传输数据及命令交互,跨Guest OS和QEMU;最下层是virtio backend设备层,用于完成来自frontend的命令请求。
QEMU virtio-scsi
QEMU virtio-scsi是最初的virtio-scsi的后端实现,Guest和QEMU之间通过virtqueue进行数据交换,当Guest提交SCSI命令到virtqueue时,根据virtio PCI设备定义,Guest会把该队列的ID写入PCI配置空间中,通知PCI设备有新的SCSI请求已经就绪;之后QEMU会得到通知,基于Guest填写的队列ID到指定的virtqueue获取最新的SCSI请求;最后发送到该模拟PCI设备的后端,这里后端可以是宿主机系统上的一个文件或块设备分区。当SCSI命令在后端的文件或块设备执行完成并返回给virtio-scsi backend模块后,QEMU会向该PCI设备发送中断通知,从而Guest基于该中断完成整个SCSI命令流程。这有两个严重影响性能的因素:
1. 当Guest提交新的SCSI请求到virtqueue队列时,需要告知QEMU哪个队列含有最新的SCSI命令
2. 在实际处理具体的SCSI读/写命令时(在host 中),存在用户态到内核态的数据复制
数据复制影响性能,比较好理解。为什么说Guest提交新的SCSI请求时也严重影响性能呢?根据virtio协议,Guest提交请求到virtqueue时需要把该队列的ID写入PCI配置空间,所以每个新的命令请求都会写入一次PCI的配置空间。在X86虚拟化环境下,Guest中对PCI空间的读/写是特权指令,需要更高级别的权限,因此会触发Hypervisor的Trap,从而导致VM_EXIT事件,CPU需要切换上下文到QEMU进程去处理该事件,在虚拟化环境下,VM_EXIT对性能有重大影响
kernel vhost-scsi
QEMU virtio-scsi有两点严重影响新能,数据复制,因虚拟机陷出引起的上下文切换。基于LIO在内核空间实现为虚拟机服务的SCSI设备,virtqueue中数据处理的逻辑在内核空间完成,就从源头上优化掉了数据复制的过程,kernel vhost-scsi就是这么做的,但是该方案并没有完全模拟一个PCI设备,QEMU仍然负责对该PCI设备的模拟,仍然没有优化掉因为虚拟机陷出而引起的CPU上下文切换所产生的消耗
SPDK vhost-user-scsi
SPDK vhost-user-scsi的virtio-scsi后端实现方案,使用零拷贝技术消除了数据复制对性能的影响,通过轮询所有的virtqueue又消除更新PCI的配置空间所带来的Hypervisor自陷引起的CPU上下文切换的损耗,基本解决了QEMU virtio-scsi性能提升的两个关键点,见QEMU-based VM communicates with SPDK Vhost-SCSI device