virtio+ovs转发原理和性能分析
virtio 是一种 I/O 半虚拟化解决方案,ovs是一个虚拟交换机,利用软件的方式实现交换功能。本文将对virtio+ovs的转发原理进行介绍和并对其性能展开分析。
1、 virtio和ovs介绍
传统数据中心的硬件服务器上运行着linux,linux使用硬件网卡收发包,硬件网卡连接的硬件交换机进行包转发实现服务器之间的互通。硬件网卡有broadcom、mellanox和intel等各种品牌,硬件交换机有H3C、cisco等品牌。在云计算环境下,对计算资源进行了切分,服务器上运行的是一个个虚拟机,虚拟机也要有网卡实现互连互通,但虚拟机的网卡不是物理的,它通过虚拟的网卡连接到虚拟的交换机上,虚拟的交换机对同一个服务器上的虚拟机之间流量进行转发,如果虚拟交换机再连接到服务器的硬件网卡,那么虚拟机就可以和服务器外面通信了。
硬件网卡收包时,CPU先分配内存,然后告诉硬件网卡内存的地址,报文从硬件交换机出来后进入硬件网卡的队列,硬件网卡通过DMA功能把包从物理网卡搬运到内存中。然后中断CPU说报文收上来了,CPU处理中断,软中断执行内核协议栈处理,最后通知应用程序收包。
虚拟网卡是CPU模拟出来的,它的队列也是模拟出来的,就是服务器上的一块内存。要模拟DMA功能就得进行一次特殊的内存拷贝,从服务器上拷贝到虚拟机里,虚拟机运行在服务器上,用的也是服务器上的物理内存,相当于服务器上物理内存之间的拷贝,只是地址转换比较复杂。
应用程序调用发包时,内核协议栈模块分配一块内存,把用户态要发的内容拷贝到内核。然后经过复杂的协议处理,最后地址告诉硬件网卡说我要发包,你发完了告诉我一声,硬件网卡的DMA就把要发的数据从内存中搬运到物理网卡的队列中,然后告诉CPU说发完了,你可以回收内存了。
虚拟网卡发包和物理网卡发包类似,包从虚拟机中搬运到物理服务器内存中,然后经过软件交换机,最后从物理网卡出去。虚拟网卡有e1000、virtio等,为什么云计算环境最终选择了virtio?首先virtio提供了一种虚拟机和物理服务器数据交换的通用机制,虚拟网卡、虚拟硬盘、虚拟显卡和虚拟串口等都可以用。其次virtio还分frontend和backend,frontend运行在虚拟机中,backend运行在物理服务器上,frontend和backend配合实现具体的网络功能,frontend和backend之间用virtio通用机制交换数据,设计真是十分巧妙。因此virtio得到了大多数hypervisor的支持,成了虚拟化事实上的标准。
硬件交换机是通过学习MAC进行二层转发的,使用STP协议防止环路出现,为了减小arp这样的广播风暴又增加了vlan隔离。软件交换机有linux bridge、vpp和ovs等。为什么云计算环境中大多使用ovs?因为云计算是最自然的SDN应用场景,不再需要复杂的控制面协议,如STP。另外云计算要求灵活,vpp pipeline和linux bridge设计时考虑更多的是二三层转发原理和转发性能,要加入新功能异常艰难,而ovs采用了openflow pipeline,多table和多group灵活跳转。pipeline设计考虑更多的是功能实现,性能方面由datapath支撑,并且datapath upstream到kernel中顺手就可使用,所以选择ovs。
2、vhost-net
这张图来自于redhat的博客,博客地址在参考文献中,非常完美的一张图,就没必要重复造轮子了。操作硬件网卡就是读写硬件网卡的寄存器,现在的外设都是PCIE外设,有配置空间和BAR空间。配置空间是PCIE标准定义,BAR空间硬件自己定义,读写寄存器就会触发硬件相应的操作,比如发送包,会构造一个ring,每个ring指向skb,然后把ring的头写到一个寄存器,再写一下触发发包的寄存器,硬件就会读取ring,把ring指向的skb都从内存搬运到网卡上去。
虚拟机也有模拟的主板芯片和PCIE总线,virtio-net是PCIE总线上的一个外设。在这张图中frontend就是guest kernel中的virtio-net driver,而backend是qemu模拟的virtio-net device。virtio-net driver读写寄存器,qemu就要把原来硬件干的活模拟一遍。因为虚拟网卡寄存器是不存在的,kvm就把一块内存做特殊标志当作寄存器,virtio-net driver一写这块内存,cpu就从guest中exit出来,停止执行guest,开始执行qemu代码模拟guest触发的动作。qemu把外设模拟分为两种:控制面模拟和数据面模拟。控制面模拟有feature协商,vring地址交换等;数据面模拟有数据搬运和消息传递,其中消息传递就是guest通知vhost-net数据准备好了,我要发送,vhost-net发送完后告诉guest我帮你发送完了。在这张图中qemu把数据面模拟的工作交给了内核的vhost-net模拟来完成。
上面说虚拟网卡模拟DMA是内存拷贝,既然是内存拷贝那干脆共享内存就不用拷贝了。vhost-net就是这么干的,不管是内存拷贝还是内存共享都要进行内存地址换算。guest中的virtio-net driver设置的都是guest physical address,而vhost-net只知道host virtual address。但别忘记guest的内存是qemu分配的,qemu知道换算关系,可以通过vhost protoco告诉vhost-net模块。剩下就是消息传递了,kvm给qemu提供了api,qemu创建出两个eventfd传递给kvm,并通知guest,写一个fd,kvm读这个fd,再把中断注入guest中,这个方向的通知叫做call。guest通知qemu,并写寄存器,kvm拦截后写另一个fd,qemu读这个fd,这个方向的通知叫做kick。qemu干脆一狠心,把这两个fd的读写都交给vhost-net来负责。
-
guest发包流程
guest在内核中分配skb,把地址写到vring中,kick kvm,kvm再通知vhost。vhost是内核线程,通过地址换算拿到skb并复制,通知guest发送完成。guest回收sbk,vhost继续给skb找netdev,加入到一个cpu的backlog,触发softirq。这个cpu的softirq发现netdev绑定在ovs桥上,查ovs流表找到出接口,调用物理网卡的驱动发送出去,物理网卡发送完成,把skb释放。
-
guest收包流程
guest中的virtio-net driver分配skb,设置到vring上,物理网卡驱动分配skb设置给网卡,网卡DMA,中断触发,在softirq发现物理网卡绑在桥上,查ovs流表找到虚拟机的tap口,把skb放入tap的队列中,叫醒vhost worker,vhost worker醒来一看skb到了tap队列中,把skb拷贝到guest分配的skb上,通知kvm包来了(不需要qemu),kvm再中断guest。
3、vhost-user
这张图也来自于redhat的博客,原图guest中运行的是virtio-net-pmd和vIOMMU,考虑到guest中的难度,我把图做了一点修改。用户在guest中搞hugepage和dpdk pmd难度很大,而且目前没有配套的成熟用户态协议栈。假设guest是最普通的guest,与vhost-net相同。这种模式ovs不再用内核的datapath,物理网卡绑定了DPDK,直接把包DMA到用户态ovs,ovs进程和qemu进程共享内存把包传递到qemu进程中,qemu进程地址换算一下包就到了guest。virtio-net控制面模拟还在qemu中,原来给vhost-net下配置的vhost protocol变成了vhost-user protocol把配置交给了ovs-dpdk进程,qemu和ovs-dpdk之间建立了unix domain socket。这个socket的神奇之处在于不仅能传递virtio配置信息,还能传递qemu和kvm之间通信的kick/call fd。qemu把virtio-net数据面模拟交给了ovs-dpdk进程,消息通道还是靠kvm。guest中一直poll不现实,但ovs-dpdk从guest中拿包时就可以一直poll。
virtio full offload
这张图也来自于redhat博客,根据我自己的理解修改一把,保持guest不变,用户在物理机上怎么部署业务,在虚拟机中也怎么部署业务,不能让用户感觉到不习惯或者不舒服。整体原理就是把virtio backend都由硬件实现了,然后用passthrough功能,和普通的物理网卡passthrough一模一样,只是这块卡实现了virtio标准。passthrough和dpdk都用了vfio-pci,原理一样,把物理网卡的pcie配置空间映射到qemu进程或者ovs-dpdk进程空间中,ovs-dpdk就直接读写,但qemu还得再地址转换一下给guest,guest中的virtio-net driver就可以直接读写了,这样virtio kick就好搞了,但call还是经过vfio-pci来通知kvm,kvm再中断注入guest中。数据搬运就是硬件直接DMA到qemu进程或者ovs-dpdk进程中,进程在虚拟地址空间中分配内存,交给硬件的地址都是进程的虚拟地址。然后vfio收到进程给的地址信息,把进程虚拟地址转换成物理地址给IOMMU。IOMMU在外设给进程虚拟地址搬运数据时把地址转换成这个物理地址。qemu相比ovs-dpdk多了一道手续,guest driver给硬件配置的地址是自己的物理地址,qemu和vfio得把guest的物理地址转换成host的物理地址。
passthrough的问题是虚拟机不能热迁移,热迁移要把网卡pcie空间的数据迁移走,而且还要知道硬件DMA修改了那些guest的内存,要把修改的内存也迁移过去,但硬件都没有提供这样的接口,提供了接口qemu还得有办法获取。既然有其它物理网卡能passthrough了,就无需实现virtio物理网卡来passthrough。
5、vdpa
这张图也来自于redhat博客,图太多可能博客作者搞混了图,原图是不对的。当时可能想把vpda往vfio/mdev方面靠,首先mdev并不要求必须遵循virtio标准,vdpa实现了数据面的virtio标准,并且把datapath offload到了硬件,控制面没有offload继续由qemu模拟,硬件设备虚拟出来的vdpa未必实现了virtio标准要求的pcie功能,显然用vfio不行。没有vfio就不能利用其控制iommu的代码,需要vdpa自己开发,内核中没有upstream,控制面利用vhost协议,vhost再调用vhost-vdpa,vhost-vdpa调用硬件厂商提供的接口把控制信息下发到硬件中。
这种方法硬件网卡直接把包DMA到guest中了,问题是kick和call还是很麻烦,kick到了kvm,kvm通知vhost-vdpa调用硬件驱动来通知硬件。硬件call时中断给了硬件驱动,硬件驱动通知vhsot-vdpa,vhost-vdpa再通知kvm,最后由kvm把中断注入guest。如果guest用dpdk poll mode driver,kick和call就不成问题了。
搞了这么复杂,第一是为了让硬件网卡实现起来容易,硬件网卡厂商基于SRIOV和Scable IOV实现资源分割,再模拟vring操作就可以包装出vdpa。第二是为了实现热迁移,寄希望于硬件厂商的driver能提供接口获取DMA写了哪些guest内存。第三guest和host用vdpa统一用virtio-net驱动,qemu只增加一个vhost-vdpa模块,硬件的不同让硬件厂商的驱动屏蔽,厂商同时提供硬件和驱动,其它模块都不动就能适配不同厂商。
6性能分析
guest保持不变,用的都是virtio-net驱动,中断和协议栈有开销,这是避免不了的。定性分析一下三种转发模型的利弊,忽略virtio full offload。首先上限受制于PCIE插槽的物理能力,当然真实场景下很难达到物理上限,只有dpdk l2fwd这些简单模型下才能达到。包处理就是一条流水线,达到无缝配合才能实现性能最高,并行几条流水线处理性能会更好。
简单总结一下影响性能的几个因素,分析每种因素在三种模块中的影响。
-
中断收包有上下文切换,有切换就会有性能开销。
vhost-net在物理网卡收发包时使用了中断,vhost-user使用dpdk pmd,因而没有中断开销。
-
zero-copy,当然是拷贝越少越好了,最好零拷贝。vdpa就是zero-copy,vhost-net 和vhost-user的rx无法zero-copy。假设rx要实现zero copy,guest分配skb,backend把skb发给硬件网卡驱动,驱动设置给硬件网卡,硬件并不知道来的包发送给哪个虚拟机。假如硬件网卡一个队列对应一个虚拟机,那guest不提供skb,硬件网卡就丢包了,rx skb只能由硬件网卡驱动分配,分配时可以从guest内存中分配吗?好像也不行,因为没法和guest同步,只能从内核或者ovs空间中分配skb,分配的skb也没法共享给guest,必须拷贝。vhost-net和vhsot-user tx都能实现zero-copy,guest 分配skb,backend直到等物理网卡驱动DMA走再通知guest发送完了,你可以回收skb了。过程有些复杂,第一guest可能需要等很久才能回收,第二等硬件网卡发送完了通知哪个guest回收难实现。所以vhost-net和vhost-user zero-copy都不现实。
-
hugepage,能减少TBL miss。
vhost-user模型中qemu和ovs-dpdk都用到了hugepage,坏处是内存不能超卖。
-
多队列
参与转发的CPU越多,从串行变成并行,当然能提高性能,但CPU资源是有限的,到底分几个队列呢。物理网卡有多队列,virtio-net也有多队列,按流分队列还是按包分队列。不管怎样,进哪个队列由物理网卡来实现,软件实现不划算,物理网卡计算一个hash值写到skb中,再根据这个值入virtio-net队列。发送最简单的配置就是一个cpu一个队列。
-
无锁
减少同步的开销,DPDK中有无锁队列,CPU之间传递skb用无锁队列开销就小。另一方面就是查流表和写流表实现无锁,用urcu等等。
-
接力干还是一口气干到底
pipeline设计问题,接力干就是一个cpu处理一段时间skb后把它放入队列交由下一个cpu处理,下一个cpu poll或者由上个cpu通知它开始处理skb。没有数据包时poll就会白白浪费cpu,而通知机制有开销和时延。一口气干到底就是就是一个cpu从收包、处理包到最后发送包,都由自己处理,配合多队列多个cpu同时进行。这两种模式的不同涉及到cpu cache/prefech等技术的利用,接力干对cache/prefetch友好。vhost-net是接力干的,vhost-user两个模式都能用,用户态修改代码和调试都容易一点。
整体来看vdpa是性能最高的,还省cpu,但是价钱贵;其次是vhost-user,有点费cpu;vhost性能最差。
7总结
vhost-net用的最多,配套完善,稳定成熟。据我所知vhost-user在电信级别云中很常用,电信级别云只追求性能,不考虑超卖,vcpu强绑定,不跨numa,网元数据面passthrough,数据面和控制面通过vhost-user通信。数据面如果用了vhost-user能热迁移能横向扩展,这样更符合NFV的理念。但普通openstack环境vhost-user就不能使用内核的QOS、netfilter、connection tracking、三层转发和arp等功能,这意味着openstack安全组,QOS和DVR都无法使用,只能用高性能三层专用网关。vpda个人没用过,没有这样的硬件,可能很多公司已经开始试用了,恐怕是一张很贵的卡,不仅要处理virtio还要处理vxlan,虚拟机能用,裸机也能用。
没有完美的开源技术,只有最佳实践经验,最佳适用场景,用起来,修改起来,回馈开源社区。
8参考文献
-
https://www.redhat.com/en/blog/deep-dive-virtio-networking-and-vhost-net
-
https://www.redhat.com/en/blog/journey-vhost-users-realm
-
https://www.redhat.com/en/blog/how-deep-does-vdpa-rabbit-hole-go
-
https://zhuanlan.zhihu.com/p/336616452
-
https://zhuanlan.zhihu.com/p/308114104