DPDK-收包完整过程

news2025/6/11 8:17:29

本篇博客作为自己了解dpdk收包过程的一个记录。在写时发现已经有很多写DPDK收包过程的博客了,但还是决定自己写一遍。

DPDK收包分为两个阶段,首先是DMA将数据包从网卡搬运到内存,然后是调用dpdk提供的接口rte_eth_rx_burst去取。但是具体是怎么做的呢?简单大致的过程如图1所示。

图1,dpdk收包大致过程

本文将按如下顺序进行展开。首先会简单介绍dma的环形缓存区,接着会介常规linux dma的收发包过程,然后会结合源码介绍dpdk的双环形缓存区设计,最后结合源码给出dpdk收包的过程。

1、DMA搬运数据包过程

网卡DMA控制器通过环形队列与CPU交互,环形队列由一组控制寄存器和一块物理上连续的缓存构成。主要的控制寄存器有Base, Size, Head和Tail。通过设置Base寄存器,可以将分配的一段物理连续的内存地址作为环形队列的起始地址,通告给DMA控制器。同样通过Size寄存器,可以通告内存快的大小。Head寄存器往往对软件只读,它表示硬件当前访问的描述符单元。而Tail寄存器则由软件来填写更新,通知DMA控制器当前已准备好被硬件访问的描述符单元。这一段话来自《深入浅出DPDK》的描述。

图2,网卡的收发描述符

下面我们结合图3先简单看下Linux中DMA收包的过程,后面再给出dpdk的收包过程。

图3,linxu dma收发包过程

详细过程如下:

1、cpu填充地址到接收侧描述符,也就是将存放sk_buff的地址物理地址填写到Rx ring;

2、网卡读取接收侧描述符获取缓冲区地址;

3、网卡收到数据包后,根据2中读到的缓冲区地址,将数据包的内容通过dma写到缓冲区;

4、网卡回写接收侧描述符更新状态,表示数据包已写完;

5、cpu读取接收侧描述符以确定数据包接收完毕;

6、cpu读取包内容做转发判断;

7、cpu填充更改包内容,做发送准备,比如调换ip地址和mac地址;

8、cpu读取发送侧描述符,检查是否有发送完成标志;

9、cpu将准备发送的缓冲区地址填写到发送侧描述符,也就是将sk_buff的地址填写到Tx ring;

10、网卡读取发送侧描述符中的地址;

11、网卡根据描述符中地址,读取缓冲区的数据内容进行发送;

12、网卡写发送侧描述符,更新发送已完成标记。

上述步骤1-6为接收过程,步骤7-12为发送过程。步骤1-12这段描述也copy自《深入浅出DPDK》。

但是,这里我要补充的是,图2中的rxd和txd就是我们图1中介绍的环形缓冲区,分别叫做Rx ring和Tx ring,也统一叫做DMA ring buffer。

DMA ring buffer的创建在驱动初始化阶段,这块内存由网卡与处理器共享,详细描述见《Linux设备驱动程序》第15章内存直接访问。这也就解释了为啥cpu可以写和读Rx/Tx ring中的描述符,网卡也可以。

2、DPDK收包

在上文描述了linux dma收包过程,那么使用dpdk收包会有啥不同呢?要弄清楚dpdk的收包过程,需要先清楚dpdk的双环形缓存区设计。

2.1 DPDK双环形缓冲区结构

dpdk收包会用到两个ring,一个是rx ring,一个是sw ring,rx ring存放的是数据包的dma地址,sw ring存放的是rte_mbuf的地址,rx ring和sw ring是一一映射的。

图4,rx_ring和sw_ring的映射关系

如图4所以,rx_ring里面的每个描述符中的地址填的是rte_mbuf的buf data地址,而rx_ring的每个描述符存放的是rte_mbuf的地址,也就是说当dma拿到dma_addr1后,然后将数据包写到dma_addr1指向的地址,实际是写到了rte_mbuf的data buffer里。

而rx_ring是一个环形,sw_ring也是一个环形,这就是《深入浅出DPDK》第6章中描述的双环形缓冲区,见图5。

