此文旨在向读者介绍Virtual Open Systems公司为QEMU开发的vhost-user特性在Snabbswitch软交换机中的使用。vhost-user的架构和Vapp软件也会涉及到。读者可跟随本文构建具有vhost-user特性的QEMU软件,并且同Vapp的参考实现进行测试。

Vhost-User的产生

基于QEMU/KVM的虚拟机访问外部网络的一种方法是通过virtio_net所实现的半虚拟化网络驱动。根据virtio架构标准,virtio_pci驱动实现了相符合的virtio ring,virtio_net等据此来实现具体的半虚拟化驱动。相应的QEMU模拟一个虚拟的PCI设备,为x86虚拟机中的virtio ring提供传输机制。除去virtio架构的机器相关特性外,virtio驱动通常使用通用API来建立virtqueues虚拟队列结构,当virtqueues中被填入了装有新数据的缓存时,其将得到kicked通知事件。

Virtio_net是基于virtio机制实现的网络驱动。运行virtio_net驱动的客户机与其所依托的QEMU进程之间共享一定数量的virtqueues。据此QEMU进程就可接收到客户机发出的网络流量,并转发到宿主机的网络中。然而这意味着,所有的客户机流量必须先被QEMU进程处理,之后才能进入宿主机的网络栈。

针对此问题产生了vhost解决方案,它允许用户空间进程与内核驱动之间共享一定数量的virtqueues。此时的传输就变为内核直接访问用户空间应用的内存,外加ioeventfs和irqfs来完成通知机制。但是客户机仍然需要QEMU模拟的PCI设备,因为所有的控制信息还是由QEMU处理,当virtqueue建立完成后,就可使用vhost的API将virtqueue的控制交由内核驱动。在此模型中,驱动vhost_net在内核层面直接将客户机的网络流量传给TUN设备,很大程度上提供了性能。但是这样对于完全运行在用户空间的虚拟软件交换机Snabbswitch来说,就不是完美的解决方案了。Snabbswitch证明直接在用户空间驱动物理以太网卡设备可获得很高的网络性能。Snabbswitch中的虚拟机虚拟接口要想达到与之匹配的性能,需要能够直接访问QEMU/KVM虚拟机的virtio相关接口,减少中间软件的处理,包括内核。据此提出了vhost-user解决方案,其为vhost方案在用户层面的实现,对于像Snabbswitch这样运行在用户空间的经常,在与虚拟机交互数据时,不需要内核在中间参与,直接在Snabbswitch中实现vhost接口。

Vhost-user概观

vhost-user的目标是实现一个Virtio传输机制,使其能足够直接的使用vhost方案中的共享内存、ioeventfds和irqfds。两个用户空间进程使用本地UNIX套接口通信,为共享Vrings建立共享内存资源。并且,配置所需的eventfds,使其在Vring收到另一端发出的kick事件时发送信号。

QEMU通过一系列的补丁实现了vhost-user,可通过将virtio_net的Vrings直接传给其它用户空间进程,在QEMU之外实现一个virtio_net的后端程序。这样,Snabbswitch进程与QEMU客户机virtio_net驱动间的直接通信就可实现了。QEMU已经实现了一个vhost接口零拷贝客户机流量到内核数据平面。此接口的配置依赖于一系列的控制平面ioctls调用。此实现中,QEMU的网络后端使用的为一个TAP设备。通常的创建命令如下:

$ qemu -netdev type=tap,script=/etc/kvm/kvm-ifup,id=net0,vhost=on \
                                            -device virtio-net-pci,netdev=net0

示意图图如下:

QEMU的vhost-user补丁旨在提供用户空间的vhost接口实现和逻辑。增加的基本组件有:

    增加-mem-path选项分配客户机可与其它进程共享的内存
    使用UNIX套接口在QEMU和用户空间vhost实现进程间通信
    用户空间进程将接收预分配的客户机共享内存的文件描述符,它可直接存取客户机这部分内存空间中相关的vrings。

vhost-user架构如下图:

目前实现中,vhost客户端在QEMU中,后端位于Snabbswitch中。

编译与使用

QEMU编译

拥有最新vhost-user补丁的QEMU版本可在Virtual Open Systems公司的代码库中取到:https://github.com/virtualopensystems/qemu.git, 分支:vhost-user-v5.

$ git clone -b vhost-user-v5 https://github.com/virtualopensystems/qemu.git

编译:

$ mkdir qemu/obj
$ cd qemu/obj/
$ ../configure --target-list=x86_64-softmmu
$ make -j

编译生成的QEMU位于qemu/obj/x86_64-softmmu/qemu-system-x86_64。

