网络虚拟化——vhost

在上一篇文章(网络虚拟化——virtio)中,我们讨论了virtio技术的由来、原理和实现。virtio为虚拟IO设备提供了一套标准的接口和实现。同时由于其半虚拟化的特质,virtio驱动在设计和实现时尽可能减少了主要操作路径上会触发host后端操作(vmexit)的指令以提升IO效率。

但在执行IO操作时,仍会不可避免的需要触发后端操作。例如virtio-net驱动发包时,在向tx virtqueue写入buffer后必然要kick后端来处理buffer,这个kick就是一个IO写操作。当后端在用户态qemu进程中实现时,这就需要经过guest driver->kvm->qemu->kvm->guest的过程,和普通的虚拟设备驱动是没有区别的,效率仍然低下。此外,qemu在向外转发报文时,仍然需要通过系统调用将报文交给内核发送,这又是一次用户态和内核态的切换。

为了缩短这个过程,一种直接的思路就是将virtio后端的实现也放入内核态,作为一个内核模块/内核线程运行。这个思路和KVM类似,将qemu原本在用户态完成的模拟能力转移到内核模块中实现,只是这里要转移的是qemu的设备模拟功能。这项技术称作vhost。

有了vhost后,后端操作的流程就变成了guest driver->kvm->vhost->kvm->guest。看似和之前差不多,但是kvm和vhost之间的交互只是一个内核函数调用,性能比之前的kvm和qemu间的用户/内核切换要好的多。同时,使用vhost也提升了后端完成实际IO操作的性能。大部分情况下,后端完成IO操作(例如块设备读写或网络收发)仍然要通过内核接口,例如qemu仍然需要使用文件或socket接口实现,这又需要引入系统调用和状态切换。而使用vhost之后,这些内核能力可以由vhost模块直接调用,又一次减少了状态切换开销。

本文将对vhost技术进行分析和介绍。

本文参考了redhat的两篇介绍virtio/vhost的文章,部分图片也来自于这两篇文章:

Introduction to virtio-networking and vhost-net

Deep dive into Virtio-networking and vhost-net

问题

  1. vhost支持的是控制面还是数据面的能力?
  2. vhost如何与virtio驱动交互获取控制面信息或数据面数据?
  3. vhost在获取了virtio设备的操作请求后如何实现后端功能?
  4. 采用vhost后,virtio设备实现是否还依赖于qemu的功能?可否在没有qemu,甚至非虚拟化场景下使用virtio/vhost架构?
  5. vhost是否和virtio一样是通用架构,使用vhost支持virtio-net、virtio-blk的后端功能是如何实现的?
  6. vhost-net是如何实现的,使用vhost-net后virtio-net设备的网络数据收发路径是怎样的?

vhost

如上文所讨论的,vhost是一种在内核中提供virtio设备后端功能的技术,其出发点是用于改善虚拟环境下virtio设备的IO性能,避免qemu在用户态实现后端功能所需的大量状态切换和系统调用。

vhost定义了一种基于消息的协议,通过这个协议,virtio后端可以将virtio设备的数据面配置信息转发给另一个组件,从而让这个组件去完成数据面的功能。也就是说,采用vhost技术后,virtio后端的控制面和数据面可以分离成两个组件,控制面组件在完成基本配置后使用vhost协议将数据面配置下发给数据面组件,后续的数据交互由数据面组件实现。

vhost协议只定义了数据面配置的下发规范,数据面能力的具体实现方式由接收vhost消息的组件自己决定。

通过vhost协议,控制面组件主要下发两类数据面配置信息:

  1. virtio后端的内存布局信息,通过这些信息,vhost组件才能够访问virtqueue和virtio driver提供的buffer内存地址
  2. 一对文件描述符(fd),用于vhost组件与virtio driver间收发通知事件。在kvm场景下这两个fd在vhost和kvm间共享,双方通过这两个fd来互相通知事件,而不需要qemu的参与。

vhost-net

vhost最常用的实现就是linux内核中提供的vhost-net,下面就以此为例分析一下vhost驱动的具体实现。

网络虚拟化——vhost_第1张图片

