Virtio-net 到Vhost

Vhost通过将virtio data plane的实现offload到另外一个地方(可以是用户进程、也可以是内核模块),目的是为了提高性能。假想下,如果没有Vhost,我们在Qemu/Vmm中实现virtio data plane部分。整体的流程是怎么样的:

Untitled

Qemu收到virtio net驱动侧发过来的数据,这里会涉及到Guest kernel vmexit,然后再从KVM侧切换到Qemu用户态,然后Qemu从virtio ring中读取数据,因为virito net的设备模拟实际上是一个TAP设备,最终virtio net 设备侧会通过socket接口将数据通过TAP设备发送出去,这里实际上就会涉及到Host 用户态到内核态的切换,还有相关的socket数据拷贝。详细的过程如下:virtio0net通过PCI scan进行发现,然后将virtio-net驱动绑定到Qemu模拟的virtio-net设备,开始进行设备的初始化。等一起Redy后,Guest开始通过虚拟网卡发送网络包,这里会触发pci over mmio的一个available buffer notification(实际应该就是一段memory的page fault)会导致KVM vmexit到QEMU,QEMU读取vring中的数据,然后然后同sendmsg syscall发送,这个会导致切换到内核态,然后将数据拷贝到内核态,然后通过真实的网卡发送出去,最后QEMU通过iocal给vCPU注入中断,最后vCPU继续执行。

Untitled

通过上面的过程可以看到,这里面有几个不高效的地方:

  1. vCPU 停止运行,然后从Guest内核切换到KVM,这里实际上存在一个VMM的上下文切换,切换到KVM后,KVM发现是一个memory fault(MMIO)于是又发生了vmexit切换到QEMU。
  2. QEMU这里存在syscall和数据拷贝,以及用户态到内核态的上下文切换
  3. QEMU通过ioctl注入中断,这里存在一个syscall的上下文切换
  4. 最后QEMU通过ioctl恢复vCPU的执行,这里又存在一次syscall的上下文切换,和VMM的上下文切换

可以看到这里从virtio net驱动侧收到的数据没必要再走QEMU过一遍,可以直接通过内核发送出去。因此有了Vhost。下面是Vhost的架构。可以看到Virtio-net和Vhost-net直接通信,绕开了QEMU。因此性能会得到提升。 此外,对于vitio的通知和中断注入,也是非常不高效。为了通知QEMU需要多次上下文转换,VMM切换、内核态到用户态切换。到了QEMU后,实际上QEMU是可以异步来处理网络包,让VM继续运行。所以这里的通知实际上代价有点大了。

Untitled

为此KVM设计了eventfd,来减少上下文切换的开销。这个机制被称为轻量级虚拟机退出。过程如下:

Untitled

可以看到当使用eventfd后,Guest仍然会停止,然后切换到KVM内核,然后KVM这里只需要通过eventfd通知用户态即可,然后迅速唤醒Guest继续运行。和eventfd类似,irqfd的引入也是为了避免中断注入带来的上下文切换的开销。通过eventfd和irqfd可以高效的进行通信。结合vhost改造后的架构如下:

Untitled

可以看vhost模式下,data path不再走QEMU,完全是在内核态来处理,QEMU只做一些控制层面的工作。我们来看下详细的过程:

Untitled

QEMU启动后通过ioctl跟vhost字符设备交互发现设备,并做一些feature协商,virtio-net驱动侧通过PCI发现设备,和QEMU做初始化,QEMU将信息转发给vhost。完成设备初始化后,Guest驱动发送网络包,触发page fault异常到Host内核,Host内核通过eventfd通知vhost模块,然后继续运行Guest,vhost开始处理请求,处理完成后通过irqfd给vcpu注入中断。最后我们通过vhost结合OVS将数据包转发给本机的其他VM。OVS的data plane部分运行在kernel,ovs-vswitchd作为用户侧的控制面,数据流走OVS后,可以做路由决定这个包是走网卡发送出去,还是理由到其他VM中的TAP虚拟网卡。