DPDK 收发包流程

2021-08-01  本文已影响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 已经超过时,执行批量释放。

上一篇下一篇

猜你喜欢

热点阅读