美文网首页
DPDK 收发包流程

DPDK 收发包流程

作者: 分享放大价值 | 来源:发表于2021-08-01 10:25 被阅读0次

    本文整理下之前的学习笔记,基于DPDK17.11版本源码,主要分析一下收发包流程。

    使用DPDK的APP收发报文流程如下

    main
        //环境抽象层初始化,比如网卡,cpu,内存等
        rte_eal_init(argc, argv);
    
        //为rx和tx队列分配内存,将用户指定的配置信息dev_conf保存到dev
        rte_eth_dev_configure(portid, 1, 1, &port_conf);
        
        //分配网卡接收队列结构体,接收ring硬件描述符和软件ring等内存
        rte_eth_rx_queue_setup(portid, 0, nb_rxd,
                             rte_eth_dev_socket_id(portid),
                             NULL,
                             l2fwd_pktmbuf_pool);
    
        //分配网卡发送队列结构体,发送ring硬件描述符等内存
        rte_eth_tx_queue_setup(portid, 0, nb_txd,
                    rte_eth_dev_socket_id(portid),
                    NULL);
    
        //启动网卡,设置网卡寄存器,将网卡和系统内存关联起来
        rte_eth_dev_start(portid);
    
        while (1) {
            //接收报文
            rte_eth_rx_burst(portid, 0, pkts_burst, MAX_PKT_BURST);
    
            //处理报文
    
            //发送报文,此函数只是将报文放到一个buffer中,满32个后才调用rte_eth_tx_burst真正发送
            rte_eth_tx_buffer(dst_port, 0, buffer, m);
        }
    

    以ixgbe驱动为例,相关的数据结构如下


    image.png

    收包流程

    我们都知道网卡会通过DMA将报文放在系统内存中,那网卡如何知道应该放在哪里呢?如何将网卡和系统内存关联起来?这需要用到网卡的几个寄存器:

    RDBAL(Receive Descriptor Base Address Low),
    RDBAH(Receive Descriptor Base Address High)
    RDLEN(Receive Descriptor Length)
    

    驱动初始化时会分配一块内存,将这块内存的起始物理地址(64位)写到寄存器RDBAL(保存物理地址的低32位)和RDBAH(保存物理地址的高32位),然后将这块内存的大小写到寄存器RDLEN中。这块内存称为硬件描述符,大小为接收队列硬件描述符个数乘接收队列硬件描述符大小。

    一个接收队列硬件描述符大小为16字节,有两种格式: 读格式和回写格式。
    读格式是从网卡角度来说的,由驱动将mbuf的物理地址写到packet buffer address字段,网卡读取此字段获取内存物理地址,收到的报文就可以存到此内存。


    image.png

    回写格式也是从网卡角度来说,网卡将报文写到指定的内存后,就会以下面的格式将报文的相关信息回写到描述符中,最后设置DD位(第二个8字节的最低位),驱动通过判断DD位是否为1来接收报文。


    image.png

    总结一下接收队列硬件描述符就是一块内存,网卡先以读格式获取内存的物理地址,将报文写到内存后,就以回写格式将报文额外信息写到描述符中,驱动可以以回写格式读取描述符,获取报文的长度,类型等信息。

    网卡和内存关联起来后,就可以收取报文了,此时又用到两个寄存器: RDH(Receive Descriptor Head)和RDT(Receive Descriptor Tail)。
    RDH为头指针,指向第一个可用描述符,网卡收取报文并回写成功后,由网卡来移动RDH到下一个可用描述符。
    RDT为尾指针,指向最后一个可用描述符,RDH和RDT之间的描述符为网卡可用描述符,RDT由驱动来移动,驱动从第一个描述符开始,轮询DD位是否为1,为1就认为此描述符对应的mbuf有报文,此时会申请新的mbuf,将新mbuf物理地址写到此描述符的pkt_addr,并将DD位置0,这样的话此描述符就又可用被网卡使用了,同时将老的有报文的mbuf返回给用户。描述符再次可用后,驱动就可以更新RDT指向此描述符,为了性能考虑不会每次都会更新RDT,而是等可用描述符超过一定阈值(rx_free_thresh)才更新一次。


    image.png

    如下为接收描述符的格式,是union类型,可同时有读和回写两种格式。

    /* Receive Descriptor - Advanced */
    union ixgbe_adv_rx_desc {
        struct {
            __le64 pkt_addr; /* Packet buffer address */
            __le64 hdr_addr; /* Header buffer address */
        } read;
        struct {
            struct {
                union {
                    __le32 data;
                    struct {
                        __le16 pkt_info; /* RSS, Pkt type */
                        __le16 hdr_info; /* Splithdr, hdrlen */
                    } hs_rss;
                } lo_dword;
                union {
                    __le32 rss; /* RSS Hash */
                    struct {
                        __le16 ip_id; /* IP id */
                        __le16 csum; /* Packet Checksum */
                    } csum_ip;
                } hi_dword;
            } lower;
            struct {
                __le32 status_error; /* ext status/error */
                __le16 length; /* Packet length */
                __le16 vlan; /* VLAN tag */
            } upper;
        } wb;  /* writeback */
    };
    

    了解网卡接收原理后,下面从代码角度看一下实现,大概分为如下几步:
    a. 分配接收队列硬件描述符rx_ring,分配软件ring sw_ring
    b. 将接收队列硬件描述符的物理地址和长度写到寄存器
    c. 分配mbuf,将mbuf接收报文的物理地址赋给接收队列硬件描述符 rx_ring->pkt_addr,虚拟地址赋给 sw_ring
    d. 设置头尾寄存器,头指针寄存器RDH为0,指向第一个可用描述符,尾指针寄存器RDT指向最后一个可用描述符

    a. rte_eth_rx_queue_setup
    接收队列设置

    1. 分配队列结构体 struct ixgbe_rx_queue
    2. 分配接收ring硬件描述符(一般为4096),每个描述符16字节,保存到 rxq->rx_ring
    3. 分配软件ring,用来保存mbuf,保存到 rxq->sw_ring

    rte_eth_rx_queue_setup -> ixgbe_dev_rx_queue_setup

    int __attribute__((cold))
    ixgbe_dev_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)
        const struct rte_memzone *rz;
        struct ixgbe_rx_queue *rxq;
        struct ixgbe_hw     *hw;
        uint16_t len;
        struct ixgbe_adapter *adapter = (struct ixgbe_adapter *)dev->data->dev_private;
        hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
        
        /* First allocate the rx queue data structure */
        rxq = rte_zmalloc_socket("ethdev RX queue", sizeof(struct ixgbe_rx_queue),
                     RTE_CACHE_LINE_SIZE, socket_id);
        rxq->mb_pool = mp;
        rxq->nb_rx_desc = nb_desc;
        rxq->rx_free_thresh = rx_conf->rx_free_thresh;
        rxq->queue_id = queue_idx;
        rxq->reg_idx = (uint16_t)((RTE_ETH_DEV_SRIOV(dev).active == 0) ?
            queue_idx : RTE_ETH_DEV_SRIOV(dev).def_pool_q_idx + queue_idx);
        rxq->port_id = dev->data->port_id;
        rxq->crc_len = (uint8_t) ((dev->data->dev_conf.rxmode.hw_strip_crc) ? 0 : ETHER_CRC_LEN);
        rxq->drop_en = rx_conf->rx_drop_en;
        rxq->rx_deferred_start = rx_conf->rx_deferred_start;
    
        #define IXGBE_MAX_RING_DESC           4096 /* replicate define from rxtx */
        #define RTE_PMD_IXGBE_RX_MAX_BURST 32
        #define RX_RING_SZ ((IXGBE_MAX_RING_DESC + RTE_PMD_IXGBE_RX_MAX_BURST) * \
                sizeof(union ixgbe_adv_rx_desc))
        /*
         * Allocate RX ring hardware descriptors. A memzone large enough to
         * handle the maximum ring size is allocated in order to allow for
         * resizing in later calls to the queue setup function.
         */
        //分配接收队列硬件描述符内存,注意这里是按最大值分配。
        //注意要128字节对齐,因为82599网卡芯片手册规则物理地址必须是128字节对齐
        rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,
                          RX_RING_SZ, IXGBE_ALIGN, socket_id);
        /*
         * Zero init all the descriptors in the ring.
         */
        memset(rz->addr, 0, RX_RING_SZ);
        
        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));
    
        //保存接收队列硬件描述符的物理地址
        rxq->rx_ring_phys_addr = rz->iova;
        //保存接收队列硬件描述符的虚拟地址
        rxq->rx_ring = (union ixgbe_adv_rx_desc *) rz->addr;
    
        /*
         * Allocate software ring. Allow for space at the end of the
         * S/W ring to make sure look-ahead logic in bulk alloc Rx burst
         * function does not access an invalid memory region.
         */
        len = nb_desc;
        if (adapter->rx_bulk_alloc_allowed)
            len += RTE_PMD_IXGBE_RX_MAX_BURST;
    
        //分配软件ring内存,这里的大小为参数指定的描述符个数 nb_desc
        rxq->sw_ring = rte_zmalloc_socket("rxq->sw_ring",
                          sizeof(struct ixgbe_rx_entry) * len,
                          RTE_CACHE_LINE_SIZE, socket_id);
        
        //将接收队列结构保存到对应位置
        dev->data->rx_queues[queue_idx] = rxq;
    

    b. ixgbe_dev_rx_init
    将接收队列硬件描述符的物理地址写到网卡寄存器RDBAL和RDBAH,将接收队列硬件描述符的长度写到网卡寄存器RDLEN。
    rte_eth_dev_start -> ixgbe_dev_start -> ixgbe_dev_rx_init

    接收队列初始化
    /*
     * Initializes Receive Unit.
     */
    int __attribute__((cold))
    ixgbe_dev_rx_init(struct rte_eth_dev *dev)
    {
        struct ixgbe_hw     *hw;
        struct ixgbe_rx_queue *rxq;
        uint64_t bus_addr;
        uint32_t rxctrl;
        uint32_t fctrl;
        uint32_t hlreg0;
        uint16_t i;
        struct rte_eth_rxmode *rx_conf = &dev->data->dev_conf.rxmode;
        int rc;
    
        hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
    
        /*
         * Make sure receives are disabled while setting
         * up the RX context (registers, descriptor rings, etc.).
         */
        //确保网卡的接收功能是关闭的
        rxctrl = IXGBE_READ_REG(hw, IXGBE_RXCTRL);
        IXGBE_WRITE_REG(hw, IXGBE_RXCTRL, rxctrl & ~IXGBE_RXCTRL_RXEN);
    
        //使能接收广播,丢弃pause报文
        /* Enable receipt of broadcasted frames */
        fctrl = IXGBE_READ_REG(hw, IXGBE_FCTRL);
        fctrl |= IXGBE_FCTRL_BAM; /* Broadcast Accept Mode */
        fctrl |= IXGBE_FCTRL_DPF; /* Discard Pause Frame */
        fctrl |= IXGBE_FCTRL_PMCF; /* Pass MAC Control Frames */
        IXGBE_WRITE_REG(hw, IXGBE_FCTRL, fctrl);
    
        /*
         * Configure CRC stripping, if any.
         */
        //设置硬件自动去掉crc
        hlreg0 = IXGBE_READ_REG(hw, IXGBE_HLREG0);
        if (rx_conf->hw_strip_crc)
            hlreg0 |= IXGBE_HLREG0_RXCRCSTRP;
        else
            hlreg0 &= ~IXGBE_HLREG0_RXCRCSTRP;
    
        /*
         * Configure jumbo frame support, if any.
         */
        //使能接收巨帧
        if (rx_conf->jumbo_frame == 1) {
            hlreg0 |= IXGBE_HLREG0_JUMBOEN;
            maxfrs = IXGBE_READ_REG(hw, IXGBE_MAXFRS);
            maxfrs &= 0x0000FFFF;
            maxfrs |= (rx_conf->max_rx_pkt_len << 16);
            IXGBE_WRITE_REG(hw, IXGBE_MAXFRS, maxfrs);
        } else
            hlreg0 &= ~IXGBE_HLREG0_JUMBOEN;
    
        IXGBE_WRITE_REG(hw, IXGBE_HLREG0, hlreg0);
    
        /* Setup RX queues */
        for (i = 0; i < dev->data->nb_rx_queues; i++) {
            rxq = dev->data->rx_queues[i];
    
            //将接收队列硬件描述符的物理地址写到网卡接收描述符寄存器中
            /* Setup the Base and Length of the Rx Descriptor Rings */
            bus_addr = rxq->rx_ring_phys_addr;
            IXGBE_WRITE_REG(hw, IXGBE_RDBAL(rxq->reg_idx), (uint32_t)(bus_addr & 0x00000000ffffffffULL));
            IXGBE_WRITE_REG(hw, IXGBE_RDBAH(rxq->reg_idx), (uint32_t)(bus_addr >> 32));
            
            //将用户请求的nb_tx_desc个数的接收队列硬件描述符长度写到寄存器
            IXGBE_WRITE_REG(hw, IXGBE_RDLEN(rxq->reg_idx), rxq->nb_rx_desc * sizeof(union ixgbe_adv_rx_desc));
            
            //头尾指针先设置为0
            IXGBE_WRITE_REG(hw, IXGBE_RDH(rxq->reg_idx), 0);
            IXGBE_WRITE_REG(hw, IXGBE_RDT(rxq->reg_idx), 0);
        }
    
        //根据设置选择不同的接收函数,后面会以 ixgbe_recv_pkts 为例说明
        ixgbe_set_rx_function(dev);
    
        ...
    
        return 0;
    }
    

    c. ixgbe_dev_rx_queue_start
    申请mbuf,将mbuf存放报文的物理地址设置到接收队列硬件描述符的pkt_addr字段,这样网卡就知道收到报文后将报文放在哪里了。
    rte_eth_dev_start -> ixgbe_dev_start -> ixgbe_dev_rxtx_start -> ixgbe_dev_rx_queue_start

    /*
     * Start Receive Units for specified queue.
     */
    int __attribute__((cold))
    ixgbe_dev_rx_queue_start(struct rte_eth_dev *dev, uint16_t rx_queue_id)
    {
        struct ixgbe_hw     *hw;
        struct ixgbe_rx_queue *rxq;
        uint32_t rxdctl;
        int poll_ms;
    
        hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
    
        if (rx_queue_id < dev->data->nb_rx_queues) {
            rxq = dev->data->rx_queues[rx_queue_id];
    
            //分配mbuf,填充到 rxq->sw_ring 中
            /* Allocate buffers for descriptor rings */
            if (ixgbe_alloc_rx_queue_mbufs(rxq) != 0) {
                PMD_INIT_LOG(ERR, "Could not alloc mbuf for queue:%d",
                         rx_queue_id);
                return -1;
            }
            
            ...
            
            //头指针为0,指向第一个可用描述符
            IXGBE_WRITE_REG(hw, IXGBE_RDH(rxq->reg_idx), 0);
            //尾指针为最大描述符,指向最后一个可用描述符
            IXGBE_WRITE_REG(hw, IXGBE_RDT(rxq->reg_idx), rxq->nb_rx_desc - 1);
            dev->data->rx_queue_state[rx_queue_id] = RTE_ETH_QUEUE_STATE_STARTED;
        }
    
        return 0;
    }
    
    static int __attribute__((cold))
    ixgbe_alloc_rx_queue_mbufs(struct ixgbe_rx_queue *rxq)
    {
        struct ixgbe_rx_entry *rxe = rxq->sw_ring;
        uint64_t dma_addr;
        unsigned int i;
    
        /* Initialize software ring entries */
        for (i = 0; i < rxq->nb_rx_desc; i++) {
            volatile union ixgbe_adv_rx_desc *rxd;
            //分配mbuf
            struct rte_mbuf *mbuf = rte_mbuf_raw_alloc(rxq->mb_pool);
    
            mbuf->data_off = RTE_PKTMBUF_HEADROOM;
            mbuf->port = rxq->port_id;
    
            //获取mbuf存放报文的物理地址,注意不是mbuf的首地址
            dma_addr =
                rte_cpu_to_le_64(rte_mbuf_data_iova_default(mbuf));
            
            rxd = &rxq->rx_ring[i];
            //清空接收描述符的DD位
            rxd->read.hdr_addr = 0;
            //将mbuf接收报文的物理地址赋给描述符
            rxd->read.pkt_addr = dma_addr;
            rxe[i].mbuf = mbuf;
        }
    
        return 0;
    }
    

    最后使能网卡的接收功能 hw->mac.ops.enable_rx_dma(hw, rxctrl);

    下面是正式收包流程,还以ixgbe驱动为例 rte_eth_rx_burst -> ixgbe_recv_pkts

    uint16_t
    ixgbe_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
        struct ixgbe_rx_queue *rxq;
        volatile union ixgbe_adv_rx_desc *rx_ring;
        volatile union ixgbe_adv_rx_desc *rxdp;
        struct ixgbe_rx_entry *sw_ring;
        struct ixgbe_rx_entry *rxe;
        struct rte_mbuf *rxm;
        struct rte_mbuf *nmb;
        union ixgbe_adv_rx_desc rxd;
        uint64_t dma_addr;
        uint32_t staterr;
        uint32_t pkt_info;
        uint16_t pkt_len;
        uint16_t rx_id;
        uint16_t nb_rx;
        uint16_t nb_hold;
        uint64_t pkt_flags;
        uint64_t vlan_flags;
    
        nb_rx = 0;
        nb_hold = 0;
        rxq = rx_queue;
        rx_id = rxq->rx_tail;
        rx_ring = rxq->rx_ring;
        sw_ring = rxq->sw_ring;
        vlan_flags = rxq->vlan_flags;
        while (nb_rx < nb_pkts) {
            /*
             * The order of operations here is important as the DD status
             * bit must not be read after any other descriptor fields.
             * rx_ring and rxdp are pointing to volatile data so the order
             * of accesses cannot be reordered by the compiler. If they were
             * not volatile, they could be reordered which could lead to
             * using invalid descriptor fields when read from rxd.
             */
            //获取硬件描述符
            rxdp = &rx_ring[rx_id];
            //获取硬件描述符的 status_error
            staterr = rxdp->wb.upper.status_error;
            //判断DD位是否被硬件置1,为1说明有报文,不是1就break
            if (!(staterr & rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD)))
                break;
            rxd = *rxdp;
            
            //分配一个新的mbuf
            nmb = rte_mbuf_raw_alloc(rxq->mb_pool);
    
            nb_hold++;
            //获取软件ring的当前元素
            rxe = &sw_ring[rx_id];
            //尾指针加1
            rx_id++;
            //如果达到最大值,则翻转为0,相当于环形效果
            if (rx_id == rxq->nb_rx_desc)
                rx_id = 0;
    
            //从rxe->mbuf取出mbuf地址,此mbuf已经有报文内容
            rxm = rxe->mbuf;
            //rxe->mbuf被赋予一个新的mbuf
            rxe->mbuf = nmb;
            //获取新mbuf的物理地址
            dma_addr =
                rte_cpu_to_le_64(rte_mbuf_data_iova_default(nmb));
            //hdr_addr清0,就会将DD位也清0,否则下次循环到此描述符就会错误的认为有报文
            rxdp->read.hdr_addr = 0;
            //将mbuf的物理地址赋给描述符,网卡就可以把新报文写到新mbuf中
            rxdp->read.pkt_addr = dma_addr;
    
            //从描述符的wb字段获取报文相关的信息,包括长度,vlanid等,并填到mbuf中
            pkt_len = (uint16_t) (rte_le_to_cpu_16(rxd.wb.upper.length) - rxq->crc_len);
            rxm->data_off = RTE_PKTMBUF_HEADROOM;
            rte_packet_prefetch((char *)rxm->buf_addr + rxm->data_off);
            rxm->nb_segs = 1;
            rxm->next = NULL;
            rxm->pkt_len = pkt_len;
            rxm->data_len = pkt_len;
            rxm->port = rxq->port_id;
    
            pkt_info = rte_le_to_cpu_32(rxd.wb.lower.lo_dword.data);
            /* Only valid if PKT_RX_VLAN set in pkt_flags */
            rxm->vlan_tci = rte_le_to_cpu_16(rxd.wb.upper.vlan);
    
            ...
    
            /*
             * Store the mbuf address into the next entry of the array
             * of returned packets.
             */
            //将已经有报文的mbuf返回给调用者
            rx_pkts[nb_rx++] = rxm;
        }
        
        //更新尾指针
        rxq->rx_tail = rx_id;
        
        //nb_hold表示本次调用成功读取的报文个数,也同时意味着本次调用重新可用mbuf的个数,
        //因为读取一次报文,就会分配新的mbuf,并赋给描述符,这个描述符就可以被网卡再次使用。
        //rxq->nb_rx_hold是累计可用的描述符个数。
        nb_hold = (uint16_t) (nb_hold + rxq->nb_rx_hold);
        //如果累计的可用描述符个数超过了阈值,就要更新网卡能看到的描述符尾指针了。
        //如果不更新尾指针,随着收包头指针一直增加,和尾指针重合时,就没有可用描述符了。
        if (nb_hold > rxq->rx_free_thresh) {
            PMD_RX_LOG(DEBUG, "port_id=%u queue_id=%u rx_tail=%u "
                   "nb_hold=%u nb_rx=%u",
                   (unsigned) rxq->port_id, (unsigned) rxq->queue_id,
                   (unsigned) rx_id, (unsigned) nb_hold,
                   (unsigned) nb_rx);
            rx_id = (uint16_t) ((rx_id == 0) ?
                         (rxq->nb_rx_desc - 1) : (rx_id - 1));
            IXGBE_PCI_REG_WRITE(rxq->rdt_reg_addr, rx_id);
            //清空计数
            nb_hold = 0;
        }
        //更新nb_rx_hold
        rxq->nb_rx_hold = nb_hold;
    
        return nb_rx;
    

    发包流程

    发送报文时也需要将网卡和内存关联起来,即将要发送的报文地址告诉网卡,这也是通过硬件描述符来实现的。
    发送队列硬件描述符格式如下,也分为读和回写两种格式,都从网卡的角度来说。
    对于读格式,驱动将报文的物理地址设置到第一个8字节的address字段,网卡读取此字段就能获取发送报文的物理地址,同时驱动也会设置第二个8字节的相关字段,比如报文长度,是否是最后一个报文段,何时回写等,网卡根据这些信息正确的将报文发送出去。
    对于回写格式,只有一个字段有效,第二个8字节的第32位,此位代表DD(Descriptor Done)位,网卡完成报文发送后,并且此描述符设置了RS标志位,则会将此DD位设置为1,驱动读取此位就知道此描述符及它之前的描述符都可以被驱动使用。


    image.png

    DCMD字段中的RS(report status)位用来控制网卡何时回写DD位。注意和接收方向的区别,在接收方向网卡每收到一个报文就会回写一次接收描述符,将报文长度等信息填写到接收描述符,这是必须的,否则驱动怎么知道接收的报文多长呢,但是发送方向网卡不需要每发送一个报文就回写一次,并且每个报文回写会影响性能,驱动只关心报文是否发送成功,对应的发送描述符是否可用,可以通过参数tx_rs_thresh设置网卡多久回写一次,如果发送报文个数超过tx_rs_thresh,就会设置DCMD的RS位。

    发送方向代码流程和接收方向大体相似,不再赘述。

    总结

    在pmd中,对于接收方向(从网卡收数据)来说,初始状态head指针指向base,tail指向指向base+len。网卡是生产者,通过移动head指针将数据放在mbuf中,驱动是消费者,将接收ring中buf_addr换成新mbuf的地址,旧的mbuf可以返回给应用程序来处理。驱动通过移动tail指针,将接收描述符还给网卡,但是并没有每次收包都更新收包队列尾部索引寄存器,而是在可释放的收包描述符数量达到一个阈值(rx_free_thresh)的时候才真正更新收包队列尾部索引寄存器。设置合适的可释放描述符数量阈值,可以减少没有必要的过多的收包队列尾部索引寄存器的访问,改善收包的性能。

    对于发送方向来说,初始状态head和tail都指向base。驱动是生产者,发包时,先将发送数据的物理地址赋值给发送描述符的txd->read.buffer_addr,最后通过移动tail指针通知网卡有数据要发送。网卡是消费者,当获知tail指针移动就会发送数据,网卡发送完数据,会移动head指针。

    Q && A

    a. pmd发包时,如何通知网卡有新数据需要发送?
    更新tail指针时就会触发网卡发送数据。比如在ixgbe_xmit_pkts函数最后,都会更新tail指针: IXGBE_PCI_REG_WRITE_RELAXED(txq->tdt_reg_addr, tx_id);

    从网卡datasheet也能看到相关说明:


    image.png

    b. 网卡发送成功后,驱动怎么知道描述符可用?
    从datasheet看到,有四种方法,默认采用第三种,即通过DD标志位获取


    image.png

    c. 网卡驱动发送方向,mbuf什么时候释放?
    许多驱动程序并没有在数据包传输后立即将mbuf释放回到mempool或本地缓存中。相反,他们将mbuf留在Tx环中,当需要在Tx环中插入,或者 tx_rs_thresh 已经超过时,执行批量释放。

    相关文章

      网友评论

          本文标题:DPDK 收发包流程

          本文链接:https://www.haomeiwen.com/subject/adfimltx.html