上图展示了采用vhost后,virtio-net设备在虚拟场景下的实现架构。可以看到guest中virtio-net设备的控制面交互仍然需要通过qemu,qemu在完成了控制面交互后获得了数据面的配置信息,再通过vhost-net模块的devfs(/dev/vhost-net)接口通过ioctl向vhost-net传递数据面配置信息。之后的数据面交互就不再需要qemu参与,virtio-net直接与vhost-net交互。

vhost-net接收到初始化请求后,会创建一个名为vhost-$pid的内核线程,这个pid就是调用vhost-net配置接口的进程pid。这个内核线程会负责对应的virtio设备的virtqueue数据收发。vhost模式下仍然会创建一个tap设备,vhost kthread通过这个tap设备收发网络报文。

qemu会创建两个eventfd同时共享给kvm和vhost-net。一个称作ioeventfd,virtio-net驱动kick时,kvm会向ioeventfd写入,从而唤醒监听这个fd的vhost kthread;一个称作irqfd,当vhost-net驱动需要生成rx/tx中断通知virtio-net处理时,vhost写入这个fd,kvm监听到这个fd上的输入后向guest注入中断。这个方式的事件通知与原先的qemu方式相比,差别只在kvm之后的逻辑,guest的virtio-net驱动和kvm间的交互接口和逻辑是不变的,因此不需要guest和virtio-net驱动做任何修改或感知。

网络虚拟化——vhost_第2张图片

上图更详细的展示了virtio-net/vhost-net的实现逻辑。

小结

最后来看看文章开始时的问题:

1. vhost支持的是控制面还是数据面的能力?

vhost协议只定义了数据面配置的下发接口,不涉及控制面的能力。vhost-net的实际实现中也是根据接口下发的数据面配置进行数据面交互,不包括控制面的协商和初始化。

2. vhost如何与virtio驱动交互获取控制面信息或数据面数据?

vhost协议并没有规定vhost如何获取配置信息。内核实现的vhost-net是通过对/dev/vhost-net执行ioctl来完成配置的。

3. vhost在获取了virtio设备的操作请求后如何实现后端功能?

vhost根据配置信息获得virtqueue/vring的内存地址,之后就可以通过vring收发数据,vhost-net通过一个tap设备在vring和host间转发报文。vhost配置时还会获得两个fd,用于接收和发送事件通知。

4. 采用vhost后,virtio设备实现是否还依赖于qemu的功能?可否在没有qemu,甚至非虚拟化场景下使用virtio/vhost架构?

在虚拟化场景下,目前的virtio设备仍然依赖qemu实现控制面的协商和vhost-net的配置。但从理论上说qemu并不是必须的,在非虚拟化场景下可以由其他组件来完成virtio虚拟设备的配置。例如DPDK中就支持了virtio-user模式,可以在host用户态进程中直接完成virtio设备配置,并与vhost-net或vhost-user交互,并不需要qemu和kvm来虚拟出一个virtio pci设备。这个模式主要用于在容器网络中使用DPDK,以及让DPDK与内核通信的场景。

5. vhost是否和virtio一样是通用架构,使用vhost支持virtio-net、virtio-blk的后端功能是如何实现的?

vhost协议和具体设备类型无关,linux内核中有vhost模块实现vhost的通用标准接口。除了常用的vhost-net外,也有vhost-blk、vhost-scsi等磁盘块设备的vhost实现。但这些实现并没能进入内核。vhost-blk至少有3次被不同作者提交到内核社区,都没能被合入,主要原因是和qemu中的后端相比性能优势不大,因此没有明显的合入价值。

6. vhost-net是如何实现的,使用vhost-net后virtio-net设备的网络数据收发路径是怎样的?

使用vhost的数据路径大致是virtio-net<-->vhost-net<-->tap<-->kernel stack<-->nic。

本文介绍了vhost的原理和linux内核中提供的vhost-net的实现。文中也提到vhost不一定要在内核中实现,也可以在用户态实现,即vhost-user。下一篇文章中将学习和讨论vhost-user的原理和应用场景。

你可能感兴趣的:(网络虚拟化,linux,网络虚拟化,virtio)