一、 前言
DPDK是intel工程师开发的一款用来快速处理数据包的框架,最初的目的是为了证明传统网络数据包处理性能低不是intel处理器导致的,而是传统数据的处理流程导致,后来随着dpdk的开源及其生态的快速发展,dpdk成为了高性能网络数据处理的优秀框架。本篇文章主要介绍DPDK接收与发送报文的流程,包括CPU与网卡DMA协同工作的整个交互流程、数据包在内存、CPU、网卡之间游走的过程。
【二、场景】
DPDK从2013开始开源,经过前辈们的缝缝补补到现在为止DPDK框架比较成熟、使用比较方便,使得现在开发者在不需要深入了解底层数据包收发原理的情况下也可以做简单的项目开发。但是个人感觉做简单的项目尚且可以应付,如果需要做性能优化等类似的需求时就需要取全面的了解DPDK的收发包机制,因为收发包性能与驱动工作流程、前期初始化配置息息相关。话不多说,下面我们进入正题。
【三、收发包处理流程】
【四、收包软件处理流程】
DPDk在初始化阶段通过igb_alloc_rx_queue_mbufs 负责将描述符,mbuf, dma,接收队列给关联起来,如下图所示。初始化配置这块内容下篇讲
1、模块/硬件介绍
Network interface: 指以太网卡,它工作在OSI的下两层(物理层、数据链路层),工作在物理层的芯片称为PHY,工作在数据链路层的芯片称为MAC控制器,即Media Access Control,即媒体访问控制子层协议.该协议位于OSI七层协议中数据链路层的下半部分,但是目前好多网卡是将MAC和PHY功能做到了一颗芯片中,但是MAC和PHY的机制还是单独存在的,只是对外体现为一颗芯片。MAC控制器的功能主要是数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能;PHY芯片的主要功能是将从PHY来的并行数据转换为穿行流数据,再按照物理层的编码规则把数字信号进行编码,最后再转换为模拟信号把数据发出去。
RX_FIFO: 数据接收缓冲区
TX_FIFO: 数据发送缓冲区
DMA Engine:Direct Memory Access,即直接寄存器访问,是一种高速的数据传输方式,允许在外部设备和存储器之间直接读写数据,数据的读写不消耗CPU资源。
DMA控制器通过一组描述符环形队列rx_ring)与CPU互相操作完成数据包的收发。CPU(cpu执行PMD驱动/程序)通过操作DMA寄存器来与DMA控制器进行部分通信与初始化配置,主要寄存器有Base、Size、Tail、Head。head寄存器用于DMA往rx_ring里插入时使用;tail是应用(PMD驱动)通过写寄存器通知给DMA控制器当前可用的最后一个描述符(head->next 为tail时表示当前rx_ring存满了,再来报文会被记录rx_missed_error)。
Rx_queue: 收包队列结构体:我们主要关注两个环形队列rx_ring、sw_ring
Rx_ring:一个地址连续环形队列,存储的是描述符,描述符中包含将来存放数据包的物理地址、DD标志(下面会介绍DD标志)等,上面图中只画了存放数据包的物理地址,物理地址供网卡DMA模块使用,也称为DMA地址(硬件使用物理地址,当网卡收到数据包后会通过DMA操作将数据包拷贝到物理地址,物理地址是通过虚拟地址转换得到的,下面分析源码时会介绍)
Sw_ring: 存储的是将来存放数据包的虚拟地址,虚拟地址供应用使用(软件应用使用虚拟地址,应用往虚拟地址读写数据包)
DD标志:用于标识一个描述符buf是否可用,无论网卡是工作在轮询方式下还是中断方式,判断数据包接收成功或者是否发送成功都需要检查描述符中的完成状态位(Description Done)DD,该状态位由DMA控制器在完成操作(从网卡FIFO接收队列到内存(收),或者从内存到网卡FIFO发送队列(发))后进行回写(设置为DD位为1(收),设置为0(发))
Mbuf:对应于Mbuf内存池中的元素,通过alloc或者free操作内存池获取或者释放mbuf对象,这里需要说的一点是mbuf池创建的时候是连续的,但是rx_ring和sw_ring里指向的数据地址不一定是连续的,下面分析收包流程时会介绍
PCIE总线:采用高速串行通信互联标准,自上而下分为事务传输层、数据链路层、物理层,网卡与CPU之间数据包的传输、CPU对网卡寄存器的MMIO操作都通过PCIE进行传输。
Tx_queue: 发包队列结构体:我们主要关注两个环形队列tx_ring、sw_ring.
tx_ring一个地址连续环形队列,存储的是描述符,描述符中包含将来存放数据包的物理地址、DD标志等,
DMA寄存器:CPU配置网卡的操作通过操作网卡的寄存器,网卡会通过DMA将报文放在系统内存中,另外,CPU(PMD驱动程序)配置网卡的操作也是通过操作网卡的寄存器(DMA寄存器)。存在几个问题:
网卡如何知道应该放在哪里呢?如何将网卡和系统内存关联起来?
这需要用到网卡的几个寄存器。
RDBAL(Receive Descriptor Base Address Low), RDBAH(Receive Descriptor Base Address High) RDLEN(Receive Descriptor Length) RDH(Receive Descriptor Head) RDT(Receive Descriptor Tail)。
PMD驱动初始化时会分配一块内存,将这块内存的起始物理地址(64位)写到寄存器。
RDBAL(保存起始物理地址的低32位)和RDBAH(保存物理地址的高32位),然后将这块内存的大小写到寄存器RDLEN中;这块内存称为硬件描述符队列(union ixgbe_adv_rx_desc 数组)。
硬件描述符队列大小 = 接收队列硬件描述符个数 * 接收队列硬件描述符大小。
一个接收队列硬件描述符大小为16字节( sizeof(union ixgbe_adv_rx_desc) ),个数可配置,一般为1024。
DD位的理解
DD标志,这个标志标识着一个描述符是否可用的情况。
网卡(DMA)在使用这个描述符前,先检查DD位是否为0,如果为0,那么就可以使用描述符,把数据拷贝到描述符指定的地址,之后把DD标志位置为1,否则表示不能使用这个描述符。
而对于PMD驱动而言,恰恰相反,在读取数据包时,先检查DD位是否为1,如果为1,表示网卡已经把数据放到了内存中,可以读取,读取完后,再把DD位设置为0,否则,就表示没有数据包可读。
网卡与内存关联
接收硬件描述符
接收硬件描述符,数据结构如下所示:
一个描述符(union ixgbe_adv_rx_desc)有两种格式;如下所示:
读格式(union ixgbe_adv_rx_desc 中的read)
读格式是从网卡(DMA)角度来说的,由PMD驱动将mbuf的物理地址写到packet buffer address字段(pkt_addr),网卡(DMA)读取此字段获取内存物理地址,收到的报文就可以存到此内存。
回写格式(union ixgbe_adv_rx_desc 中的 wb「write-back」)
回写格式也是从网卡(DMA)角度来说,网卡(DMA)将报文写到指定的内存后,就会以下面的格式将报文的相关信息回写到描述符(union ixgbe_adv_rx_desc)中,最后设置DD位(第二个8字节的最低位);PMD驱动程序通过判断DD位是否为1来接收报文(DD位为1,说明DMA将报文放入到内存了,PMD驱动可以接收报文)。
总结:
接收队列硬件描述符队列就是一块内存。
网卡先以读格式,读取描述符,获取内存的物理地址,将报文写到内存;
然后以回写格式将报文额外信息写到描述符中;
PMD驱动程序可以以回写格式读取描述符,获取报文的长度,类型等信息。
收包流程
接收报文时需要将网卡和内存关联起来,即将要将要存放报文的地址告诉网卡,这是通过接收硬件描述符来实现的。
接收报文时需要将网卡和内存关联起来,即将要将要存放报文的地址告诉网卡,这是通过接收硬件描述符来实现的。
PMD驱动/程序收包
RDH/RDT寄存器
网卡和内存关联起来后,就可以收取报文了,此时又用到两个寄存器: RDH(Receive Descriptor Head)和RDT(Receive Descriptor Tail)。
RDH:
RDH为头指针,指向第一个可用描述符。
网卡收取报文并且DMA回写成功后,由网卡(DMA)来移动RDH到下一个可用描述符。
RDT:
RDT为尾指针,指向最后一个可用描述符。
RDH和RDT之间的描述符为网卡可用描述符,RDT由PMD驱动来移动。
PMD驱动从第一个描述符开始,轮询DD位是否为1,为1就认为此描述符对应的mbuf有报文,此时会申请新的mbuf,将新mbuf物理地址写到此描述符的pkt_addr,并将DD位置0,这样的话此描述符就又可用被网卡使用了;同时将老的有报文的mbuf返回给用户。
描述符再次可用后,PMD驱动就可以更新RDT指向此描述符,为了性能考虑不会每次都会更新RDT,而是等可用描述符超过一定阈值(rx_free_thresh)才更新一次(PMD驱动/CPU通过写RDT寄存器来更新)。
收包大致流程:
DMA从描述符队列(rx_ring数组)的可用位置(union ixgbe_adv_rx_desc类型)获取到mbuf的硬件地址;
然后将数据包从网卡FIFO写入到指定的硬件地址中(对应sw_ring数组的某个mbuf元素);设置对应描述符的DD位以及其他的信息(比如Rss hash,pkt-len等);(DD位为1表示当前描述符被占用了,接下来DMA将包放入到下一个可用描述符中指定的硬件地址中)
最后发送一个硬件中断(对于DPDK,屏幕了中断,PMD驱动程序通过轮询的方式检查是否有包到达)。
2、收包流程
Rx_ring收数据时的状态如下:
DMA控制器收到数据后往head写,当head=tail时表示当前队列为空,head->bext = tail表示当前队列已存满,dpdk启动刚初始化完后如下图所示:
1> dpdk启动刚初始化完后,如下图所示:
2> 收取数据包,网卡DMA 移动head
可以看出来cpu对tail寄存器的更新并不是在rx_ring描述符中填充完dma地址后立马就执行,而是等dma可用描述符低于一定阈值时才执行写寄存器更新tail
3> 应用程序(PMD驱动)取包:
4>驱动继续取包: