在上一篇文章(网络虚拟化——vhost-user_dillanzhou的博客-CSDN博客)中,介绍了通过DPDK框架,将vhost移入用户态的技术——vhost-user的原理。其中也提到了可以将virtio设备驱动也放到用户态,从而实现更高效率的基于virtio设备的网络收发应用。DPDK中提供了virtio网卡的用户态驱动,称为virtio-pmd。更进一步,DPDK还支持了virtio-user,能够在DPDK进程中自己创建virtio设备并与vhost完成初始化配置,能够在没有kvm/qemu参与的非虚拟化场景下使用virtio设备。
从前面几篇文章的介绍中我们可以发现,virtio设备和驱动从虚拟机角度来看和普通的网卡设备没有什么区别,只是数据面和控制面接口符合virtio规范而已。因此virtio-pmd驱动和DPDK提供的其他pmd驱动没有什么本质区别。因此本文将从DPDK的用户态驱动原理出发,探讨一下在用户态实现virtio设备驱动所需要的相关技术。此外,和DPDK提供的其他用户态驱动相比,virtio-pmd还是有一个特殊点,那就是支持vdev虚拟设备模式,即virtio-user。在这种模式下,DPDK驱动的virtio设备是一个纯软件的DPDK内部虚拟设备,和通过qemu/kvm模拟的virtio-pci设备不同,这种设备由DPDK自己管理,DPDK明确知道这个设备是自己虚拟出来的,会在驱动中直接与vhost-net/vhost-user交互配置和初始化,并直接通过操作eventfd来实现和vhost端的事件通知。
本文将对上述两方面的内容做具体介绍。
本文内容部分参考了:https://www.redhat.com/zh/blog/journey-vhost-users-realm
一般情况下,Linux系统中的设备驱动都是运行在内核态的。原因包括:
DPDK最主要的特点,就是实现了用户态网卡驱动,使得网络数据的处理逻辑可以和网卡的收发逻辑紧密结合在一起,减少了内核/用户态切换、内存拷贝和不必要的复杂封装逻辑。用户态驱动需要内核模块的支持,通过内核模块提供的接口使得用户态程序能够映射设备地址空间,处理设备中断。
用户态驱动技术有两种:UIO和VFIO。早期版本的DPDK只支持UIO框架,从DPDK1.7开始也支持VFIO。目前VFIO已经是DPDK的主要用户态驱动框架,从DPDK20.02版本开始基于UIO框架的igb_uio模块不再默认编译,文档也不建议再使用UIO。
UIO是“Userspace I/O”的缩写,是一种相对简单的用户态驱动框架。linux内核中包含了uio.ko内核模块,提供了框架功能。UIO的原理是为每个注册使用UIO的设备生成一个/dev/uioX的字符设备,通过这个字符设备,用户态程序可以实现设备内存空间映射(mmap)、设备中断开关(write)、设备中断获取(read)等操作。
UIO模块本身只提供一个框架,使用UIO框架的设备需要再实现一个内核驱动模块,在DPDK中就是igb_uio。igb_uio本身是一个PCI设备驱动,提供标准的probe接口来向内核接管目标设备。igb_uoi会调用UIO的注册接口,从而真正提供UIO的用户态文件接口。在内核看来,网卡设备的驱动仍然是内核态的igb_uio,但实际上igb_uio通过UIO框架将设备内存地址映射到了用户态,用户态程序可以直接向网卡收发数据。在DPDK默认的polling mode中,用户态驱动初始化后就几乎不需要igb_uio参与后续的工作了。但在中断模式下,用户态获取设备中断仍然需要通过UIO提供的/dev/uioX文件。igb_uio需要在内核态处理设备中断,在中断处理函数中将事件反映到uioX文件的read/poll系统调用上。
上图是DPDK应用使用UIO框架实现用户态驱动的示意图。
需要注意的是,由于UIO框架中没有使用IOMMU,因此注册给NIC的DMA内存地址必须是物理地址。这就要求DPDK在用户态翻译出所使用内存的物理地址并注册给NIC。
从上面的介绍可以发现,UIO的接口和功能比较简单,主要有几个方面的限制:
VFIO是“Virtual Function I/O”的缩写,但事实上VFIO并不专用于Virtual Function。DPDK的物理网卡pmd也都使用VFIO支持。因此内核VFIO模块的maintainer Alex Williamson建议将其称作“Versatile Framework for userspace I/O”——全能用户态IO框架。从这个解释就能看出,VFIO是对UIO的增强,特别是在前面提到的UIO的几个局限性上,VFIO给出了更完善的解决方案。
VFIO主要通过IOMMU(Input/Output Memory Management Unit)来实现设备的DMA访问和中断重定向。IOMMU是CPU中集成的一个芯片部件,其功能和MMU类似。CPU执行内存访问指令时,MMU会通过查询页表,将指令中提供的虚拟地址转换为物理地址,然后才能访问内存数据。与MMU类似,IOMMU则会为IO设备维护页表,当IO设备需要DMA访问内存时,IOMMU可以将设备访问的虚拟地址转换为物理地址,从而实现物理内存访问。通过IOMMU,VFIO可以将用户态进程的虚拟内存地址注册给网卡设备,并维护相应的页表。设备只能访问页表中维护了的虚拟地址空间,不需要也不能够直接访问物理内存空间。这就避免了UIO框架让用户态程序注册物理DMA地址的高危操作,也让VFIO的操作不再需要在特权模式下执行。
VFIO的用法和UIO类似,也有对应的内核驱动模块。需要将目标IO设备绑定到VFIO驱动上,再通过操作/dev/vfio/目录下的设备文件实现设备驱动功能。
与UIO类似,VFIO也分为接口层的框架模块和具体的设备驱动模块,分别为vfio和vfio-pci(PCI设备驱动)。通过将网卡设备绑定到vfio_pci驱动,vfio-pci就可以通过vfio提供的/dev/vfio文件接口,向用户态提供设备访问和IOMMU管理功能。
VFIO的文件接口有两个,分别是/dev/vfio/vfio和/dev/vfio/$IOMMU_GROUP_ID。
$IOMMU_GROUP_ID是设备所属的IOMMU group的编号,一个IOMMU group中的设备必须全部绑定到VFIO驱动中(或者不绑定驱动)才能使用IOMMU功能。IOMMU group是IOMMU管理设备地址空间映射的最小单元。关于IOMMU group,可以参考《IOMMU是如何划分PCI device group的? - 知乎》这篇文章的介绍。一般情况下,一个PCI设备IOMMU group中只有一个device。
/dev/vfio/vfio用于创建IOMMU container,可以在一个container中管理多个IOMMU group。
VFIO向用户态提供的功能主要有两类:访问设备和配置IOMMU。
VFIO访问设备的接口和UIO类似,也是对设备fd执行mmap/read/write操作。不同的是设备fd的获取方式,需要如下步骤:
配置IOMMU的方式如下:
通过上述方式,VFIO提供了用户态设备驱动接口,并解决了UIO存在的问题:
virtio-pmd是DPDK中实现的virtio设备的PMD(poll mode driver)。virtio-pmd通过VFIO接口实现对虚拟/物理virtio网卡的用户态配置和数据处理。与其他网卡设备的PMD相比,virtio-pmd没有什么明显的特别之处。
virtio-user是在virtio-pmd的基础上,完全在用户态实现的virtio虚拟设备。virtio-user与传统的虚拟化技术完全无关,不再需要qemu/kvm等hypervisor介入。virtio-user相当于在用户态进程内同时实现了virtio-pmd和qemu的virtio后端控制面配置能力。
在virtio-user模式下,virtio设备挂载在DPDK内部虚拟的vdev总线上。virtio-user直接与vhost-net/vhost-user交互配置和初始化,通过ioctl/unix socket方式将virtqueue配置和内存布局、以及eventfd发送给vhost-net/vhost-user。之后,virtio-pmd会直接通过操作eventfd的方式来实现和vhost端的事件通知。
本文介绍了用户态驱动的原理和virtio驱动的用户态实现——virtio-pmd,以及在此基础上实现的纯用户态虚拟virtio设备virtio-user。最后来看一下文章开头的问题:
1. 用户态驱动的原理是什么?如何在用户态直接操作设备?
用户态驱动仍然依赖内核驱动框架。支持用户态驱动的UIO/VFIO内核框架主要向用户态提供了两类功能接口:设备地址空间映射和设备中断处理。通过这些接口,用户态驱动可以直接读写设备内存,获取设备中断事件,从而实现对设备的操作和设备中断事件的处理。
2. 用户态驱动框架uio和vfio有什么区别?
和UIO相比,VFIO增强了两方面的能力:通过IOMMU支持了设备通过虚拟地址访问主存,提升了用户态驱动的安全性;通过eventfd通知中断事件,支持了单个设备上的多个中断。
3. virtio-pmd和virtio-user有哪些使用场景?在非虚拟化场景下如何运行?
virtio-pmd最基本的使用场景还是在虚拟机中,可以用于加速虚拟机中的网络应用。
另一种场景是物理网卡直接将PF或VF以virtio设备的形式呈现,使用virtio-pmd可以直接向物理网卡收发报文。例如阿里云的神龙网卡就提供这种网络接口。
virtio-user的一种场景是在容器中使用,DPDK支持在用户态创建virtio虚拟设备,由于这种场景下没有qemu存在,DPDK会主动通过vhost-net/vhost-user的配置接口配置virtio信息。通过这种方式,容器中的应用在没有VF passthrough的情况下,也可以通过一种相对通用的方式实现高性能的网络数据处理。
virtio-user的另一种用途是通过vhost-net模块与内核交互报文。在virtio-user之前,DPDK支持的内核交互方式有KNI、tap、raw socket等,但这些方式的效率都较低,需要频繁调用syscall来注入报文。使用virtio-user后,用户态进程与内核通过共享内存的方式就能实现报文交互,性能有明显改善。