图5,内存池的双环形缓存区结构

这里有一个问题,就是rx_ring和sw_ring是啥时候初始化的以及建立映射关系的?

拿ice driver举例,先看一个结构体struct ice_rx_queue,rx_ring和sw_ring成员是我们后面的重点关注对象。

(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂

更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! ! 

@dpdk-stable-20.11.3/drivers/net/ice/ice_rxtx.h
struct ice_rx_entry {
    struct rte_mbuf *mbuf;
};

struct ice_rx_queue {
    struct rte_mempool *mp; /* mbuf pool to populate RX ring */
    volatile union ice_rx_flex_desc *rx_ring;/* RX ring virtual address */
    rte_iova_t rx_ring_dma; /* RX ring DMA address */
    struct ice_rx_entry *sw_ring; /* address of RX soft ring */
    uint16_t nb_rx_desc; /* number of RX descriptors */
    ...
}

rx_ring和sw_ring是ice_rx_queue结构体成员。rx_ring和sw_ring的初始化都是在dpdk应用程序中调用rte_eth_rx_queue_setup函数时进行的,这个函数最后调用了ice_rx_queue_setup。

@dpdk-stable-20.11.3/drivers/net/ice/ice_rxtx.c
int ice_rx_queue_setup(struct rte_eth_dev *dev,
           uint16_t queue_idx,
           uint16_t nb_desc,
           unsigned int socket_id,
           const struct rte_eth_rxconf *rx_conf,
           struct rte_mempool *mp)
{
    struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
    struct ice_adapter *ad =
        ICE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
    struct ice_vsi *vsi = pf->main_vsi;
    struct ice_rx_queue *rxq;
    const struct rte_memzone *rz;
    uint32_t ring_size;
    uint16_t len;
    int use_def_burst_func = 1;

...

    /* Allocate the rx queue data structure */
    rxq = rte_zmalloc_socket(NULL,                //创建rxq,rxq的类型为ice_rx_queue
                 sizeof(struct ice_rx_queue),
                 RTE_CACHE_LINE_SIZE,
                 socket_id);

    ...
    /* Allocate the maximun number of RX ring hardware descriptor. */
    len = ICE_MAX_RING_DESC;

    /**
     * Allocating a little more memory because vectorized/bulk_alloc Rx
     * functions doesn't check boundaries each time.
     */
    len += ICE_RX_MAX_BURST;

    /* Allocate the maximum number of RX ring hardware descriptor. */
    ring_size = sizeof(union ice_rx_flex_desc) * len;
    ring_size = RTE_ALIGN(ring_size, ICE_DMA_MEM_ALIGN);
    /* 创建rx_ring */
    rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,
                      ring_size, ICE_RING_BASE_ALIGN,
                      socket_id);
    ...

    /* Zero all the descriptors in the ring. */
    memset(rz->addr, 0, ring_size); 
    /* 设置rx_ring的dma地址和虚拟地址。*/
    rxq->rx_ring_dma = rz->iova;
    rxq->rx_ring = rz->addr;

    /* always reserve more for bulk alloc */
    len = (uint16_t)(nb_desc + ICE_RX_MAX_BURST);

    /* Allocate the software ring. */
    rxq->sw_ring = rte_zmalloc_socket(NULL,              //创建sw_ring
                      sizeof(struct ice_rx_entry) * len,
                      RTE_CACHE_LINE_SIZE,
                      socket_id);

    ...
}

根据上面的分析,我们知道了rx_ring和sw_ring是在调用rte_eth_rx_queue_setup进而调用ice_rx_queue_setup时创建的,但是我们不知道这两个ring是啥时候建立的映射。

rx_ring和sw_ring映射关系的建立发生在rte_eth_dev_start调用栈里,具体为调用关系为rte_eth_dev_start-->ice_dcf_dev_start-->ice_dcf_start_queues-->ice_dcf_rx_queue_start-->alloc_rxq_mbufs,看看源码吧。

