当前位置: 代码迷 >> 综合 >> DPDK--报文收发流程
  详细解决方案

DPDK--报文收发流程

热度:37   发布时间:2024-03-05 20:51:13.0

原文链接:https://blog.csdn.net/ApeLife/article/details/102469243?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

作者:ApeLife

 

  本篇文章来分析下dpdk是如何接收报文以及发送报文的。

一、报文的接收流程

         传统方式接收报文时,当网卡接收到报文后会产生硬件中断,进而报文会通过协议栈,最后到达应用层,这个过程需要内核协议栈的处理。 和传统报文接收不同,当应用层想要接收来自网卡的报文时, 应用层通过while死循环的方式,调用rte_eth_rx_burst接口轮询接收来自网卡的报文,相当于绕过了内核协议栈,将内核旁路了。通过轮询的方式,报文不经过内核,提高了网络转发性能,同时也降低了内核与用户态系统调用的开销。

        来看下报文接收的整体流程。当网卡接收到报文时将会产生硬件中断,通知dma控制器接收报文。dma控制器会从网卡接收队列中将报文拷贝到硬件接收空间(也就是描述符空间)指向的地址位置,也就是mbuf。最终dma控制器将报文从网卡接收队列中拷贝到mbuf来。 应用层通过while死循环,轮询调用rte_eth_rx_burst接口,从这个mbuf软件接收空间中获取报文。

        来看下代码的实现过程。应用层调用rte_eth_rx_burst接口来接收报文,函数内部会调用pmd用户态驱动的接收报文接口。如果是e1000网卡,则pmd用户态驱动接收报文的接口为eth_igb_recv_pkts

