赞
踩
在Linux下有两种启用用户态驱动的子系统:一个是UIO,另一个是VFIO,ixy这两种都支持。 UIO通过虚拟文件系统sysfs下的内存映射文件来暴露所有必要的接口以完成用户态的驱动。这些基于文件的系统调用接口给了我们充足的权限来获取设备资源而不需要写任何内核代码。ixy要卸载到所给PCI设备的任何内核驱动以避免冲突,比如,当ixy正在运行时,NIC的任何驱动都不会被加载。
VFIO提供了更多的特性:IOMMU和中断,且这些只能由VFIO提供。然而,这些特性会带来额外的复杂度开销:它要求PCIe设备和通用的vfio-pci驱动绑定,然后它会在指定的文件上通过ioclt系统调用提供API。
第一个需要理解的是用户态驱动如何与设备打交道的,也就是理解驱动是如何写在用户态的。驱动能够与PCIe设备通信的两种途径:驱动能初始化到设备BAR的访问通道或设备能初始化DMA通道映射到任何的主存储区。BARs是由设备暴露配置信息给驱动,并给驱动来控制寄存器。这些寄存器可以通过内存映射IO方式(MMIO)获得,也可以通过依赖于设备的X86 独立IO获得,后者在PCIE设备上不推荐使用。
MMIO将设备IO与内存空间映射起来,比如从这块内存区域读取或写入来完成向设备的接收或发送操作。UIO用虚拟文件系统暴露所有的BARs,这样有权限的程序就可以简单地把BARs映射到它自己的地址空间。VFIO提供了一个ioctl返回映射到这片区域的内存地址。设备通过这个接口暴露出它的配置寄存器,这样用户就可以用读写的方式来访问寄存器了。例如,ixgbe网卡设备通过BAR0地址空间暴露出所有的配置寄存器,状态寄存器和调试寄存器。这些映射的实现是在pci.c文件的pci_map_resource()函数和在libixy-vfio.c文件的vfio_map_region()函数里。不幸的是,VirtIO(在我们的实现与VirtualBox的兼容版本中)是基于PCI而不是在 PCIe ,其 BAR 是一个 IO 端口资源,必须使用古老的 IN 和 OUT x86 指令进行访问,这些指令需要IO 权限。
Linux能通过ioperm授予进程必要的权限,DPDK为他们的VirtIO驱动使用这样的通道。我们发现这种初始化工作太复杂,因为它需要解析 PCIe 配置空间或 procfs 和 sysfs 中的文本文件来初始化。Linux uio 通过sysfs的文件暴露出BAR空间,但与 MMIO 对应项不同,这些文件无法进行 mmap。这些文件能被打开,并通过通用的读写调用来访问,然后由内核转换为相应的 IO 端口命令。我们发现这样做更便于使用和理解,但由于所要求的系统调用而变得更慢。实现代码可见pci.c文件内的pci_open_resource()函数,device.h文件内的read/write_ioX()函数。
一个潜在的陷阱是读取和写入的确切大小很重要,例如,访问单个 32 位寄存器,用 2 个 16 位读取通常会失败,并且用一次大的读取来尝试读取多个小单位的寄存器通常是不支持的。确切的语义取决于设备,Intel的ixgbe NIC仅公开支持部分读取的 32 位寄存器(除了读取时清除寄存器),但不是部分写入。VirtIO 使用不同的寄存器大小,特别是,任何访问宽度应该是可以在我们正在使用的模式下工作的,事实上对齐且大小正确的访问可以可靠地工作的。
DMA是由PCIe设备初始化的,并允许它读写任意的物理地址。这被用来在驱动和网卡之间传输数据包和存放在DMA上的数据包描述符(指向数据包)。DMA需要在设备的PCI配置空间里显示地使能,我们在pci.c的enable_dma()函数里实现了这个使能,同样在libixy-vfio.c的vfio_enable_dma()函数为VFIO同样实现了这个使能。
DMA的内存分配在UIO和VFIO之间是不同的。
用于DMA的内存空间必须是常驻于物理内存区域。mlock(2)能用来禁用swapping。然后,这仅能保障页被内存保持支持,并不保障分配的内存物理地址保持始终一样。Linux的页迁移机制能在任何时间改变用户空间分配的任何页的物理地址,比如,来实现传输巨页和NUMA选项等。Linux 没有实现显式分配的大页(x86 上的 2 MiB 或1 GiB 页面)的页面迁移。因而ixy使用巨页来简单地分配连续的大的物理内存。在用户空间分配巨页被所有的用户态驱动采用,但他们经常是作为性能提升来使用,尽管可靠的DMA内存分配也至关重要。因为用户态驱动也需要能讲它的虚拟地址转换为物理地址,这可以通过procfs文件/proc/self/pagemap来实现,这种传输逻辑在memory.c文件的virt_to_phys()函数里实现。
之前的DMA内存分配机制是X86平台上的Linux的特殊机制,而不是可移植的。VFIO的特点是,它是可移植的,他内部调用dma_alloc_coherent()函数来分配内存,就像其他的内核态驱动那样做。这个系统调用简化了所有的复杂细节并在我们的驱动里以libixy-vfio.c文件的vfio_map_dma()函数来实现。它需要一个IOMMU,并配置必要的映射来为物理设备使用虚拟地址。
这两种实现都要求我们的CPU架构满足缓存一致的DMA访问。老式的CPU可能不支持这个功能,并要求在DMA数据能被设备读到之前把缓存上的数据同步到内存。现代的CPU没有这个问题。实际上,为实现高速数据IO的一个主流技术是DMA访问并不真的到内存,而是到新CPU架构的缓存上。
VFIO的特点是对中断全支持,具体设置函数为libixy-vfio.c文件内的vfio_setup_interrupt()函数,为VFIO使能一个指定的中断,并将它与一个eventfd的文件描述符绑定。ixgbe.c里的enbale_msiz_interrupt()函数为设备上的一个队列配置中断。中断被映射到一个文件描述符上,被系统调用如epoll检测,一般进入睡眠状态,直到中断发生,请见libixy-vfio.c文件内的vfio_epoll_wait()函数。
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。