3. l2fwd 基础架构:主循环逻辑、端口初始化、mbuf 池创建、转发核心模型

l2fwd 是 DPDK 中最经典的二层转发示例,它剥离了协议栈细节,聚焦于数据面的核心收-转-发流水线。理解 l2fwd 的基础架构,是掌握 DPDK 应用开发的第一步。本章将拆解其四大核心模块:主循环逻辑、端口初始化、mbuf 池创建与转发核心模型。

3.1 整体架构概览

l2fwd 采用“一对端口”的对称转发模型:从端口 A 收到的报文,直接修改 MAC 地址后从端口 B 发出;反之亦然。整个应用运行在用户态轮询模式(PMD)之上,无中断、无系统调用。

l2fwd 整体架构图 l2fwd 主循环 (Main Loop) 转发核心 (l2fwd_main_loop) mbuf 池 (mempool) 端口初始化 PMD 驱动层 NIC 网卡硬件 (Port 0 / Port 1)
设计要点:l2fwd 不涉及路由表、ARP 表或控制面协议。它只做 MAC 地址交换,是 DPDK 转发面性能测试的基准用例。

3.2 主循环逻辑 (Main Loop)

每个 lcore 运行一个独立的主循环。循环体内执行“收包 → 转发 → 发包”的原子操作。核心代码如下:

static int
l2fwd_main_loop(void *dummy)
{
    struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
    unsigned lcore_id = rte_lcore_id();
    uint8_t portid;

    printf("Core %u is forwarding.\n", lcore_id);

    for (;;) {
        // 1. 轮询所有绑定的端口,批量收包
        for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) {
            if (rte_eth_devices[portid].data->dev_started != 1)
                continue;

            // 2. 批量接收报文
            uint16_t nb_rx = rte_eth_rx_burst(portid, 0,
                                              pkts_burst, MAX_PKT_BURST);
            if (unlikely(nb_rx == 0))
                continue;

            // 3. 批量转发处理
            l2fwd_macswitching(pkts_burst, nb_rx, portid);

            // 4. 批量发送报文
            uint16_t nb_tx = rte_eth_tx_burst(portid ^ 1, 0,
                                              pkts_burst, nb_rx);
            if (unlikely(nb_tx < nb_rx))
                do {
                    // 释放未能发送的报文
                    rte_pktmbuf_free(pkts_burst[nb_tx]);
                } while (++nb_tx < nb_rx);
        }
    }
    return 0;
}
关键指标:主循环中 rte_eth_rx_burst 的批量大小 MAX_PKT_BURST 通常设为 32 或 64。批量越大,分摊到每报文的函数调用开销越低,但会增加延迟抖动。

3.3 端口初始化流程

端口初始化在 rte_eal_init 之后执行,主要完成以下步骤:

步骤 API 函数 说明
1. 端口数量检测 rte_eth_dev_count_avail() 获取可用物理端口数,至少需要 2 个
2. 端口配置 rte_eth_dev_configure() 设置 RX/TX 队列数、混杂模式、硬件卸载能力
3. RX 队列设置 rte_eth_rx_queue_setup() 绑定 mbuf 池,配置描述符数量(通常 512 或 1024)
4. TX 队列设置 rte_eth_tx_queue_setup() 配置发送描述符数量,释放阈值
5. 启动端口 rte_eth_dev_start() 使能端口,开始接收和发送
6. MAC 地址获取 rte_eth_macaddr_get() 保存端口 MAC 地址,用于后续 MAC 交换
端口初始化流程图 1. 端口数量检测 2. 端口配置 3. RX 队列设置 4. TX 队列设置 5. 启动端口
常见错误:如果 rte_eth_rx_queue_setup() 时传入的 mbuf 池大小不足,会导致收包时分配 mbuf 失败,出现丢包。建议每个端口至少分配 8192 个 mbuf。

3.4 mbuf 池创建

mbuf 是 DPDK 中报文存储的基本单元。l2fwd 在初始化阶段创建一个全局的 mbuf 池,所有端口共享使用。

static struct rte_mempool *l2fwd_pktmbuf_pool = NULL;

// 在 main() 中创建 mbuf 池
l2fwd_pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool",
                                              NB_MBUF,      // 池中 mbuf 总数
                                              256,          // 每个 lcore 的缓存大小
                                              0,            // 私有数据大小
                                              RTE_MBUF_DEFAULT_BUF_SIZE,
                                              rte_socket_id());
参数 典型值 说明
name "mbuf_pool" 内存池名称,用于调试和统计
n 8192 ~ 65535 mbuf 总数,决定最大并发报文数
cache_size 256 或 512 每个 lcore 的本地缓存,减少锁竞争
priv_size 0 用户私有数据区,l2fwd 不使用
data_room_size RTE_MBUF_DEFAULT_BUF_SIZE (2176) 每个 mbuf 的数据区大小,容纳最大 MTU 报文
socket_id rte_socket_id() 绑定到当前 lcore 所在的 NUMA 节点
性能提示:mbuf 池的 cache_size 应设置为 RTE_MEMPOOL_CACHE_MAX_SIZE 或 256。过小的缓存会导致频繁访问全局池,引发锁竞争;过大的缓存会浪费内存。

3.5 转发核心模型:MAC 交换

l2fwd 的转发逻辑极其简洁:收到报文后,交换源 MAC 和目标 MAC,然后从配对端口发出。核心函数 l2fwd_macswitching 实现如下:

static void
l2fwd_macswitching(struct rte_mbuf *pkts[], uint16_t nb_rx, uint8_t portid)
{
    struct rte_ether_hdr *eth_hdr;
    struct rte_ether_addr tmp_addr;
    uint16_t i;

    for (i = 0; i < nb_rx; i++) {
        // 1. 获取以太网头部指针
        eth_hdr = rte_pktmbuf_mtod(pkts[i], struct rte_ether_hdr *);

        // 2. 交换源 MAC 和目标 MAC
        rte_ether_addr_copy(&eth_hdr->d_addr, &tmp_addr);
        rte_ether_addr_copy(&eth_hdr->s_addr, &eth_hdr->d_addr);
        rte_ether_addr_copy(&tmp_addr, &eth_hdr->s_addr);

        // 3. 将源 MAC 替换为本端口的 MAC(可选)
        // rte_ether_addr_copy(&l2fwd_ports_eth_addr[portid],
        //                     &eth_hdr->s_addr);
    }
}
MAC 交换转发模型 Port 0 (RX) Port 1 (TX) MAC 交换 s_addr ↔ d_addr 转发规则 从 Port 0 收到的报文 → 交换 MAC → 从 Port 1 发出 从 Port 1 收到的报文 → 交换 MAC → 从 Port 0 发出

3.6 核心模型总结

l2fwd 的基础架构可以归纳为以下四点:

  • 主循环:无休止的轮询,批量收包 → 批量转发 → 批量发包,零中断。
  • 端口初始化:6 步标准流程,从设备检测到端口启动,确保硬件就绪。
  • mbuf 池:全局共享内存池,通过 per-lcore 缓存减少锁竞争,是零拷贝的基础。
  • 转发核心:MAC 地址交换,不查表、不路由,极致简洁,适合性能基准测试。
性能基线:在 Intel Xeon 平台 + 25G NIC 上,l2fwd 单核转发性能可达 14.88 Mpps(64 字节报文),接近线速。这一基线是后续优化(如 DMA 引擎、批量收包)的起点。