4. 网卡初始化流程:设备探测、资源分配、端口启动、队列设置

网卡初始化是 PMD 驱动开发中最基础也最关键的环节。一个完整的初始化流程涉及从 PCI 设备发现到数据路径就绪的四个阶段:设备探测、资源分配、端口启动、队列设置。任何阶段的失败或配置不当,都会导致后续收发包异常甚至驱动崩溃。

核心原则:初始化顺序不可逆。设备探测必须在资源分配之前完成,端口启动必须在队列设置之后触发。违反此顺序会导致未定义行为。

4.1 设备探测(Probe)

设备探测是 PMD 与硬件交互的起点。DPDK 通过 EAL(Environment Abstraction Layer) 扫描 PCI 总线,匹配厂商 ID(Vendor ID)和设备 ID(Device ID),调用对应 PMD 的 probe 回调函数。

4.1.1 探测流程

  1. PCI 扫描:EAL 读取 /sys/bus/pci/devices/ 下的设备列表,获取 BDF(Bus:Device.Function)地址。
  2. ID 匹配:遍历已注册的 PMD 驱动表,匹配 pci_id_table 中的 VID/PID/Subsystem ID。
  3. Probe 回调:调用 PMD 的 eth_dev_init() 函数,完成硬件寄存器初始化、MAC 地址读取、固件版本校验。
  4. 设备注册:将 rte_eth_dev 结构体加入 DPDK 设备链表,分配端口 ID。
工程经验:在 probe 阶段建议打印完整的设备信息(BDF、MAC、固件版本),便于后续调试时确认设备状态。使用 RTE_LOG(DEBUG, PMD, ...) 输出,避免在生产环境产生过多日志。

4.1.2 设备探测流程图

设备探测流程 PCI 总线扫描 ID 匹配(VID/PID) Probe 回调 硬件初始化 设备注册 端口 ID 分配 关键检查点 • 确认 BDF 地址正确 • 检查 PCIe 链路状态 • 验证固件版本兼容性

4.2 资源分配

设备探测完成后,PMD 需要为每个端口分配运行时所需的系统资源。资源分配阶段决定了数据路径的性能上限。

4.2.1 主要资源类型

资源类型 分配时机 典型大小 对齐要求
硬件描述符环(Rx/Tx Ring) dev_configure 512 ~ 4096 个描述符 4KB 对齐
DMA 缓冲区(mbuf pool) dev_configure 前 每队列 2048 ~ 8192 个 mbuf Cacheline 对齐
中断向量 dev_start 1 ~ 16 个向量 —
统计计数器 dev_init 硬件寄存器映射 页对齐
常见陷阱:mbuf pool 必须在 rte_eth_dev_configure() 之前创建。如果先配置设备再创建 pool,驱动在配置阶段无法验证 mbuf 大小是否满足硬件最小帧要求,可能导致运行时丢包。

4.2.2 资源分配顺序

/* 典型资源分配代码片段 */
struct rte_mempool *mbuf_pool;

/* 1. 创建 mbuf 池(必须在 configure 之前) */
mbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF,
    MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
    rte_socket_id());
if (!mbuf_pool)
    rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

/* 2. 配置设备(分配描述符环、队列资源) */
ret = rte_eth_dev_configure(port_id, nb_rx_queues,
    nb_tx_queues, &port_conf);
if (ret < 0)
    rte_exit(EXIT_FAILURE, "Cannot configure device\n");

/* 3. 设置 Rx/Tx 队列(绑定 mbuf pool 到队列) */
for (q = 0; q < nb_rx_queues; q++) {
    ret = rte_eth_rx_queue_setup(port_id, q, nb_rxd,
        rte_eth_dev_socket_id(port_id), &rx_conf, mbuf_pool);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "Rx queue setup failed\n");
}

4.3 端口启动

端口启动是初始化流程中与硬件交互最密集的阶段。调用 rte_eth_dev_start() 后,PMD 将配置写入硬件寄存器,使能 DMA 引擎,启动链路协商。

4.3.1 启动阶段关键操作

  • 硬件复位:将 MAC 和 PHY 恢复到已知状态,清除残留中断状态。
  • 描述符环加载:将 Rx/Tx 描述符环的物理地址写入硬件寄存器,使硬件能够访问主机内存。
  • 链路配置:设置自动协商(Auto-Negotiation)或强制速率/双工模式。
  • 中断使能:对于支持中断的 PMD,注册并启用链路状态变化中断。
  • 启动 DMA:设置 Tx/Rx 使能位,硬件开始接收和发送数据包。
