dpdk 网卡队列初始化 + 收发包
ixgbe_dev_rx_queue_start 设置好dma地址
IXGBE_WRITE_REG(hw, IXGBE_RDH(rxq->reg_idx), 0); IXGBE_WRITE_REG(hw, IXGBE_RDT(rxq->reg_idx), rxq->nb_rx_desc - 1);
recv { rxdp = &rx_ring[rx_id]; /* 若网卡回写的DD为0,跳出循环 */ staterr = rxdp->wb.upper.status_error; if (!(staterr & rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD))) break; rxd = *rxdp; /* 分配新mbuf */ nmb = rte_mbuf_raw_alloc(rxq->mb_pool); rxe = &sw_ring[rx_id]; /* 得到旧mbuf */ rxm = rxe->mbuf; /* rxm指向旧mbuf */ rxe->mbuf = nmb; /* rxe->mbuf指向新mbuf */ dma_addr = rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(nmb)); /* 得到新mbuf的总线地址 */ rxdp->read.hdr_addr = 0; /* 清零新mbuf对应的desc的DD,后续网卡会读desc */ rxdp->read.pkt_addr = dma_addr; /* 设置新mbuf对应的desc的总线地址,后续网卡会读desc */ }
每个队列都要设置
/* Allocate and set up 1 RX queue per Ethernet port. */ for (q = 0; q < rx_rings; q++) { retval = rte_eth_rx_queue_setup(port, q, nb_rxd, rte_eth_dev_socket_id(port), NULL, mbuf_pool); if (retval < 0) return retval; }
ret = (*dev->dev_ops->rx_queue_setup)(dev, rx_queue_id, nb_rx_desc,
socket_id, &local_conf, mp);
ixgbe_dev_rx_queue_setup()
/** * Structure associated with each descriptor of the RX ring of a RX queue. */ struct ixgbe_rx_entry { struct rte_mbuf *mbuf; /**< mbuf associated with RX descriptor. */ }; struct ixgbe_tx_entry { struct rte_mbuf *mbuf; /**< mbuf associated with TX desc, if any. */ uint16_t next_id; /**< Index of next descriptor in ring. */ uint16_t last_id; /**< Index of last scattered descriptor. */ };
struct ixgbe_rx_queue { struct rte_mempool *mb_pool; /**< mbuf pool to populate RX ring. */ volatile union ixgbe_adv_rx_desc *rx_ring; /**< RX ring virtual address. */ -----------------每个queue都有一个设备描述符 uint64_t rx_ring_phys_addr; /**< RX ring DMA address. */ volatile uint32_t *rdt_reg_addr; /**< RDT register address. */ volatile uint32_t *rdh_reg_addr; /**< RDH register address. */ struct ixgbe_rx_entry *sw_ring; /**< address of RX software ring. */ }
- pkt_addr:报文数据的物理地址,网卡DMA将报文数据通过该物理地址写入对应的内存空间。
- hdr_addr:报文的头信息,hdr_addr的最后一个bit为DD位,因为是union结构,即status_error的最后一个bit也对应DD位。
DD位(Descriptor Done Status)用于标志标识一个描述符buf是否可用。
- 网卡每次来了新的数据包,就检查rx_ring当前这个buf的DD位是否为0,如果为0那么表示当前buf可以使用,就让DMA将数据包copy到这个buf中,然后设置DD为1。如果为1,那么网卡就认为rx_ring队列满了,直接会将这个包给丢弃掉,记录一次imiss。(0->1)
- 对于应用而言,DD位使用恰恰相反,在读取数据包时,先检查DD位是否为1,如果为1,表示网卡已经把数据包放到了内存中,可以读取,读取完后,再放入一个新的buf并把对应DD位设置为0。如果为0,就表示没有数据包可读。(1->0)
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) { ... /* 分配ixgbe_rx_queue */ rxq = rte_zmalloc_socket("ethdev RX queue", sizeof(struct ixgbe_rx_queue), RTE_CACHE_LINE_SIZE, socket_id); ... /* 初始化rxq */ 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; ... /* 分配desc数组,数组元素类型为union ixgbe_adv_rx_desc * (IXGBE_MAX_RING_DESC + RTE_PMD_IXGBE_RX_MAX_BURST) * sizeof(union ixgbe_adv_rx_desc) * (4096 + 32) * sizeof(union ixgbe_adv_rx_desc) */ rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx, RX_RING_SZ, IXGBE_ALIGN, socket_id); ... memset(rz->addr, 0, RX_RING_SZ); /* 清零desc数组 */ ... /* 设置rdt_reg_addr为RDT寄存器的地址 */ rxq->rdt_reg_addr = IXGBE_PCI_REG_ADDR(hw, IXGBE_RDT(rxq->reg_idx)); /* 设置rdh_reg_addr为RDH寄存器的地址 */ rxq->rdh_reg_addr = IXGBE_PCI_REG_ADDR(hw, IXGBE_RDH(rxq->reg_idx)); ... /* rx_ring_phys_addr指向desc数组的总线地址 */ rxq->rx_ring_phys_addr = rte_mem_phy2mch(rz->memseg_id, rz->phys_addr); /* rx_ring指向desc数组的虚拟地址 */ rxq->rx_ring = (union ixgbe_adv_rx_desc *) rz->addr; ... /* 分配entry数组,地址赋给sw_ring ---------------------------------------- */ rxq->sw_ring = rte_zmalloc_socket("rxq->sw_ring", sizeof(struct ixgbe_rx_entry) * len, RTE_CACHE_LINE_SIZE, socket_id); ... /* rx_queues[queue_idx]指向ixgbe_rx_queue */ dev->data->rx_queues[queue_idx] = rxq;
//dev->data->rx_queues = rte_zmalloc
... /* 设置接收队列参数 */ ixgbe_reset_rx_queue(adapter, rxq); ... }
ixgbe_recv_pkts()
接收时回写:
1、网卡使用DMA写Rx FIFO中的Frame到Rx Ring Buffer中的mbuf,设置desc的DD为1
2、网卡驱动取走mbuf后,设置desc的DD为0,更新RDT
uint16_t ixgbe_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts) { ... nb_rx = 0; nb_hold = 0; rxq = rx_queue; rx_id = rxq->rx_tail; /* 相当于ixgbe的next_to_clean */ rx_ring = rxq->rx_ring; sw_ring = rxq->sw_ring; ... while (nb_rx < nb_pkts) { ... /* 得到rx_tail指向的desc的指针 */ rxdp = &rx_ring[rx_id]; /* 若网卡回写的DD为0,跳出循环 */ staterr = rxdp->wb.upper.status_error; if (!(staterr & rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD))) break; /* 得到rx_tail指向的desc */ rxd = *rxdp; ... /* 分配新mbuf */ nmb = rte_mbuf_raw_alloc(rxq->mb_pool); ... nb_hold++; /* 统计接收的mbuf数 */ rxe = &sw_ring[rx_id]; /* 得到旧mbuf */ rx_id++; /* 得到下一个desc的index,注意是一个环形缓冲区 */ if (rx_id == rxq->nb_rx_desc) rx_id = 0; ... rte_ixgbe_prefetch(sw_ring[rx_id].mbuf); /* 预取下一个mbuf */ ... if ((rx_id & 0x3) == 0) { rte_ixgbe_prefetch(&rx_ring[rx_id]); rte_ixgbe_prefetch(&sw_ring[rx_id]); } ... rxm = rxe->mbuf; /* rxm指向旧mbuf */ rxe->mbuf = nmb; /* rxe->mbuf指向新mbuf */ dma_addr = rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(nmb)); /* 得到新mbuf的总线地址 */ rxdp->read.hdr_addr = 0; /* 清零新mbuf对应的desc的DD,后续网卡会读desc */ rxdp->read.pkt_addr = dma_addr; /* 设置新mbuf对应的desc的总线地址,后续网卡会读desc */ ... 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; ... if (likely(pkt_flags & PKT_RX_RSS_HASH)) /* RSS */ rxm->hash.rss = rte_le_to_cpu_32( rxd.wb.lower.hi_dword.rss); else if (pkt_flags & PKT_RX_FDIR) { /* FDIR */ rxm->hash.fdir.hash = rte_le_to_cpu_16( rxd.wb.lower.hi_dword.csum_ip.csum) & IXGBE_ATR_HASH_MASK; rxm->hash.fdir.id = rte_le_to_cpu_16( rxd.wb.lower.hi_dword.csum_ip.ip_id); } ... rx_pkts[nb_rx++] = rxm; /* 将旧mbuf放入rx_pkts数组 */ } rxq->rx_tail = rx_id; /* rx_tail指向下一个desc */ ... nb_hold = (uint16_t) (nb_hold + rxq->nb_rx_hold); /* 若已处理的mbuf数大于上限(默认为32),更新RDT */ if (nb_hold > rxq->rx_free_thresh) { ... 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); /* 将rx_id写入RDT */ nb_hold = 0; /* 清零nb_hold */ } rxq->nb_rx_hold = nb_hold; /* 更新nb_rx_hold */ return nb_rx; }
参考https://blog.csdn.net/hz5034/article/details/88367518
https://blog.csdn.net/hz5034/article/details/88381486
函数 | 功能 |
---|---|
rte_eth_dev_count() | 网卡数 |
rte_eth_dev_configure() | 配置网卡 |
rte_eth_rx_queue_setup() rte_eth_tx_queue_setup() |
为网卡分配接收/发送队列 |
rte_eth_dev_start() | 启动网卡 |
rte_eth_rx_burst() rte_eth_tx_burst() |
基于指定网卡指定队列的收/发包函数 |
rte_eth_dev / rte_eth_dev_data
DPDK定义了一个rte_eth_devices数组,数组元素类型为struct rte_eth_dev,一个数组元素表示一块网卡。struct rte_eth_dev有四个重要的成员:rx/tx_pkt_burst、dev_ops、data,其中前两者分别是网卡的burst收/发包函数;dev_ops是网卡驱动注册的函数表,类型为struct eth_dev_ops;data包含了网卡的主要信息,类型为struct rte_eth_dev_data
struct rte_eth_dev { /* 在rte_bus_probe()中注册rx/tx_pkt_burst */ eth_rx_burst_t rx_pkt_burst; /**< Pointer to PMD receive function. */ eth_tx_burst_t tx_pkt_burst; /**< Pointer to PMD transmit function. */ eth_tx_prep_t tx_pkt_prepare; /**< Pointer to PMD transmit prepare function. */ struct rte_eth_dev_data *data; /**< Pointer to device data */ /* 在rte_bus_probe()中注册dev_ops */ const struct eth_dev_ops *dev_ops; /**< Functions exported by PMD */ struct rte_device *device; /**< Backing device */ struct rte_intr_handle *intr_handle; /**< Device interrupt handle */ /** User application callbacks for NIC interrupts */ struct rte_eth_dev_cb_list link_intr_cbs; /** * User-supplied functions called from rx_burst to post-process * received packets before passing them to the user */ struct rte_eth_rxtx_callback *post_rx_burst_cbs[RTE_MAX_QUEUES_PER_PORT]; /** * User-supplied functions called from tx_burst to pre-process * received packets before passing them to the driver for transmission. */ struct rte_eth_rxtx_callback *pre_tx_burst_cbs[RTE_MAX_QUEUES_PER_PORT]; enum rte_eth_dev_state state; /**< Flag indicating the port state */ } __rte_cache_aligned; struct rte_eth_dev_data { char name[RTE_ETH_NAME_MAX_LEN]; /**< Unique identifier name */ /* 接收队列数组 */ void **rx_queues; /**< Array of pointers to RX queues. */ /* 发送队列数组 */ void **tx_queues; /**< Array of pointers to TX queues. */ /* 接收队列数组长度 */ uint16_t nb_rx_queues; /**< Number of RX queues. */ /* 发送队列数组长度 */ uint16_t nb_tx_queues; /**< Number of TX queues. */ struct rte_eth_dev_sriov sriov; /**< SRIOV data */ void *dev_private; /**< PMD-specific private data */ struct rte_eth_link dev_link; /**< Link-level information & status */ struct rte_eth_conf dev_conf; /**< Configuration applied to device. */ uint16_t mtu; /**< Maximum Transmission Unit. */ uint32_t min_rx_buf_size; /**< Common rx buffer size handled by all queues */ uint64_t rx_mbuf_alloc_failed; /**< RX ring mbuf allocation failures. */ struct ether_addr* mac_addrs;/**< Device Ethernet Link address. */ uint64_t mac_pool_sel[ETH_NUM_RECEIVE_MAC_ADDR]; /** bitmap array of associating Ethernet MAC addresses to pools */ struct ether_addr* hash_mac_addrs; /** Device Ethernet MAC addresses of hash filtering. */ uint8_t port_id; /**< Device [external] port identifier. */ __extension__ uint8_t promiscuous : 1, /**< RX promiscuous mode ON(1) / OFF(0). */ scattered_rx : 1, /**< RX of scattered packets is ON(1) / OFF(0) */ all_multicast : 1, /**< RX all multicast mode ON(1) / OFF(0). */ dev_started : 1, /**< Device state: STARTED(1) / STOPPED(0). */ lro : 1; /**< RX LRO is ON(1) / OFF(0) */ uint8_t rx_queue_state[RTE_MAX_QUEUES_PER_PORT]; /** Queues state: STARTED(1) / STOPPED(0) */ uint8_t tx_queue_state[RTE_MAX_QUEUES_PER_PORT]; /** Queues state: STARTED(1) / STOPPED(0) */ uint32_t dev_flags; /**< Capabilities */ enum rte_kernel_driver kdrv; /**< Kernel driver passthrough */ int numa_node; /**< NUMA node connection */ struct rte_vlan_filter_conf vlan_filter_conf; /**< VLAN filter configuration. */ }; struct rte_eth_dev rte_eth_devices[RTE_MAX_ETHPORTS]; static struct rte_eth_dev_data *rte_eth_dev_data;
rte_eth_dev_configure()
rte_eth_dev_configure()的主要工作是分配接收/发送队列数组,数组元素类型是void *,一个数组元素表示一个接收/发送队列
int rte_eth_dev_configure(uint8_t port_id, uint16_t nb_rx_q, uint16_t nb_tx_q, const struct rte_eth_conf *dev_conf) { struct rte_eth_dev *dev; struct rte_eth_dev_info dev_info; int diag; /* 检查port_id是否合法 */ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -EINVAL); /* 检查接收队列数是否大于DPDK上限 */ if (nb_rx_q > RTE_MAX_QUEUES_PER_PORT) { RTE_PMD_DEBUG_TRACE( "Number of RX queues requested (%u) is greater than max supported(%d)\n", nb_rx_q, RTE_MAX_QUEUES_PER_PORT); return -EINVAL; } /* 检查发送队列数是否大于DPDK上限 */ if (nb_tx_q > RTE_MAX_QUEUES_PER_PORT) { RTE_PMD_DEBUG_TRACE( "Number of TX queues requested (%u) is greater than max supported(%d)\n", nb_tx_q, RTE_MAX_QUEUES_PER_PORT); return -EINVAL; } /* 得到port_id对应的设备 */ dev = &rte_eth_devices[port_id]; /* 检查dev_infos_get和dev_configure是否定义 */ RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_infos_get, -ENOTSUP); RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_configure, -ENOTSUP); /* 检查设备是否已启动 */ if (dev->data->dev_started) { RTE_PMD_DEBUG_TRACE( "port %d must be stopped to allow configuration\n", port_id); return -EBUSY; } /* Copy the dev_conf parameter into the dev structure */ /* 复制dev_conf到dev->data->dev_conf */ memcpy(&dev->data->dev_conf, dev_conf, sizeof(dev->data->dev_conf)); /* * Check that the numbers of RX and TX queues are not greater * than the maximum number of RX and TX queues supported by the * configured device. */ /* ixgbe为ixgbe_dev_info_get() */ (*dev->dev_ops->dev_infos_get)(dev, &dev_info); /* 检查接收/发送队列数是否同时为0 */ if (nb_rx_q == 0 && nb_tx_q == 0) { RTE_PMD_DEBUG_TRACE("ethdev port_id=%d both rx and tx queue cannot be 0\n", port_id); return -EINVAL; } /* 检查接收队列数是否大于网卡上限 */ if (nb_rx_q > dev_info.max_rx_queues) { RTE_PMD_DEBUG_TRACE("ethdev port_id=%d nb_rx_queues=%d > %d\n", port_id, nb_rx_q, dev_info.max_rx_queues); return -EINVAL; } /* 检查发送队列数是否大于网卡上限 */ if (nb_tx_q > dev_info.max_tx_queues) { RTE_PMD_DEBUG_TRACE("ethdev port_id=%d nb_tx_queues=%d > %d\n", port_id, nb_tx_q, dev_info.max_tx_queues); return -EINVAL; } /* Check that the device supports requested interrupts */ if ((dev_conf->intr_conf.lsc == 1) && (!(dev->data->dev_flags & RTE_ETH_DEV_INTR_LSC))) { RTE_PMD_DEBUG_TRACE("driver %s does not support lsc\n", dev->device->driver->name); return -EINVAL; } if ((dev_conf->intr_conf.rmv == 1) && (!(dev->data->dev_flags & RTE_ETH_DEV_INTR_RMV))) { RTE_PMD_DEBUG_TRACE("driver %s does not support rmv\n", dev->device->driver->name); return -EINVAL; } /* * If jumbo frames are enabled, check that the maximum RX packet * length is supported by the configured device. */ if (dev_conf->rxmode.jumbo_frame == 1) { if (dev_conf->rxmode.max_rx_pkt_len > dev_info.max_rx_pktlen) { RTE_PMD_DEBUG_TRACE("ethdev port_id=%d max_rx_pkt_len %u" " > max valid value %u\n", port_id, (unsigned)dev_conf->rxmode.max_rx_pkt_len, (unsigned)dev_info.max_rx_pktlen); return -EINVAL; } else if (dev_conf->rxmode.max_rx_pkt_len < ETHER_MIN_LEN) { RTE_PMD_DEBUG_TRACE("ethdev port_id=%d max_rx_pkt_len %u" " < min valid value %u\n", port_id, (unsigned)dev_conf->rxmode.max_rx_pkt_len, (unsigned)ETHER_MIN_LEN); return -EINVAL; } } else { if (dev_conf->rxmode.max_rx_pkt_len < ETHER_MIN_LEN || dev_conf->rxmode.max_rx_pkt_len > ETHER_MAX_LEN) /* 小于64或大于1518 */ /* Use default value */ dev->data->dev_conf.rxmode.max_rx_pkt_len = ETHER_MAX_LEN; /* 默认值为1518 */ } /* * Setup new number of RX/TX queues and reconfigure device. */ /* 分配接收队列数组,地址赋给dev->data->rx_queues,长度赋给dev->data->nb_rx_queues */ diag = rte_eth_dev_rx_queue_config(dev, nb_rx_q); if (diag != 0) { RTE_PMD_DEBUG_TRACE("port%d rte_eth_dev_rx_queue_config = %d\n", port_id, diag); return diag; } /* 分配发送队列数组,地址赋给dev->data->tx_queues,长度赋给dev->data->nb_tx_queues */ diag = rte_eth_dev_tx_queue_config(dev, nb_tx_q); if (diag != 0) { RTE_PMD_DEBUG_TRACE("port%d rte_eth_dev_tx_queue_config = %d\n", port_id, diag); rte_eth_dev_rx_queue_config(dev, 0); return diag; } /* ixgbe为ixgbe_dev_configure() */ diag = (*dev->dev_ops->dev_configure)(dev); if (diag != 0) { RTE_PMD_DEBUG_TRACE("port%d dev_configure = %d\n", port_id, diag); rte_eth_dev_rx_queue_config(dev, 0); rte_eth_dev_tx_queue_config(dev, 0); return diag; } return 0; }
rte_eth_dev_rx_queue_config() -------------创建多个rx队列
static int rte_eth_dev_rx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues) { ... dev->data->rx_queues = rte_zmalloc("ethdev->rx_queues", sizeof(dev->data->rx_queues[0]) * nb_queues, RTE_CACHE_LINE_SIZE); ... dev->data->nb_rx_queues = nb_queues; /* 更新nb_rx_queues */ ... }
rte_eth_dev_tx_queue_config()
static int rte_eth_dev_tx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues) { ... dev->data->tx_queues = rte_zmalloc("ethdev->tx_queues", sizeof(dev->data->tx_queues[0]) * nb_queues, RTE_CACHE_LINE_SIZE); ... dev->data->nb_tx_queues = nb_queues; /* 更新nb_tx_queues */ ... }
ixgbe_dev_configure()
static int ixgbe_dev_configure(struct rte_eth_dev *dev) { ... /* multipe queue mode checking */ ret = ixgbe_check_mq_mode(dev); ... /* * Initialize to TRUE. If any of Rx queues doesn't meet the bulk * allocation or vector Rx preconditions we will reset it. */ adapter->rx_bulk_alloc_allowed = true; adapter->rx_vec_allowed = true; ... }
int __attribute__((cold)) ixgbe_dev_rx_queue_start(struct rte_eth_dev *dev, uint16_t rx_queue_id) { ... /* 为每个接收队列分配mbuf */ if (ixgbe_alloc_rx_queue_mbufs(rxq) != 0) { ... /* 使能接收 */ rxdctl = IXGBE_READ_REG(hw, IXGBE_RXDCTL(rxq->reg_idx)); rxdctl |= IXGBE_RXDCTL_ENABLE; IXGBE_WRITE_REG(hw, IXGBE_RXDCTL(rxq->reg_idx), rxdctl); ... /* 写RDH为0 */ IXGBE_WRITE_REG(hw, IXGBE_RDH(rxq->reg_idx), 0); /* 写RDT为rxq->nb_rx_desc - 1 */ IXGBE_WRITE_REG(hw, IXGBE_RDT(rxq->reg_idx), rxq->nb_rx_desc - 1); /* 设置接收队列状态为RTE_ETH_QUEUE_STATE_STARTED */ dev->data->rx_queue_state[rx_queue_id] = RTE_ETH_QUEUE_STATE_STARTED; ... } 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; struct rte_mbuf *mbuf = rte_mbuf_raw_alloc(rxq->mb_pool); /* 分配mbuf */ if (mbuf == NULL) { PMD_INIT_LOG(ERR, "RX mbuf alloc failed queue_id=%u", (unsigned) rxq->queue_id); return -ENOMEM; } mbuf->data_off = RTE_PKTMBUF_HEADROOM; mbuf->port = rxq->port_id; dma_addr = rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(mbuf)); /* mbuf的总线地址 */ rxd = &rxq->rx_ring[i]; rxd->read.hdr_addr = 0; rxd->read.pkt_addr = dma_addr; /* 总线地址赋给rxd->read.pkt_addr */ rxe[i].mbuf = mbuf; /* 将mbuf挂载到rxe */ } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律