@dpdk-stable-20.11.3/drivers/net/ice/ice_dcf_ethdev.c
static int
alloc_rxq_mbufs(struct ice_rx_queue *rxq)
{
    volatile union ice_rx_flex_desc *rxd;
    struct rte_mbuf *mbuf = NULL;
    uint64_t dma_addr;
    uint16_t i;

    for (i = 0; i < rxq->nb_rx_desc; i++) {
        mbuf = rte_mbuf_raw_alloc(rxq->mp);     //申请一个mbuf
        if (unlikely(!mbuf)) {
            PMD_DRV_LOG(ERR, "Failed to allocate mbuf for RX");
            return -ENOMEM;
        }

        rte_mbuf_refcnt_set(mbuf, 1);
        mbuf->next = NULL;
        mbuf->data_off = RTE_PKTMBUF_HEADROOM;
        mbuf->nb_segs = 1;
        mbuf->port = rxq->port_id;
        
        //获取rte_mbuf data buff的dma地址
        dma_addr =
            rte_cpu_to_le_64(rte_mbuf_data_iova_default(mbuf));

        rxd = &rxq->rx_ring[i];
        //填充rx_ring的描述符,其实就是rte_mbuf data buff的dma地址,当网卡收到数据包后直接通过
        //dma往这个地址写,所以数据包就存放rte_mbuf里面了。
        rxd->read.pkt_addr = dma_addr;
        rxd->read.hdr_addr = 0;
#ifndef RTE_LIBRTE_ICE_16BYTE_RX_DESC
        rxd->read.rsvd1 = 0;
        rxd->read.rsvd2 = 0;
#endif
        //填充sw_ring,结合图4,这样sw_ring和rx_ring就建立起了映射关系
        rxq->sw_ring[i].mbuf = (void *)mbuf;
    }

    return 0;
}

简单的说就是rte_eth_rx_queue_setup函数创建rx_ring和sw_ring,然后rte_eth_dev_start建立rx_ring和sw_ring之间的映射关系。

2.2 dpdk收包过程

花了很大篇幅来说明网卡的收包原理,以及解释dpdk底层相关的知识,现在终于可以说说dpdk是怎么收包的了。

dpdk提供的收包接口为rte_eth_rx_burst。这个函数最后其实调用了ice_recv_pkts函数。

@dpdk-stable-20.11.3/drivers/net/ice/ice_rxtx.c

