DPDK之网卡收包流程

发布时间:19-05-0709:06

1.导读

一个网络报文从网卡接收到被应用处理,中间主要需要经历两个阶段:

阶段一:网卡通过其DMA硬件将收到的报文写入到收包队列中(入队阶段二:应用从收包队列中读取报文(出队下面以ixgbe网卡在dpdk框架下工作为例,分别介绍下收包队列的构造、启动和收包三个流程。

2.构造

收包队列的构造主要是通过调用网卡队列设置函数rte_eth_rx_queue_setup(dpdk rte_ethdev.h)来完成。

收包队列的结构体为ixgbe_rx_queue,该结构体里包含两个重要的环形队列rx_ring和sw_ring,rx_ring和sw_ring的关系可以简单如下认为。

rx_ring主要存储报文数据的物理地址,物理地址供网卡DMA使用,也称为DMA地址(硬件使用物理地址,将报文copy到报文物理位置上)。sw_ring主要存储报文数据的虚拟地址,虚拟地址供应用使用(软件使用虚拟地址,读取报文)。其中,报文数据的物理地址可以由报文数据的虚拟地址转化得到。下面详细介绍下rx_ring与sw_ring两个队列的构成。

2.1.rx_ring

rx_ring是由一个动态申请的数组构建的环形队列,队列的元素是ixgbe_adv_rx_desc类型,队列的长度为(4096+4-1)。

pkt_addr:报文数据的物理地址,网卡DMA将报文数据通过该物理地址写入对应的内存空间。hdr_addr:报文的头信息,hdr_addr的最后一个bit为DD位,因为是union结构,即status_error的最后一个bit也对应DD位。

DD位(Descriptor Done Status)用于标志标识一个描述符buf是否可用。

网卡每次来了新的数据包,就检查rx_ring当前这个buf的DD位是否为0,如果为0那么表示当前buf可以使用,就让DMA将数据包copy到这个buf中,然后设置DD为1。如果为1,那么网卡就认为rx_ring队列满了,直接会将这个包给丢弃掉,记录一次imiss。(0->1对于应用而言,DD位使用恰恰相反,在读取数据包时,先检查DD位是否为1,如果为1,表示网卡已经把数据包放到了内存中,可以读取,读取完后,再放入一个新的buf并把对应DD位设置为0。如果为0,就表示没有数据包可读。(1->0

2.2.sw_ring

sw_ring是由一个动态申请的数组构建的环形队列,队列的元素是ixgbe_rx_entry类型,队列的大小可配,一般最大可配4096。

mbuf:报文mbuf结构指针,mbuf用于管理一个报文,主要包含报文相关信息和报文数据。

3.启动

收包队列的启动主要是通过调用rte_eth_dev_start(dpdk rte_ethdev.h)函数完成,收包队列初始化的核心流程如下。

循环从mbuf pool中申请mbuf,从mbuf中得到报文数据对应的物理地址,物理地址存入rx_ring中,mbuf指针存入sw_ring中。其中通过rxd->read.hdr_addr = 0,完成了DD位设置为0。

一切ok后,就可以开始收包了。

3.收包

收包由网卡入队和应用出队两个操作完成。

3.1入队

入队的操作是由网卡DMA来完成的,DMA(Direct Memory Access,直接存储器访问)是系统和网卡(外设)打交道的一种方式,该种方式允许在网卡(外部设备)和系统内存之间直接读写数据,这样能有效减轻CPU的工作。

网卡收到报文后,先存于网卡本地的buffer-Rx(Rx FIFO)中,然后由DMA通过PCI总线将报文数据写入操作系统的内存中,即数据报文完成入队操作。(PS:PCIe总线可能成为网卡带宽的瓶颈)

3.2.出队

应用调用rte_eth_rx_burst(dpdk rte_ethdev.h)函数开始批量收包,最大收包数量由参数nb_pkts决定(比如设置为64)。其核心流程由ixgbe_recv_pkts(dpdk ixgbe_rxtx.c)实现,从收包队列rx_tail位置开始收,循环读取一个报文、填空一个报文(空报文数据),读取64个后,重新标记rx_tail的位置,完成出队操作,将收取的报文作返回供应用处理。代码简化如下。

struct rte_mbuf *rxm;

//从队列的tail位置开始取包

rx_id = rxq->rx_tail;

//循环获取nb_pkts个包

while (nb_rx < nb_pkts)

{

......

rxdp = &rx_ring[rx_id];

//检查DD位是否为1,是1则说明该位置已放入数据包,否则表示没有报文,退出

staterr=rxdp->wb.upper.status_error;

if(!(staterr&rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD)))

break;

rxd = *rxdp;

//申请一个mbuf(nmb),用于交换

nmb = rte_mbuf_raw_alloc(rxq->mb_pool);

//从sw_ring中读取一个报文mbuf(存入rxm)

rxe = &sw_ring[rx_id];

rxm = rxe->mbuf;

//往sw_ring中填空一个新报文mbuf(nmb)

rxe->mbuf = nmb;

//新mbuf对应的报文数据物理地址填入rx_ring对应位置,并将hdr_addr置0(DD位置0)

dma_addr =rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(nmb));

rxdp->read.hdr_addr = 0;

rxdp->read.pkt_addr = dma_addr;

//对读取mbuf的报文信息进行初始化

rxm->pkt_len = pkt_len;

rxm->data_len = pkt_len;

rxm->port = rxq->port_id;

......

//读取的报文mbuf存入rx_pkts

rx_pkts[nb_rx++] = rxm;

}

//重新标记rx_tail位置

rxq->rx_tail = rx_id;

返回顶部