1. dpdk报文收发流程

1.1 报文接收流程

传统方式接收报文时,当网卡接收到报文后会产生硬件中断,进而报文会通过协议栈,最后到达应用层,这个过程需要内核协议栈的处理。

和传统报文接收不同,当应用层想要接收来自网卡的报文时, 应用层通过while死循环的方式,调用rte_eth_rx_burst接口轮询接收来自网卡的报文,相当于绕过了内核协议栈,将内核旁路了。

通过轮询的方式,报文不经过内核,提高了网络转发性能,同时也降低了内核与用户态系统调用的开销。

  1. 当网卡接收到报文时将会产生硬件中断,通知dma控制器接收报文。
  2. dma控制器会从网卡接收队列中将报文拷贝到硬件接收空间(也就是描述符空间)指向的地址位置,也就是mbuf。最终dma控制器将报文从网卡接收队列中拷贝到mbuf来。
  3. 应用层通过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框架组成。

  1. 用户态驱动pmd通过轮询的方式,直接从网卡收发报文,将内核旁路了,绕过了协议栈,避免了内核和应用层之间的拷贝性能;
  2. 内核态驱动igb_uio,用于将pci网卡的内存空间,io空间暴露给应用层,供应用层访问,同时会处理在网卡的硬件中断;
  3. 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应用层对网卡进行配置

对于每一个网卡,应用层都需要调用相应接口进行配置。例如:

  1. 应用层将对网卡进行配置下发,将配置信息下发给网卡;
  2. 应用层对网卡接收队列进行设置;
  3. 应用层对网卡发送队列进行设置;
  4. 应用层启动网卡等操作。

以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相关推荐

  1. DPDK 报文收发流程(二十五)

    一.报文的接收流程 传统方式接收报文时,当网卡接收到报文后会产生硬件中断,进而报文会通过协议栈,最后到达应用层,这个过程需要内核协议栈的处理. 和传统报文接收不同,当应用层想要接收来自网卡的报文时, ...

  2. 移动和云环境下的报文传输流程--理解DNS解析、CDN资源下发、公网传输流程、数据中心网络

    DNS解析 手机打开app时,首先需要解析网站的域名. 在手机运营商所在的互联网区域中,有一个本地DNS,手机向本地DNS请求解析DNS.如果本地DNS有缓存,则直接返回:如果本地DNS没有缓存,则递 ...

  3. DPDK报文收发 run to completion, pipeline

    摘自 深入浅出DPDK

  4. 【DMA】DMA 控制器文档

    DMAengine 控制器文档¶ 这本书有助于 DMAengine 内部 API 和 DMAEngine 设备驱动程序编写者指南. 硬件介绍¶ 大多数从 DMA 控制器具有相同的一般操作原理. DMA ...

  5. 【SemiDrive源码分析】【MailBox核间通信】46 - Android侧 RPMSG_IPCC_RPC驱动分析(下) 之 RPMSG_IPCC_RPC驱动初始化、数据收发流程分析

    [SemiDrive源码分析][MailBox核间通信]46 - Android侧 RPMSG_IPCC_RPC驱动分析(下) 之 RPMSG_IPCC_RPC驱动初始化.数据收发流程分析 三. rp ...

  6. 深入理解Access和Trunk端口的报文收发规则

    下面的内容摘自一年多来一直热销不断的<Cisco/H3C交换机配置与管理完全手册>图书. 许多朋友对Cisco交换机中的Access和Trunk二层端口的报文收发规则的理解都只停留在表面, ...

  7. DPDK uio驱动实现(二十)

    一.dpdk uio驱动框架 在系统加载igb_uio驱动后,每当有网卡和igb_uio驱动进行绑定时, 就会在/dev目录下创建一个uio设备,例如/dev/uio1.uio设备是一个接口层,用于将 ...

  8. linux内核网络协议栈--网卡报文收发(十六)

    版本说明 Linux版本: 3.10.103 网卡驱动: ixgbev 报文收发简单流程 网卡驱动默认采用的是NAPI的报文处理方式.即中断+轮询的方式,网卡收到一个报文之后会产生接收中断,并且屏蔽中 ...

  9. 认识DPDK的UIO驱动(一)

    用户态驱动程序UIO UIO(Userspace I/O)是运行在用户空间的I/O技术.Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可. UIO的内核部分和用户空间的 ...

最新文章

  1. 实现扫码登陆的最简单方案与原理
  2. 单一nginx负载均衡+LNMP分布式架构
  3. carafe 上采样
  4. 基于 Knative 低成本部署在线应用,灵活自动伸缩
  5. 修复kali grub引导
  6. 数码管和573锁存器的细节问题
  7. my.ini优化mysql数据库性能的十个参数(推荐)
  8. 前端学习(624):小结
  9. 基于JAVA+SpringMVC+MYSQL的宠物管理系统
  10. 基于JavaWeb开发的智慧水务管理系统软件设计说明书
  11. matlab adaptfilt.rls,基于RLS算法的多麦克风降噪
  12. 上门洗车软件的核心功能和技术选型
  13. CSTC 部分pwn wp
  14. pinyin4j新手教程
  15. TCP/IP卷一:26---Internet协议之(IPv4、IPv6数据报,Internet校验和)
  16. 机器人学:操作臂运动学(Manipulator Forward Kinematics)
  17. Linux中软件管理的yum命令
  18. 20220210纪中集训总结
  19. 求解随机规划的情景树,情景规划 scenario 方法
  20. 【数学建模】-多元线性回归分析

热门文章

  1. listFiles返回null
  2. TcaplusDB君 · 行业新闻汇编(四)
  3. 达芬奇17调色软件支持m1
  4. 查看MDB格式文件数据表
  5. 基于FIddler实现Chrome内核下对网页进行填表操作以及原理
  6. IOS14实用教程——如何在iPhone和iPad上更改默认的Web浏览器
  7. J1939协议与CAN2.0对应关系图表
  8. 导出App Inventor的aix扩展方法
  9. java 数字全排列_Java实现n位数字的全排列
  10. CAS 使用 HTTPS 单向认证方式 服务端和客户端配置