先从左边看起,ovs-vswitchd是ovs中最核心的组件,openflow的相关逻辑都在vswitchd里实现,一般来说ovs分为datapath, vswitchd以及ovsdb三个部分,datapath一般是和具体是数据面平台相关的,比如白盒交换机,或者linux内核等。ovsdb用于存储vswitch本身的配置信息,比如端口,拓扑,规则等。vswitchd本身是分层的结构,最上层daemon主要用于和ovsdb通信,做配置的下发和更新等,中间层ofproto,用于和openflow控制器通信,以及通过ofproto_class暴露了ofproto provider接口,目前只支持一个ofproto provider,即ofproto-dpif。
ofproto-dpif通过backer关联dpif。struct dpif_class是datapath interface实现的工厂接口类,用于和实际的datapath(比如openvswitch.ko, 或者userspace datapath)交互。目前已有的两个dpif的实现是dpif-netlink和dpif-netdev,前者是基于内核datapath的dpif实现(原生ovs),后者基于用户态datapath(ovs+dpdk):
1)lib/dpif-netlink.c: 特定Linux实现的dpif,该dpif与Open vSwith实现的内核模块通信。内核模块执行所有的交换工作,将内核态不匹配的数据包发送到用户态。dpif封装调用内核接口。
2)lib/dpif-netdev.c :是一种通用的 dpif 实现。该dpif就是Open vSwith在用户态的实现。数据包的交换不会进入内核。
两者的详细区别见:
https://blog.csdn.net/qq_15437629/article/details/77887910
再看右边,netdev 是对网络设备(如Ethernet)的抽象,该层基于netdev provider实现。端口类型包括linux平台的system,tap,internal,以及dpdk的dpdk,dpdkr,dpdkvhostuser,dpdkvhostuserclient等。由图可知,对于不同类型的虚拟网卡,都有对应的netdev_class。
dpdk加速的ovs与原始ovs的区别在于,从ovs连接的某个网络端口接收到的报文不再需要openvswitch.ko内核态的处理,报文通过pmd轮询直接到达ovs-vswitchd里。
在添加dpdk端口或者ovs启动(已添加dpdk端口)的时候,会触发创建pmd_thread_main线程。通过pmd_thread_setaffinity_cpu设置线程绑定的lcore,然后通过for循环各个端口,执行dp_netdev_process_rxq_port处理端口的数据包:
dp_netdev_process_rxq_port
dp_netdev_input=>dp_netdev_input__
具体的ovs-vswitchd的启动流程分析见:
https://blog.csdn.net/qq_15437629/article/details/79598556
pmd_thread_main及流表查找过程分析:
https://blog.csdn.net/qq_15437629/article/details/80873307
如果是物理网卡收发包很好理解,那么如果从连接vm的虚拟网卡中收发包,这部分流程是怎么样的呢?也就是下面要介绍的vhostuser技术了
一个完整的数据包从虚拟机到物理机的路径是:
虚拟机–QEMU虚拟网卡–虚拟化层–内核网桥–物理网卡
完全虚拟化网卡包含了复杂的io端口,寄存器,缓存配置,虚拟机每次收发包都会引起更多的io和mmio操作,使得虚拟机频繁的陷出,最终导致网络性能不佳。
为了解决上述全虚拟化的性能问题,IBM在2005年提出了virtio, 虚拟机中的半虚拟化前端驱动virtio-net和主机上的后端驱动virtio-net简单的使用virtqueue共享队列交换数据,大幅的减少了虚拟网卡模拟时复杂的io操作,从而可以较大程度的提升虚拟网络性能。
但是,virtio-net的性能并不如人意,主要原因在数据通道和消息通知路径这两块:
(1)数据通道是从Tap设备到Qemu的报文拷贝和Qeme到客户机的报文拷贝,两次拷贝导致报文接收和发送上的性能瓶颈。
(2)消息通知机制是当报文到达Tap设备时内核发送并送到Qemu的通知消息,然后Qemu利用IOCTL向KVM请求中断,KVM发送中断到客户机,带来不必要的开销。
vhost技术对virtio-net进行了优化,在内核中加入了vhost-net.ko模块,使得对网络数据可以在内核态得到处理。
随着技术的发展,将网络数据放入用户态处理将可以得到更灵活的形式。因此,在原有的vhost架构中,进行了改动,增加了 一种新的vhost-backend,叫做vhost-user。将原来kernel中的vhost-net 变成了用户空间的vhost-user,直接接管物理网卡的驱动,从而直接控制网络信息的输入输出。
在<<深入浅出dpdk>>中提到,vhost-user(用户态驱动)配合前端virtio(这就是传说中的半虚拟化)性能最佳。
详细的演进过程见:
https://blog.csdn.net/qq_15437629/article/details/77899905
DPDK便是一个在用户态可以直接操作物理网卡的库函数。它和vhost-user结合便可以实现性能强劲的用户态交换机了。dpdkvhostuser port将创建unix socket将虚机的virtio-net虚拟网卡的网卡缓冲区共享给物理机上的ovs port设备。
主要使用了下面的技术来提高性能:
虚拟队列(Virtqueue)是连接guest操作系统中virtio前端驱动和宿主机vhostuser后端驱动的实际数据链路。网络设备有 2 个 virtqueue,一个用于发送数据包,一个用于接收数据包。
每个虚拟队列主要由三部分组成:
描述符列表指向实际要传输的数据,两个环表指向描述符列表,标记前端和后端驱动对描述符列表中的描述符处理进度。
当virtqueue用于发送报文时,前端驱动将待发送报文加入avail ring等待后端的处理,后端处理完后,会将其放入used ring,并由前端将其释放desc中,最后通过try_fill_recv重新装入avail ring中; 当virtqueue用于接收报文时,前端将空白物理块加入avail ring中,提供给后端用来接收报文,后端接收完报文会放入used ring。
整个收包流程如下:
1,前端填充好desc(addr/len),并更新vring->avail(ring[0])
2,后端读取avail ring索引,拿到desc(if ring[0]=2,then desctable[2] 记录的就是一个逻辑buffer的首个物理块的信息),填充buffer数据;将buffer索引存在desc,将desc索引存放在used ring中
3,前端读取used ring索引,找到desc,获取buffer数据
virtio前端机制详解:
https://blog.csdn.net/qq_15437629/article/details/82084470
virtio后端技术–vhost-user详解:
https://blog.csdn.net/qq_15437629/article/details/81226901