uint16_t ice_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
{
    struct ice_rx_queue *rxq = rx_queue;
    volatile union ice_rx_flex_desc *rx_ring = rxq->rx_ring;   //重点关注
    volatile union ice_rx_flex_desc *rxdp;
    union ice_rx_flex_desc rxd;
    struct ice_rx_entry *sw_ring = rxq->sw_ring;    //重点关注
    struct ice_rx_entry *rxe;
    struct rte_mbuf *nmb; /* new allocated mbuf */
    struct rte_mbuf *rxm; /* pointer to store old mbuf in SW ring */
    uint16_t rx_id = rxq->rx_tail;
    uint16_t nb_rx = 0;
    uint16_t nb_hold = 0;
    uint16_t rx_packet_len;
    uint16_t rx_stat_err0;
    uint64_t dma_addr;
    uint64_t pkt_flags;
    uint32_t *ptype_tbl = rxq->vsi->adapter->ptype_tbl;
    struct rte_eth_dev *dev;

    while (nb_rx < nb_pkts) {         //期望接收nb_pkts个数据包
        rxdp = &rx_ring[rx_id];   //rx_id是rx_ring新包的起始id,因为rx_ring是一个环
        rx_stat_err0 = rte_le_to_cpu_16(rxdp->wb.status_error0);

        /* Check the DD bit first */
        if (!(rx_stat_err0 & (1 << ICE_RX_FLEX_DESC_STATUS0_DD_S)))
            break;

        /* allocate mbuf */   
        nmb = rte_mbuf_raw_alloc(rxq->mp);  //申请一个新的rte_mbuf,用于回填rx_ring和sw_ring
        if (unlikely(!nmb)) {
            dev = ICE_VSI_TO_ETH_DEV(rxq->vsi);
            dev->data->rx_mbuf_alloc_failed++;
            break;
        }
        //rxdp指向一个rx_ring,现在rxd也指向了这个rx_ring
        rxd = *rxdp; /* copy descriptor in ring to temp variable*/
        
        nb_hold++;
        //rx_id是sw_ring新包的起始id,因为sw_ring是一个环
        rxe = &sw_ring[rx_id]; /* get corresponding mbuf in SW ring */
        rx_id++;  //rx_ring和sw_ring的下一个地址
        if (unlikely(rx_id == rxq->nb_rx_desc))
            rx_id = 0;   //表示到了ring的尾部,重新开始从ring的头部开始取rte_mbuf
        //保存sw_ring里面的rte_mbuf指针,这个rte_mbuf里面是有数据的
        rxm = rxe->mbuf;
        //给sw_ring里面回填新的描述符,也就是新申请的rte_mbuf指针,用于下一轮收包
        rxe->mbuf = nmb;
        //计算新申请的rte_mbuf data buff的dma地址
        dma_addr =
            rte_cpu_to_le_64(rte_mbuf_data_iova_default(nmb));

        /**
         * fill the read format of descriptor with physic address in
         * new allocated mbuf: nmb
         */
        rxdp->read.hdr_addr = 0;
        //更新rx_ring里面的描述符地址
        rxdp->read.pkt_addr = dma_addr;

        /* calculate rx_packet_len of the received pkt */
        rx_packet_len = (rte_le_to_cpu_16(rxd.wb.pkt_len) &
                 ICE_RX_FLX_DESC_PKT_LEN_M) - rxq->crc_len;

        //填充收到的rte_mbuf里面的相关字段,这个应该很熟悉了
        /* fill old mbuf with received descriptor: rxd */
        rxm->data_off = RTE_PKTMBUF_HEADROOM;
        rte_prefetch0(RTE_PTR_ADD(rxm->buf_addr, RTE_PKTMBUF_HEADROOM));
        rxm->nb_segs = 1;
        rxm->next = NULL;
        rxm->pkt_len = rx_packet_len;
        rxm->data_len = rx_packet_len;
        rxm->port = rxq->port_id;
        rxm->packet_type = ptype_tbl[ICE_RX_FLEX_DESC_PTYPE_M &
            rte_le_to_cpu_16(rxd.wb.ptype_flex_flags0)];
        ice_rxd_to_vlan_tci(rxm, &rxd);
        rxq->rxd_to_pkt_fields(rxq, rxm, &rxd);
        pkt_flags = ice_rxd_error_to_pkt_flags(rx_stat_err0);
        rxm->ol_flags |= pkt_flags;
        /* copy old mbuf to rx_pkts */
        rx_pkts[nb_rx++] = rxm;
    }
    rxq->rx_tail = rx_id;
    /**
     * If the number of free RX descriptors is greater than the RX free
     * threshold of the queue, advance the receive tail register of queue.
     * Update that register with the value of the last processed RX
     * descriptor minus 1.
     */
    nb_hold = (uint16_t)(nb_hold + rxq->nb_rx_hold);
    if (nb_hold > rxq->rx_free_thresh) {
        rx_id = (uint16_t)(rx_id == 0 ?
                   (rxq->nb_rx_desc - 1) : (rx_id - 1));
        /* write TAIL register */
        ICE_PCI_REG_WC_WRITE(rxq->qrx_tail, rx_id);
        nb_hold = 0;
    }
    rxq->nb_rx_hold = nb_hold;

    /* return received packet in the burst */
    return nb_rx;
}

3、参考资料

[1] https://stackoverflow.com/questions/47450231/what-is-the-relationship-of-dma-ring-buffer-and-tx-rx-ring-for-a-network-card

[2] 《深入浅出DPDK》

[3] 《LINUX设备驱动程序》

[4] LINUX网络子系统中DMA机制的实现 | HeapDump性能社区

原文连接:https://zhuanlan.zhihu.com/p/586609849   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/104748.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle和其他数据库有什么区别?从引号开始了解!

