dpdk报文收发流程--理解dma控制器、UIO驱动、描述符空间、mbuf空间、KNI
1. dpdk报文收发流程
1.1 报文接收流程
传统方式接收报文时,当网卡接收到报文后会产生硬件中断,进而报文会通过协议栈,最后到达应用层,这个过程需要内核协议栈的处理。
和传统报文接收不同,当应用层想要接收来自网卡的报文时, 应用层通过while死循环的方式,调用rte_eth_rx_burst接口轮询接收来自网卡的报文,相当于绕过了内核协议栈,将内核旁路了。
通过轮询的方式,报文不经过内核,提高了网络转发性能,同时也降低了内核与用户态系统调用的开销。
- 当网卡接收到报文时将会产生硬件中断,通知dma控制器接收报文。
- dma控制器会从网卡接收队列中将报文拷贝到硬件接收空间(也就是描述符空间)指向的地址位置,也就是mbuf。最终dma控制器将报文从网卡接收队列中拷贝到mbuf来。
- 应用层通过while死循环,轮询调用rte_eth_rx_burst接口,从这个mbuf软件接收空间中获取报文。
应用层调用rte_eth_rx_burst接口来接收报文,函数内部会调用pmd用户态驱动的接收报文接口。
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);
}
如果是e1000网卡,则pmd用户态驱动接收报文的接口为eth_igb_recv_pkts
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;}
}
描述符是mbuf与dma控制器的中介,dma控制器通过读取寄存器就知道描述符队列的地址。
当应用层从软件队列中获取到mbuf后, 需要重新从内存池申请一个mbuf空间,并将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; }
}
1.2 报文发送流程
当应用层需要发送报文时,调用rte_eth_tx_burst接口,将报文放到软件发送空间,也就是mbuf空间中。同时将mbuf的地址写入到硬件发送空间,也就是描述符空间。dma控制器读取描述符空间,就知道需要从描述符指向的位置,也就是mbuf中获取报文,然后通过网卡发送出去。
//发送报文
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);
}
当应用层要发包时,eth_igb_xmit_pkts内部会将应用层的报文放到软件队列中。在软件队列中找到了最后一次发送的的位置后,就可以将报文放到这个软件队列相应位置上。
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);
}
发送过程,也是将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);
}
对于要发送的每一个mbuf报文,都需要将mbuf的地址信息保存到描述符队列中。dma控制器通过读取寄存器就可以知道描述符队列的地址,也就知道需要从描述符队列指向的位置,也就是从mbuf中获取报文,然后从网卡发送出去。
2. dpdk uio驱动实现
在系统加载igb_uio驱动后,每当有网卡和igb_uio驱动进行绑定时, 就会在/dev目录下创建一个uio设备,例如/dev/uio1。uio设备是一个接口层,用于将pci网卡的内存空间以及网卡的io空间暴露给应用层。通过这种方式,应用层访问uio设备就相当于访问网卡。
2.1 用户态驱动架构
整个架构由用户态驱动pmd,运行在内核态的igb_uio驱动,以及linux的uio框架组成。
- 用户态驱动pmd通过轮询的方式,直接从网卡收发报文,将内核旁路了,绕过了协议栈,避免了内核和应用层之间的拷贝性能;
- 内核态驱动igb_uio,用于将pci网卡的内存空间,io空间暴露给应用层,供应用层访问,同时会处理在网卡的硬件中断;
- linux uio框架提供了一些给igb_uio驱动调用的接口,例如uio_open打开uio; uio_release关闭uio; uio_read从uio读取数据; uio_write往uio写入数据。linux uio框架的代码在内核源码drivers/uio/uio.c文件中实现。
应用层pmd通过read系统调用来访问/dev/uiox设备,进而调用igb_uio驱动中的接口, igb_uio驱动最终会调用linux uio框架提供的接口。
2.2 用户态驱动pmd数据结构
虽然pmd运行在用户态,但也是一个网卡驱动程序。 既然是网卡驱动程序也就少不了对网卡硬件进行配置操作,例如设置网卡接收缓冲区,发送缓冲区的大小。这里说的对网卡进行配置, 也就是对网卡寄存器进行配置。每个网卡都有自己的配置空间,配置空间里面有很多的寄存器,每种寄存器各自负责不同的功能。例如接收控制寄存器,用于对网卡接收到报文的一些设置; 中断寄存器,用于设置允许产生哪里中断事件,例如链路中断。
pmd里面会维护一个网卡数组,对于每一个网卡结构,都会维护这个网卡的接收发送数据的回调,关联的驱动等信息。以此同时,对于每一个网卡,都会创建一个以之一一对应的网卡数据空间结构,这个结构维护了网卡 的接收队列,发送队列信息。后续报文的收发,都会用到这个网卡数据空间结构。
2.3 dpdk应用层对网卡进行配置
对于每一个网卡,应用层都需要调用相应接口进行配置。例如:
- 应用层将对网卡进行配置下发,将配置信息下发给网卡;
- 应用层对网卡接收队列进行设置;
- 应用层对网卡发送队列进行设置;
- 应用层启动网卡等操作。
以l2fwd为例:
- 应用层调用rte_eth_dev_configure进行配置下发;
- 调用rte_eth_rx_queue_setup对网卡接收队列进行设置;
- 调用rte_eth_tx_queue_setup对网卡发送队列进行设置;
- 调用rte_eth_dev_start启用网卡。
2.4 网卡接收队列空间开辟
- 描述符空间,是用于告诉dma控制器,网卡收到报文后,将报文放到哪个位置。
- dma控制器从网卡收包后,放到硬件描述符空间e1000_adv_rx_desc描述符成员pkt_addr指向的位置,也就是mbuf位置。最终报文是存放到mbuf中
2.5 rte_eth_tx_queue_setup网卡发送队列设置
- 描述符空间,是用于告诉dma控制器,dma应该从哪个位置获取报文后发送出去。
- dma控制器从描述符空间e1000_adv_tx_desc成员buffer_addr指向的位置获取报文,实际上就是从mbuf中获取报文。然后将报文从网卡发送出去
3. dpdk之kni实现
通常情况下dpdk用于二三层报文转发,接收到来自网卡的报文后,如果是二层报文则查找fdb表; 如果是三层报文,则进行dnat, snat处理后,查找路由表, 将报文转发给下一跳路由。这些二三层转发操作都是直接转发到另一台设备上,不需要经过内核,无需内核协议栈的参与。
然而有些场景下报文是直接发给运行dpdk程序的这台设备本身的。
那dpdk通过什么方式将报文转发给内核呢? 可以通过kni设备,也可以通过tun/tap虚拟网卡来实现。 相对于tun/tap实现, kni减少了内核态与应用层之间内存拷贝的操作,具有更高的转发性能。
要使得dpdk能够利用kni设备将报文发给内核协议栈, kni需要实现应用层功能与驱动层功能。
驱动层需要创建一个/dev/kni混合设备,这个在应用层加载kni驱动的时候自动完成创建。 通过这个/dev/kni混合设备,可以接收应用层的ioctl消息,按需来创建各种kni设备、删除kni设备、打开kni设备、关闭kni设备、设置mtu、接收ethtool工具的命令操作消息等等。需要注意的是,驱动层创建的两种设备,一个是/dev/kni混合设备, 另一个是kni设备,这两个是不同的设备类型。
应用层则提供给调用者操作kni设备的接口。例如kni的初始化、按照需要为每个网卡分配一个或者多个kni设备、将来自网卡的报文通过kni设备发给内核、接收来自内核的报文后将报文通过网卡发送出去。另外也可以使用linux工具ifconfig、ethtool、tcpdump来操作kni设备。例如给kni设备配置ip地址,抓包等。
3.1 应用层kni的实现
对于每一个网卡,都可以创建一个或者多个kni设备。具体每一张网卡可以创建多少个kni设备,由应用层自行指定。创建完kni设备后,ifconfig -a命令执行后就可以看到这些虚拟网卡名,例如veth1_0, veth1_1。
如果加载驱动的时候,指定了单线程模型,则kni驱动将只会创建一个线程,用于所有的kni设备接收来自应用层的报文。 如果加载kni驱动的时候,指定了多线程模型,则对于每个kni设备,kni驱动都会创建一个线程去接收来自应用层发来的报文。 kni设备与线程是一一对应的关系。
当应用层从网卡收到报文后,将报文放到kni设备的rx接收队队列。kni驱动就会从这个rx接收队列中取出mbuf报文,将mbuf报文转为内核协议栈支持的sk_buff,调用netif_rx内核接口发给内核。
当kni驱动收到来自内核的报文后,会调用kni_net_tx从malloc分配队列中获取一个应用层已经分配好的mbuf结构。同时将sk_buff报文转为mbuf报文,存放到mbuf中。之后将mbuf报文发到tx发送队列中。应用层从tx发送队列中获取报文后,将报文通过网卡发送出去。
3.2 驱动层kni的实现
驱动层会创建一个/dev/kni混合设备,这个在应用层加载kni驱动的时候自动完成创建。 通过这个/dev/kni混合设备,可以接收应用层的ioctl消息,按需来创建各种kni设备、删除kni设备、打开kni设备、关闭kni设备、设置mtu、接收ethtool工具的命令操作消息等等。
参考
dpdk报文收发流程
dpdk源码分析
dpdk报文收发流程--理解dma控制器、UIO驱动、描述符空间、mbuf空间、KNI相关推荐
- DPDK 报文收发流程(二十五)
一.报文的接收流程 传统方式接收报文时,当网卡接收到报文后会产生硬件中断,进而报文会通过协议栈,最后到达应用层,这个过程需要内核协议栈的处理. 和传统报文接收不同,当应用层想要接收来自网卡的报文时, ...
- 移动和云环境下的报文传输流程--理解DNS解析、CDN资源下发、公网传输流程、数据中心网络
DNS解析 手机打开app时,首先需要解析网站的域名. 在手机运营商所在的互联网区域中,有一个本地DNS,手机向本地DNS请求解析DNS.如果本地DNS有缓存,则直接返回:如果本地DNS没有缓存,则递 ...
- DPDK报文收发 run to completion, pipeline
摘自 深入浅出DPDK
- 【DMA】DMA 控制器文档
DMAengine 控制器文档¶ 这本书有助于 DMAengine 内部 API 和 DMAEngine 设备驱动程序编写者指南. 硬件介绍¶ 大多数从 DMA 控制器具有相同的一般操作原理. DMA ...
- 【SemiDrive源码分析】【MailBox核间通信】46 - Android侧 RPMSG_IPCC_RPC驱动分析(下) 之 RPMSG_IPCC_RPC驱动初始化、数据收发流程分析
[SemiDrive源码分析][MailBox核间通信]46 - Android侧 RPMSG_IPCC_RPC驱动分析(下) 之 RPMSG_IPCC_RPC驱动初始化.数据收发流程分析 三. rp ...
- 深入理解Access和Trunk端口的报文收发规则
下面的内容摘自一年多来一直热销不断的<Cisco/H3C交换机配置与管理完全手册>图书. 许多朋友对Cisco交换机中的Access和Trunk二层端口的报文收发规则的理解都只停留在表面, ...
- DPDK uio驱动实现(二十)
一.dpdk uio驱动框架 在系统加载igb_uio驱动后,每当有网卡和igb_uio驱动进行绑定时, 就会在/dev目录下创建一个uio设备,例如/dev/uio1.uio设备是一个接口层,用于将 ...
- linux内核网络协议栈--网卡报文收发(十六)
版本说明 Linux版本: 3.10.103 网卡驱动: ixgbev 报文收发简单流程 网卡驱动默认采用的是NAPI的报文处理方式.即中断+轮询的方式,网卡收到一个报文之后会产生接收中断,并且屏蔽中 ...
- 认识DPDK的UIO驱动(一)
用户态驱动程序UIO UIO(Userspace I/O)是运行在用户空间的I/O技术.Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可. UIO的内核部分和用户空间的 ...
最新文章
- 实现扫码登陆的最简单方案与原理
- 单一nginx负载均衡+LNMP分布式架构
- carafe 上采样
- 基于 Knative 低成本部署在线应用,灵活自动伸缩
- 修复kali grub引导
- 数码管和573锁存器的细节问题
- my.ini优化mysql数据库性能的十个参数(推荐)
- 前端学习(624):小结
- 基于JAVA+SpringMVC+MYSQL的宠物管理系统
- 基于JavaWeb开发的智慧水务管理系统软件设计说明书
- matlab adaptfilt.rls,基于RLS算法的多麦克风降噪
- 上门洗车软件的核心功能和技术选型
- CSTC 部分pwn wp
- pinyin4j新手教程
- TCP/IP卷一:26---Internet协议之(IPv4、IPv6数据报,Internet校验和)
- 机器人学:操作臂运动学(Manipulator Forward Kinematics)
- Linux中软件管理的yum命令
- 20220210纪中集训总结
- 求解随机规划的情景树,情景规划 scenario 方法
- 【数学建模】-多元线性回归分析