性能关键:在 dev_start 中,描述符环地址的写入必须使用 rte_write64() 或 rte_mmio_write() 确保 64 位原子写入。32 位拆分写入可能导致硬件读到错误的高 32 位地址,引发 DMA 错误。

4.3.2 端口启动状态机

端口启动状态机 STOPPED RESETTING CONFIGURING LINK_UP RUNNING dev_start 配置完成 链路协商 DMA 使能 dev_stop STOPPED 超时处理 • 链路协商超时:5s • 硬件复位超时:1s • 超时后回退 STOPPED

4.4 队列设置

队列设置是初始化流程的最后一步,也是数据路径的入口。队列配置直接影响收发包的吞吐量和延迟。

4.4.1 Rx 队列配置参数

参数 说明 推荐值 影响
nb_rx_desc Rx 描述符环大小 1024 / 2048 越大抗突发能力越强,但延迟增加
rx_free_thresh 空闲描述符阈值 32 / 64 触发硬件回写,影响 CPU 利用率
rx_drop_en 是否允许硬件丢包 1(开启) 防止描述符耗尽时系统挂死
offloads 硬件卸载功能位图 DEV_RX_OFFLOAD_CHECKSUM 开启后硬件计算校验和,减少 CPU 负载

4.4.2 Tx 队列配置参数

参数 说明 推荐值 影响
nb_tx_desc Tx 描述符环大小 1024 / 2048 越大支持更多待发送包,但占用内存
tx_free_thresh 空闲描述符阈值 32 触发回写,影响发送完成通知频率
tx_rs_thresh 报告状态阈值 32 控制硬件回写完成状态的频率
offloads 硬件卸载功能位图 DEV_TX_OFFLOAD_IPV4_CKSUM 硬件计算 IP/TCP/UDP 校验和

4.4.3 队列设置代码模板

/* Rx 队列设置 */
struct rte_eth_rxconf rx_conf = {
    .rx_thresh = { .pthresh = 8, .hthresh = 8, .wthresh = 4 },
    .rx_free_thresh = 64,
    .rx_drop_en = 1,
    .offloads = DEV_RX_OFFLOAD_CHECKSUM,
};

for (q = 0; q < nb_rx_queues; q++) {
    ret = rte_eth_rx_queue_setup(port_id, q, nb_rx_desc,
        socket_id, &rx_conf, mbuf_pool);
    if (ret < 0) {
        RTE_LOG(ERR, PMD, "Rx queue %u setup failed: %d\n",
            q, ret);
        return ret;
    }
}

/* Tx 队列设置 */
struct rte_eth_txconf tx_conf = {
    .tx_thresh = { .pthresh = 36, .hthresh = 0, .wthresh = 0 },
    .tx_free_thresh = 32,
    .tx_rs_thresh = 32,
    .offloads = DEV_TX_OFFLOAD_IPV4_CKSUM,
};

for (q = 0; q < nb_tx_queues; q++) {
    ret = rte_eth_tx_queue_setup(port_id, q, nb_tx_desc,
        socket_id, &tx_conf);
    if (ret < 0) {
        RTE_LOG(ERR, PMD, "Tx queue %u setup failed: %d\n",
            q, ret);
        return ret;
    }
}
调试技巧:在队列设置完成后,使用 rte_eth_rx_queue_info_get() 和 rte_eth_tx_queue_info_get() 验证实际配置是否与预期一致。特别检查 nb_desc 字段,硬件可能向上取整到 2 的幂次。

4.5 初始化流程总结

完整的网卡初始化流程可以归纳为以下四个阶段,每个阶段都有明确的入口函数和退出条件:

  1. 设备探测:rte_eal_init() → rte_eth_dev_count_avail() → 确认端口存在。
  2. 资源分配:rte_pktmbuf_pool_create() → rte_eth_dev_configure() → rte_eth_rx/tx_queue_setup()。
  3. 端口启动:rte_eth_dev_start() → 等待链路 up → rte_eth_promiscuous_enable()(可选)。
  4. 队列就绪:验证队列描述符数量 → 启动收发包线程 → 进入轮询循环。
生产环境建议:在端口启动后、进入主循环前,执行一次 rte_eth_stats_reset() 清零统计计数器。这样可以确保后续监控数据从零开始,避免累积旧数据干扰告警判断。