随着网络的高速发展,对网络的性能要求也越来越高,DPDK框架是目前的一种加速网络IO的解决方案之一,也是最为流行的一套方案。DPDK通过bypass内核协议栈与内核驱动,将驱动的工作从内核态移至用户态,并利用polling mode的线程工作模式加速网络I/O使得网络IO性能出现大幅度的增长。
在使用DPDK的时候,我们常常会说提到用DPDK来接管网卡以达到bypass内核驱动以及内核协议栈的操作,本篇文章将主要分析DPDK是如何实现的bypass内核驱动来实现所谓的“接管网卡”的功能。
注意:
- 本篇文章会涉及一些pci设备的内容,但是不会重点讲解pci设备,pci设备中的某些规则就是这么设计的,并没有具体原因。
- 本篇部分原理的讲解会以Q&A的方式拖出,因为DPDK bypass内核的这部分涉及的知识维度比较多,没有办法按照线性的思路讲解。
- 本人能力以及水平有限,没办法保证没有疏漏原创内容
- 由于内容过多,本篇文章会着重基础的将PCI以及igb_uio相关的知识与分析,以便于不光是从DPDK本身,而是全面的了解DPDK如果做到的bypass内核驱动,另外关于DPDK的代码部分实现将会放在后续文章中放出,另外还有DPDK的中断模式以及vfio也会在后续的文章中依次发出(先开个坑,立个flag)
【1.谈一谈使用】
通常启动一个基于dpdk开发的应用,都需要几步准备来完成。
- 首先需要插入igb_uio/vfio-pci这两个驱动中的一个,接下来会以igb_uio为例讲解(因为简单...vfio还是有点复杂的...vfio的解析会放在以后的文章中放出)。
- 其次需要运行dpdk-devbinds.py这个dpdk官方给出的py脚本,以此来完成内核驱动到igb_uio/vfio的接管。接管之后,再次运行dpdk-devbinds可以很明显的看到驱动从ixgbe转为了igb_uio。请见图1.
- 运行dpdk应用,以-p参数指定要接管的网口,例如-c 0x03,那么接管的网口便是port 0和port 1.
图1.接管前后pci设备驱动发生的变化
那么经过上述三个操作,至少脑子里会产生这么几个问题:
Q:igb_uio/vfio-pci的作用是什么?为什么要用这两个驱动?这里的“驱动”和dpdk内部对网卡的“驱动”(dpdk/driver/)有什么区别呢?
Q:dpdk-devbinds是如何做到的将内核驱动解绑后绑定新的驱动呢?
Q:dpdk应用内部是如何操作pci设备的呢?是怎么让pci设备可以将数据包直接扔到用户态的呢?
这三个问题,实际上也是我当初在研究这一部分是遇到的三个问题。首先我们先来看第一个问题。
【问题一:igb_uio/vfio-pci是什么?】
我们会以igb_uio驱动为例进行讲解。这里其实很难一步讲清楚igb_uio的作用以及实现原理,所以接下来的讲解还是会以Q&A和“挖坑式”的方式进行逐步将原理展现给各位看官面前。先说说操作一个外设,最先想到的是什么呢?如果有过单片机等嵌入式外设开发的朋友肯定会冒出这样的一个想法
我得配置这个外设,为此我需要找到它的寄存器,但是找到它的寄存器前提是我得先拿到基地址才行, 接下来通过基地址+寄存器偏移就能找到寄存器所在的地址,然后就可以配置了
所以第一个任务便是我们要拿到”基地址“,首先有必要先科普一下pci设备的基地址。因此我必须得掏出一张图,即描述pci配置空间的一张图,如果图2所示。
图2.pci设备的配置空间
图2为pci配置空间的分布图,在图中,0x0010 ~ 0x0028这24个字节中,分布着6个PCI BAR(base address register),也就是最最重要的“基地址”,那这里有人可能会想问“这个图和我们有关系么?这个图中的空间在哪?我们该怎么解析?”,答案是“无关”,这些图中的信息事实上在系统启动时,就已经被解析完成了,以文件系统的方式供用户态程序取读取。但是这里其实有这样的一个问题:
PCI设备为啥有6个BAR,而不是3个、8个?这些BAR都有啥区别?实际访问寄存器的时候以哪一个BAR为基准呢?
其实解释这个问题,是一件简单而又不简单的事情。简单是因为pci设备规定就是有6个bar空间,而不简单是因为不知道为什么规定6个bar空间。那么这些BAR又有什么区别呢?这里要引用一下stackoverflow上面一位老哥说的话,见图3.(这里其实我之前也一直不太明白,因为国内的很多论坛帖子都是千篇一律...很难筛选出自己想要的信息...)
图3.不同BAR空间的区别之StackOverflow
其实关键就是蓝色的那句话,即”6个槽(BAR)允许设备以不同的目的提供不同的区域“,根据这个线索,我们来看一下intel 82599这款经典的10G网卡的datasheet中9.3.6中的解释。见图4.
图4.intel 82599 datasheet中关于不同pci bar的划分
可以看到这款经典网卡(其实intel的卡基本都是这么分的)主要将6个pci bar分成了三块区域:
- Memory BAR : 内存BAR,Memory BAR标志着这块BAR空间位于内存空间,通过mmap映射后可以直接访问。
- I/O BAR : IO BAR空间,I/O BAR标志着这块BAR空间位于IO空间,对其的访问不能像Memory BAR那样映射之后就可以随心所欲访问,IO BAR必须通过专门的操作来进行读写。
- MSI-X BAR : 这个BAR空间主要是用来配置MSI -X 中断向量。
那么这里可能有人会问,一共不是6个BAR空间么?这里只分了3个区域,那么每个区域分多少呢?这里请注意的是关于图3中6个PCI BAR,每个PCI BAR都是32位的,但是像82599这种工作在64位的网卡,其实就只有三个BAR。BAR0 BAR1为Memory BAR,BAR2 BAR3为I/O BAR,BAR4 BAR5为MSI-X BAR。这里我们可以对照一款低端网卡I350的datasheet,见图5.
图6.I350网卡datasheet中关于BAR分布的描述
从图6可以看到,对于I350这种低端的千兆网卡,可以将其配置位工作在32位还是64位模式下,但是对于82599这种万兆10g的卡,就没那么多选择余地了,只能工作在64位模式下,因此回到图3中,我们可以根据intel 82599的datasheet来得知intel的64bit网卡的bar分布是长什么样子的,如图7.
图7.intel 82599网卡的BAR分布
所以PCI配置空间的规范结合intel的I350和82599这两款网卡的datasheet进行分析,我们可以得出这样的一个结论:”PCI有6个BAR是规范,6个BAR的区别和作用取决于具体的PCI外设,需要查看datasheet才能给出答案“。
说完6个BAR的作用以及分布,接下来还有个问题,实际访问PCI BAR的时候以哪一个BAR为基准呢?这里主要有疑问的地方会出现在Memory BAR还是I/O BAR。因为需要搞清楚这两者的区别,才能真正判断在哪个BAR写配置。关于IO BAR和Memory BAR的区别首先需要科普一下,在x86体系架构下,内存的编址情况。接下来进入科普时间。
其实这里是比较晦涩难懂的,首先我们得知道,为什么会出现I/O空间和外设空间?在讨论区别之前我们可以看一张图,看看I/O空间和Memory空间长什么样子,这里可以看宝华叔经典的《Linux设备驱动开发详解》的第11章部分,这里我就简单的说一下,x86下的I/O空间和Memory空间到底长啥样子。见图8.
图8.I/O空间与内存空间,来自宝华叔的《Linux设备驱动开发详解》中第11章
另外需要注意的时,非x86体系架构下,例如ARM、PowerPC这些架构下,所有的外设和主存(RAM)都会进行统一的编址,所以kernel可以像访问正常的内存空间一样访问内设。而x86体系架构下,外设是进行独立编址的,如图8所示,因此也就出现了IO空间和Memory空间的区别。(其实可以将RAM看成一种”专门用来内存映射的IO设备“)。另外我们从图8还可一看到另外一个信息,那