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_pkts
	return (*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;
		};
		//找到了描述符的位置,也就从软件队列中找到了mbuf
		rxe = &sw_ring[rx_id];
		rx_id++;
		rxm = rxe->mbuf;		
		//填充mbuf
		pkt_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) 
	{
		//申请一个新的mbuf
		nmb = rte_rxmbuf_alloc(rxq->mb_pool);
		//因为原来的mbuf被应用层取走了。这里替换原来的软件队列mbuf,这样网卡收到报文后可以放到这个新的mbuf
		rxe->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标记清0
		rxdp->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_pkts
	return (*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报文的发送,接收流程已经分析完成了

原文链接:https://blog.csdn.net/ApeLife/article/details/102469243?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-3

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值