无论测试或者开发&#xff0c;对数据库的增删改查都是家常便饭。但有些小知识是经常被忽略&#xff0c;却又不能不去了解的&#xff0c;例如单引号和双引号的用法和区别&#xff0c;看完这一篇&#xff0c;你肯定会有收获。 首先我们要区别一个概念&#xff0c;即单引号(‘)和…

【Java语言】— Java基础02

1.数据类型 &#xff08;1&#xff09;数据类型的作用 数据类型就是约束变量存储数据的形式。 数据类型 变量名称初始值;&#xff08;2&#xff09;数据类型的分类 引用数据类型&#xff08;除基本数据类型之外的&#xff0c;如String&#xff09;基本数据类型:4大类8种。 …

如何从 0 开始学 Python 自动化测试开发(一)

本文是「如何从 0 开始学 Python 自动化测试开发」专题系列文章第一篇&#xff0c;适合零基础入门的同学。 作者方程老师&#xff0c;是前某跨国通信公司高级测试经理&#xff0c;目前为某互联网名企资深测试技术专家&#xff0c;也是霍格沃兹测试学院特邀讲师。有十余年大型电…

项目管理软件怎么选?只需要关注4点

项目管理有许多不同的风格&#xff0c;但无论如何管理项目&#xff0c;根据企业的当前需求和未来发展轨迹选择合适的项目管理软件都很重要。 虽然大多数优秀的项目管理软件都提供相似的功能&#xff0c;但没有两个平台是完全相同的。以下是企业在选择项目管理软件时应该考虑的…

【虹科新闻】虹科与SOSLAB正式建立合作伙伴关系

近日&#xff0c;虹科与SOSLAB正式建立合作伙伴关系&#xff0c;虹科将共同与SOSLAB开展亚太地区市场开发&#xff0c;聚焦于工业领域客户开拓&#xff0c;深入本地技术支持、测试与售后服务落地&#xff0c;为客户提供高效、可靠的激光雷达解决方案。 “虹科很高兴与SOSLAB合作…

锁等待超时

问题背景 今天测试同事发现项目里面大部分接口报错&#xff0c;把日志捞出来看了下出现大量的锁等待超时的错误。 Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transactionat sun.reflect.N…

MULLS: Versatile LiDAR SLAM via Multi-metric Linear Least Square论文阅读

1. 摘要 随着自动驾驶与移动建图的快速发展&#xff0c;实际项目中对现成的激光SLAM建图方案的需求也越来越强烈&#xff0c;并且要求解决方案适用于各种不同规格的激光雷达与各种复杂场景。因此&#xff0c;我们提出了MULLS&#xff0c;一种高效&#xff0c;低漂移&#xff0…

状态观测控制器设计与仿真验证

【无限嚣张&#xff08;菜菜&#xff09;】&#xff1a;hello您好&#xff0c;我是菜菜&#xff0c;很高兴您能来访我的博客&#xff0c;我是一名爱好编程学习研究的菜菜&#xff0c;每天分享自己的学习&#xff0c;想法&#xff0c;博客来源与自己的学习项目以及编程中遇到问题…

深度学习炼丹-数据处理和增强

前言一&#xff0c;Normalization 概述 1.1&#xff0c;Normalization 定义1.2&#xff0c;什么情况需要 Normalization1.3&#xff0c;Data Normalization 方法1.4&#xff0c;示例代码 二&#xff0c;normalize images 2.1&#xff0c;图像 normalization 定义2.2&#xff0c…

[XCTF]halo(2019护网杯)(难度2)

目录 前言 一、题目重述 二、解题思路 1.Base64解密 2.难以想到的异或运算 三、flag 总结 前言 注意&#xff01;攻防世界题目有误&#xff01;&#xff01;给出题目与原题不一样但是答案却和护网杯原题答案一样&#xff01;&#xff01; 一、题目重述 aWdxNDs0NDFSOz…

NVM Express Base Specification 2.0c - 2 Theory of Operation