使用vhost-user功能的QEMU

QEMU要搭配vhost-user后端运行,需要首先提供一个命名的UNIX套接口,并且此套接口要已经由后端打开。

$ qemu -m 1024 -mem-path /hugetlbfs,prealloc=on,share=on \
-netdev type=vhost-user,id=net0,file=/path/to/socket \
-device virtio-net-pci,netdev=net0

Vapp参考实现

Vapp为一套vhost-user的客户端与后端的参考实现代码。可使用其测试vhost-user协议,或者据其实现一套新的客户端或者服务端程序。Vapp的源代码位于Virtual Open Systems公司的github仓库中:

$ git clone https://github.com/virtualopensystems/vapp.git

编译:

$ cd vapp
$ make

vhost-user参考后端如下启动:

$ ./vhost -s ./vhost.sock

参考客户端如下启动:

$ ./vhost -q ./vhost.sock

Snabbswitch中的vhost-user支持

Snabbswitch的vhost-user功能支持有SnabbCo维护。代码可由官方的Snabbswitch库中的vhostuser分支获取:

$ git clone -b vhostuser --recursive https://github.com/SnabbCo/snabbswitch.git
$ cd snabbswitch
$ make

如下测试:

$ sudo src/snabbswitch -t apps.vhost.vhost_user

设计与架构

通用的用户空间之间virtio架构

当前virtio传输仅考虑了客户机操作系统驱动同运行于Hypervisor中的virtio后端的通信。对于QEMU/KVM,仅有两种情况:

  • 客户机的virtio驱动到QEMU中运行的virtio后端。这是最普遍的情况。在x86平台virtio_pci传输被用来交换Vrings(virtio的基本数据集)和新数据的通知消息(kicks)。实际的数据有QEMU直接存取,因为其可访问到客户机的整个内存空间。
  •  客户机的virtio驱动到Linux内核中的vhost后端。此优化可使宿主机的内核直接处理客户机的流量不需要通过用户空间的QEMU。此时,virtio_pci仍然被用来做配置使用,QEMU将创建eventfds来处理客户机发出的通知(kicks),配置内核vhost驱动使其可直接处理通知事件。

很明显我们需要第三组选择,即客户机的virtio驱动到用户空间进程中的virtio后端。尽管KVM的基础建构可将virtio的事件(kicks)匹配到eventfds,我们仍然需要一个标准接口将virtio事件的控制处理交由宿主机的用户空间进程。此用户进程需要能够存取客户机与宿主机交换缓存或者数据结构的任何内存空间。

此接口基于共享内存和eventfds实现,与已得到验证的内核vhost接口类似。

Vapp参考实现

尽管与已存在内核vhost架构类似,但是毕竟是一种新的virtio机制,需要一个测试案例以验证用户空间进程的virtio后端代码以及概念。

我们称此测试实现为Vapp,本质上有两个主要组件,其一Vapp客户端代表虚拟机的角色;其二Vapp服务端代码virtio后端的角色。在测试案例中,客户端中运行的程序创建一个数据包,放置到Vapp客户端的Vring中。Vapp客户端与服务端共享一段内存,其中保存了Vrings和数据缓存,两端通过eventfds通信。建立过程使用了简单的UNIX套接口,使用了类似vhost内核实现的机制。

Vapp的客户端服务端架构如下:

服务端Vring实现

Vring的架构足够通用,以便其可在QEMU中实现(客户端),也可在Snabbswitch中实现(服务端)。尽管如此,在下一节将要讨论的现实世界中,Vring是运行在客户机操作系统中而不是QEMU中。QEMU仅仅建立起客户机virtio(利用virtio_pci)与目标用户空间进程的连接。

这就要求服务端实现的Vring必须一字不差的准确遵照virtio规范。另外,virtio_pci驱动可使用KVM创建的eventfds(ioeventfd和irqfd)交互,也可通过访问客户机的地址空间的方式去获取virtio驱动产生(或消耗)的包含有数据的共享缓存。对于Vapp服务端要成功于客户机通信并且与其中的virtio_pci驱动交互数据,需要至少满足以下几点:

  • 能够访问客户机的地址空间,其中存放有数据缓存;
  • 处理响应(或产生)virtio事件的eventfds;
  • 能够共享访问virtio数据结构,包括最重要的Vrings。

此模型也被内核的vhost成功验证,即宿主机内核空间中运行的Vring实现可以直接与客户机的virtio_pci所实现的Vring成功交互。与内核vhost实现的主要区别:

  • 我们的virtio后端运行在用户空间。我们使用UNIX套接口和共享内存接口实现,而不是ioctl接口;
  • 我们不需要TAP设备。运行virtio后端的目标应用如何处理接收到的流量对于我们来说是不可知的。

