DPDK收发包全景分析

本文深入剖析DPDK收发包的过程,包括配置和初始化收发队列、数据包的获取和发送。文章详细介绍了如何实现零拷贝,以及如何建立mempool、queue、DMA和ring之间的关系,确保高效的数据传输。此外,还探讨了设备启动时的链路设置、中断处理和硬件重启等关键步骤。
摘要由CSDN通过智能技术生成

前言:DPDK收发包是基础核心模块,从网卡收到包到驱动把包拷贝到系统内存中,再到系统对这块数据包的内存管理,由于在处理过程中实现了零拷贝,数据包从接收到发送始终只有一份,对这个报文的管理在前面的mempool内存池中有过介绍。这篇主要介绍收发包的过程。

一、收发包分解

收发包过程大致可以分为2个部分

  • 1.收发包的配置和初始化,主要是配置收发队列等。
  • 2.数据包的获取和发送,主要是从队列中获取到数据包或者把数据包放到队列中。

二、收发包的配置和初始化

收发包的配置

收发包的配置最主要的工作就是配置网卡的收发队列,设置DMA拷贝数据包的地址等,配置好地址后,网卡收到数据包后会通过DMA控制器直接把数据包拷贝到指定的内存地址。我们使用数据包时,只要去对应队列取出指定地址的数据即可。

收发包的配置是从rte_eth_dev_configure()开始的,这里根据参数会配置队列的个数,以及接口的配置信息,如队列的使用模式,多队列的方式等。

前面会先进行一些各项检查,如果设备已经启动,就得先停下来才能配置(这时应该叫再配置吧)。然后把传进去的配置参数拷贝到设备的数据区。

memcpy(&dev->data->dev_conf, dev_conf, sizeof(dev->data->dev_conf));

之后获取设备的信息,主要也是为了后面的检查使用:

(*dev->dev_ops->dev_infos_get)(dev, &dev_info);

这里的dev_infos_get是在驱动初始化过程中设备初始化时配置的(eth_ixgbe_dev_init())

eth_dev->dev_ops = &ixgbe_eth_dev_ops;

重要的信息检查过后,下面就是对发送和接收队列进行配置
先看接收队列的配置,接收队列是从rte_eth_dev_tx_queue_config()开始的
在接收配置中,考虑的是有两种情况,一种是第一次配置;另一种是重新配置。所以,代码中都做了区分。
(1)如果是第一次配置,那么就为每个队列分配一个指针。
(2)如果是重新配置,配置的queue数量不为0,那么就取消之前的配置,重新配置。
(3)如果是重新配置,但要求的queue为0,那么释放已有的配置。

发送的配置也是同样的,在rte_eth_dev_tx_queue_config()

当收发队列配置完成后,就调用设备的配置函数,进行最后的配置。(*dev->dev_ops->dev_configure)(dev),我们找到对应的配置函数,进入ixgbe_dev_configure()来分析其过程,其实这个函数并没有做太多的事。

在函数中,先调用了ixgbe_check_mq_mode()来检查队列的模式。然后设置允许接收批量和向量的模式

adapter->rx_bulk_alloc_allowed = true;
adapter->rx_vec_allowed = true;

接下来就是收发队列的初始化,非常关键的一部分内容,这部分内容按照收发分别介绍:

接收队列的初始化

接收队列的初始化是从rte_eth_rx_queue_setup()开始的,这里的参数需要指定要初始化的port_id,queue_id,以及描述符的个数,还可以指定接收的配置,如释放和回写的阈值等。

依然如其他函数的套路一样,先进行各种检查,如初始化的队列号是否合法有效,设备如果已经启动,就不能继续初始化了。检查函数指针是否有效等。检查mbuf的数据大小是否满足默认的设备信息里的配置。

rte_eth_dev_info_get(port_id, &dev_info);

这里获取了设备的配置信息,如果调用初始化函数时没有指定rx_conf配置,就会设备配置信息里的默认值

dev_info->default_rxconf = (struct rte_eth_rxconf) {
        .rx_thresh = {
            .pthresh = IXGBE_DEFAULT_RX_PTHRESH,
            .hthresh = IXGBE_DEFAULT_RX_HTHRESH,
            .wthresh = IXGBE_DEFAULT_RX_WTHRESH,
        },
        .rx_free_thresh = IXGBE_DEFAULT_RX_FREE_THRESH,
        .rx_drop_en = 0,
    };

还检查了要初始化的队列号对应的队列指针是否为空,如果不为空,则说明这个队列已经初始化过了,就释放这个队列。

rxq = dev->data->rx_queues;
    if (rxq[rx_queue_id]) {
        RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->rx_queue_release,
                    -ENOTSUP);
        (*dev->dev_ops->rx_queue_release)(rxq[rx_queue_id]);
        rxq[rx_queue_id] = NULL;
    }

最后,调用到队列的setup函数做最后的初始化。

ret = (*dev->dev_ops->rx_queue_setup)(dev, rx_queue_id, nb_rx_desc,
                          socket_id, rx_conf, mp);

对于ixgbe设备,rx_queue_setup就是函数ixgbe_dev_rx_queue_setup(),这里就是队列最终的初始化咯

依然是先检查,检查描述符的数量最大不能大于IXGBE_MAX_RING_DESC个,最小不能小于IXGBE_MIN_RING_DESC个。

接下来的都是重点咯:
<1>.分配队列结构体,并填充结构

rxq = rte_zmalloc_socket("ethdev RX queue", sizeof(struct ixgbe_rx_queue),
                 RTE_CACHE_LINE_SIZE, socket_id);

填充结构体的所属内存池,描述符个数,队列号,队列所属接口号等成员。
<2>.分配描述符队列的空间,按照最大的描述符个数进行分配

rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,
                      RX_RING_SZ, IXGBE_ALIGN, socket_id);

接着获取描述符队列的头和尾寄存器的地址,在收发包后,软件要对这个寄存器进行处理。

rxq->rdt_reg_addr =
            IXGBE_PCI_REG_ADDR(hw, IXGBE_RDT(rxq->reg_idx));
        rxq->rdh_reg_addr =
            IXGBE_PCI_REG_ADDR(hw, IXGBE_RDH(rxq->reg_idx));

设置队列的接收描述符ring的物理地址和虚拟地址。

rxq->rx_ring_phys_addr = rte_mem_phy2mch(rz->memseg_id, rz->phys_addr);
    rxq->rx_ring = (union ixgbe_adv_rx_desc *) rz->addr;

<3>分配sw_ring,这个ring中存储的对象是struct ixgbe_rx_entry,其实里面就是数据包mbuf的指针。

rxq->sw_ring = rte_zmalloc_socket("
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值