The interface has the following key attributes: 在命令提交或完成路径中不需要非缓存/ MMIO寄存器读取;在命令提交路径中最多需要一个MMIO寄存器写或一个64B消息;支持多达65,535个I/O队列&#xff0c;每个I/O队列支持多达65,535个未完成的命令;优先级与每个I/O队列相关联&a…

【MySQL】基于InnoDB的数据库索引

文章目录前言1、索引引入2、索引语法2.1、创建索引2.2、查看索引2.3、删除索引2.4、案例引入3、索引结构3.1、概述3.2、引擎支持3.3、BTree3.4、Hash4、索引类别4.1、分类4.2、过程分析5、性能分析5.1、执行频率5.2、慢查询日志5.3、explain6、最左前缀原则7、索引失效7.1、范围…

2023年学一门IT技术的最佳选择就是软件测试!

互联网行业的不断发展&#xff0c;也增加了IT行业的就业机会&#xff0c;作为最适合零基础小白入行、门槛低的软件测试岗位来说&#xff0c;也受到越来越多转行者的关注&#xff0c;但是耳边依然充斥着各种关于这个行业不好的言论&#xff0c;诸如“行业饱和了&#xff0c;学完…

华为OD机试真题 Python 实现【最长连续方波信号】

目录 题目 思路 考点 Code 题目 输入一串方波信号,求取最长的完全连续交替方波信号,并将其输出,如果有相同长度的交替方波信号,输出任一即可, 方波信号高位用1标识,低位用0标识,如图: 说明: 1) 一个完整的信号一定以0开始然后以0结尾,即010是一个完整信号,但10…

【自动化】【autojs】02 autox环境搭建和踩坑

▒ 目录 ▒&#x1f6eb; 导读需求开发环境1️⃣ 环境搭建AutoXadbVSCode插件scrcpy2️⃣ ADB方式实战控制微信启动正确设置autox选项启动服务执行代码停止代码执行3️⃣ 踩坑函数id需要传递全名称app.launch未生效ui.layout报错&#x1f4d6; 参考资料&#x1f6eb; 导读 需求…

基于区块链技术的信息服务新架构探讨

【摘 要】为探索区块链技术对信息通信基础架构的影响及实现,在梳理信息技术架构和信息服务架构演进的基础上,分析了区块链新型数字化分布式账本体系赋予数字世界生产关系升级的功能,并基于“云-管-端-边”信息服务架构研究了区块链技术实现信息物理空间中“设备民主”的具体…

EL表达式与JSTL标签库(JSP标准标签库)

EL表达式 EL 全名为Expression Language&#xff0c;是表达式语言。 EL表达式主要是代替jsp页面中的表达式脚本在jsp页面中进行数据的输出。因为EL表达式在输出数据的时候&#xff0c;要比jsp的表达式脚本要简洁很多。 不需要加任何jar包。不依赖任何其它库。提供了在脚本元素…

《软件开发本质论》笔记——了解价值,然后从可能去做的所有事情中选择那些最重要的去做

目录 一、传统的软件项目分阶段进行 二、根据”挑战性的目标“制订计划&#xff0c;危害性很大 三、推荐随时发现缺陷随时修复 四、 价值是什么 一、传统的软件项目分阶段进行 潜在风险&#xff1a;试图去计划并实现所有的功能特性&#xff0c;这使我们处于不利的境地。我们…

转行互联网,零基础应届生应该选择什么样的岗位作为切入点?

对于想要转行互联网的零基础小白&#xff0c;如果你耐心看完了这个答案&#xff0c;恭喜你已经找到了转行互联网的方法&#xff01;我不说空的理论&#xff0c;也不讲心灵鸡汤&#xff0c;作为文艺青年&#xff0c;我只说经验&#xff0c;我是工作十三年的互联网老兵&#xff0…

整合第三方登录之微信扫码登录

&#x1f4c2;文章目录&#x1f393;前言&#x1f3f7;️引入相关依赖&#x1f9f1;操作步骤&#x1f4d4;生成微信登录二维码&#x1f4d4;获取微信用户信息并实现微信注册登录&#x1f497;总结&#x1f393;前言 前置条件&#xff1a;具备微信开发者资质。 通过微信开放平…