建立机制

以上描述的virtio机制要工作,我们需要首先创建接口来初始化共享内存区域、交互事件文件描述符。UNIX套接口实现的API可完成此功能。此套接口接口可用来初始化用户空间virtio传输(vhost-user),包括:

  • 初始化时Vrings确定下来,并放置到两个进程间的共享内存中;
  • 只有virtio事件(Vring kicks),我们使用eventfd映射到Vring事件。这使我们的实现与下一章描述的QEMU/KVM实现相兼容。KVM运行我们将客户机中virtio_pci发出的事件与eventfd(ioeventfd和irqfd)相匹配。

在两个进程间共享文件描述符和在进程与内核间共享时不同的。前者需要使用UNIX套接口的sendmsg系统调用设置SCM_RIGHTS值。

QEMU对Snabbswitch用户空间virtio的支持

测试案例证明了Vapp的可行性并且验证了用户空间virtio(vhost-user)的实现代码,据此我们希望在真实的场景中为QEMU/KVM客户机匹配运行一个位于单独用户进程中的virtio后端,即Snabbswitch交换机。我们将添加vhost-user支持到QEMU和Snabbswitch中,前者取代Vapp客户端,后者取代Vapp服务端。

QEMU的vhost-user支持

在之前的章节我们已经讨论了客户机操作系统基于virtio_pci的virtio实现。当前情况下,需要由vhost-user客户端去发起与vhost-user服务端的建立过程,此过程与virtio_pci的关系不大。客户机中的virtio_pci产生的事件由vhost-user的服务端的eventfs所关联。同时,服务端可通过共享内存接口访问客户机操作系统的内存。QEMU中对vhostvhost-user的支持如下图:

考虑到客户机并不知道它运行在何种virtio_net后端实现上;所有我们不能对客户机中已实现的virtio_pci和virtio_net驱动增加新的限制条件。这就意味着需要意识到,客户机有可能在它的内存空间中任意位置分配缓存,进而请求virtio_net驱动将其指到宿主机可访问的Vring中。

Snabbswitch中vhost-user服务端的实现

Snabbswitch要使用virtio机制需要进行扩展以实现一个完整的virtio_net后端。不像QEMU,Snabbswitch将要实现一个自身完整的virtio协议栈和virtio_net模块。基于Vapp的服务端接口和virtqueues可实现一个通用的virtio API。

无论从前端还是后端来看,virtio驱动的高层实现中,virtio Vrings都体现为一套基于virtqueues的更抽象的API接口;尽管本质上有着相同的功能,但是被高层的帮助函数进行了封装。virtio驱动,包括virtio_net,就是基于封装层实现的。目前介绍的组件能够允许移植vhost-user服务端的接口部分,使得Snabbswitch中virtio_net的实现从virtio具体使用的传输机制中抽象出来。

访问客户机地址空间中的缓存

对于运行在QEMU中的virtio_net后端而言,由于QEMU可以访问客户机全部的地址空间,所以客户机操作系统可将任意位置的缓存放入Vring中。所以,客户机操作系统将有可能放置网络数据在任意其可访问的缓存位置。

保存于目前运行于客户机操作系统的virtio_net驱动的兼容,我们不能够对客户机放置数据的缓存位置施加限制。所以当初始化Snabbswitch的vhost-user服务端,使用其中的virtio_net后端去对接客户机中的virtio_net前端驱动时,我们需要Snabbswitch能够访问客户机的全部物理地址空间。因此Snabbswitch有可能与客户机的操作相干扰,Snabbswitch需要为一个可信任进程。

结论

本文描述了一种在两个用户空间进程间实现直接virtio传输的设计和架构。随后将这种架构扩展到真实世界中一个运行virtio_net前端驱动的QEMU/KVM客户机和一个运行virtio_net后端的Snabbswitch交换机独立用户进程间。此实现基于Linux内核的vhost,对协议做了扩展以支持用户空间vhost后端(vhost-user)。

此项virtio技术可使得Snabbswitch接收运行与QEMU/KVM的客户机操作系统的网络流量,同时获得以下优势:

  • 避免退出到用户空间(QEMU)的消耗;
  • 网络数据包的处理不需要内核参与。一旦有客户机exit事件,Snabbswitch通过eventfd能够尽快接收到通知。

Snabbswitch完全在用户空间处理数据流量,可获得使用vhost-user架构直接与客户机的virtio_net交互的益处,避免了经由QEMU的上下文切换和内核的参与。

 

此文翻译自此:Snabbswitch-qemu,略有增减。

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