uint16_t rte_eth_rx_burst(uint8_t port_id, uint16_t queue_id, struct rte_mbuf **rx_pkts, uint16_t nb_pkts){//如果是e1000网卡,则接收报文的接口为eth_igb_recv_pktsreturn (*dev->rx_pkt_burst)(dev->data->rx_queues[queue_id], rx_pkts, nb_pkts);}

        现在来看下eth_igb_recv_pkts接口的实现过程。

 

1、从描述符中获取报文返回给应用层

        首先根据应用层最后一次获取报文的位置,进而从描述符队列找到待被应用层接收的描述符。此时会判断描述符中的status_error是否已经打上了dd标记,有dd标记说明dma控制器已经把报文放到mbuf中了。这里解释下dd标记,当dma控制器将接收到的报文保存到描述符指向的mbuf空间时,由dma控制器打上dd标记,表示dma控制器已经把报文放到mbuf中了。应用层在获取完报文后,需要清除dd标记。

        找到了描述符的位置,也就找到了mbuf空间。此时会根据描述符里面保存的信息,填充mbuf结构。例如填充报文的长度,vlanid, rss等信息。填充完mbuf后,将这个mbuf保存到应用层传进来的结构中,返回给应用层,这样应用层就获取到了这个报文。

uint16_t eth_igb_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts){while (nb_rx < nb_pkts){//从描述符队列中找到待被应用层最后一次接收的那个描述符位置rxdp = &rx_ring[rx_id];staterr = rxdp->wb.upper.status_error;//检查状态是否为dd, 不是则说明驱动还没有把报文放到接收队列,直接退出if (! (staterr & rte_cpu_to_le_32(E1000_RXD_STAT_DD))){break;};//找到了描述符的位置,也就从软件队列中找到了mbufrxe = &sw_ring[rx_id];rx_id++;rxm = rxe->mbuf;//填充mbufpkt_len = (uint16_t) (rte_le_to_cpu_16(rxd.wb.upper.length) - rxq->crc_len);rxm->data_off = RTE_PKTMBUF_HEADROOM;rxm->nb_segs = 1;rxm->pkt_len = pkt_len;rxm->data_len = pkt_len;rxm->port = rxq->port_id;rxm->hash.rss = rxd.wb.lower.hi_dword.rss;rxm->vlan_tci = rte_le_to_cpu_16(rxd.wb.upper.vlan);//保存到应用层rx_pkts[nb_rx++] = rxm;}}

2、从内存池中获取新的mbuf告诉dma控制器

        当应用层从软件队列中获取到mbuf后, 需要重新从内存池申请一个mbuf空间,并将mbuf地址放到描述符队列中, 相当于告诉dma控制器,后续将收到的报文保存到这个新的mbuf中, 这也是狸猫换太子的过程。描述符是mbuf与dma控制器的中介,那dma控制器怎么知道描述符队列的地址呢?这在上一篇文章中已经介绍过了,将描述符队列的地址写入到了寄存器中,dma控制器通过读取寄存器就知道描述符队列的地址。

        需要注意的是,将mbuf的地址保存到描述符中,此时会将dd标记给清0,这样dma控制器就认为这个mbuf里面的内容已经被应用层接收了,收到新报文后可以重新放到这个mbuf中。

uint16_t eth_igb_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts){while (nb_rx < nb_pkts){//申请一个新的mbufnmb = rte_rxmbuf_alloc(rxq->mb_pool);//因为原来的mbuf被应用层取走了。这里替换原来的软件队列mbuf,这样网卡收到报文后可以放到这个新的mbufrxe->mbuf = nmb;dma_addr = rte_cpu_to_le_64(RTE_MBUF_DATA_DMA_ADDR_DEFAULT(nmb));//将mbuf地址保存到描述符中,相当于高速dma控制器mbuf的地址。rxdp->read.hdr_addr = dma_addr; //这里会将dd标记清0rxdp->read.pkt_addr = dma_addr;}}

二、报文的发送流程

        先整体来看下报文的发送流程。当应用层需要发送报文时,调用rte_eth_tx_burst接口,将报文放到软件发送空间,也就是mbuf空间中。同时将mbuf的地址写入到硬件发送空间,也就是描述符空间。dma控制器读取描述符空间,就知道需要从描述符指向的位置,也就是mbuf中获取报文,然后通过网卡发送出去。

        来看下代码的实现过程。应用层调用rte_eth_tx_burst接口来发送报文,函数内部会调用pmd用户态驱动的发送报文接口。如果是e1000网卡,则pmd用户态驱动发送报文的接口为eth_igb_xmit_pkts

//发送报文uint16_t rte_eth_tx_burst(uint8_t port_id, uint16_t queue_id, struct rte_mbuf **tx_pkts, uint16_t nb_pkts){//如果是e1000网卡,则发送报文的接口为eth_igb_xmit_pktsreturn (*dev->tx_pkt_burst)(dev->data->tx_queues[queue_id], tx_pkts, nb_pkts);}

1、将应用层的报文放到软件队列中

        当应用层要发包时,eth_igb_xmit_pkts内部会将应用层的报文放到软件队列中。在软件队列中找到了最后一次发送的的位置后,就可以将报文放到这个软件队列相应位置上。应用层要发送的报文有可能是需要分片的,每一个分片报文都会占用软件队列中的一个元素。某个报文的所有分片报文所占用的软件队列元素,last_id都指向同一个软件队列元素。

 
 
uint16_t eth_igb_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts){//将分片报文放到发送队列中m_seg = tx_pkt;do{//将mbuf放到发送软件队列中txe->mbuf = m_seg;//同一个分片的软件队列元素,last_id指向同一个队列位置txe->last_id = tx_last;tx_id = txe->next_id;//指向下一个软件队列txe = txn;m_seg = m_seg->next;} while (m_seg != NULL);}

2、将mbuf地址信息保存到描述符中

        对于要发送的每一个mbuf报文,都需要将mbuf的地址信息保存到描述符队列中。而这个描述符队列的地址已经写入到寄存器了,这个在上一篇文章已经分析过了。dma控制器通过读取寄存器就可以知道描述符队列的地址,也就知道需要从描述符队列指向的位置,也就是从mbuf中获取报文,然后从网卡发送出去。

        需要注意的是,这个发送过程,也是将mbuf、dma控制器、描述符队列关联起来的过程。

 
 
uint16_t eth_igb_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts){//将分片报文放到发送队列中m_seg = tx_pkt;do{//找到最后一次发送的位置,也就是找到最后一次可以使用的描述符txd = &txr[tx_id];//将mbuf地址信息保存到发送描述符中slen = (uint16_t) m_seg->data_len;buf_dma_addr = RTE_MBUF_DATA_DMA_ADDR(m_seg);txd->read.buffer_addr = rte_cpu_to_le_64(buf_dma_addr);txd->read.cmd_type_len = rte_cpu_to_le_32(cmd_type_len | slen);txd->read.olinfo_status = rte_cpu_to_le_32(olinfo_status);m_seg = m_seg->next;} while (m_seg != NULL);}

        到此为止,dpdk报文的发送,接收流程已经分析完成了