3. l2fwd 基础架构:主循环逻辑、端口初始化、mbuf 池创建、转发核心模型
l2fwd 是 DPDK 中最经典的二层转发示例,它剥离了协议栈细节,聚焦于数据面的核心收-转-发流水线。理解 l2fwd 的基础架构,是掌握 DPDK 应用开发的第一步。本章将拆解其四大核心模块:主循环逻辑、端口初始化、mbuf 池创建与转发核心模型。
3.1 整体架构概览
l2fwd 采用“一对端口”的对称转发模型:从端口 A 收到的报文,直接修改 MAC 地址后从端口 B 发出;反之亦然。整个应用运行在用户态轮询模式(PMD)之上,无中断、无系统调用。
设计要点: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 交换 |
常见错误:如果
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(ð_hdr->d_addr, &tmp_addr);
rte_ether_addr_copy(ð_hdr->s_addr, ð_hdr->d_addr);
rte_ether_addr_copy(&tmp_addr, ð_hdr->s_addr);
// 3. 将源 MAC 替换为本端口的 MAC(可选)
// rte_ether_addr_copy(&l2fwd_ports_eth_addr[portid],
// ð_hdr->s_addr);
}
}
3.6 核心模型总结
l2fwd 的基础架构可以归纳为以下四点:
- 主循环:无休止的轮询,批量收包 → 批量转发 → 批量发包,零中断。
- 端口初始化:6 步标准流程,从设备检测到端口启动,确保硬件就绪。
- mbuf 池:全局共享内存池,通过 per-lcore 缓存减少锁竞争,是零拷贝的基础。
- 转发核心:MAC 地址交换,不查表、不路由,极致简洁,适合性能基准测试。
性能基线:在 Intel Xeon 平台 + 25G NIC 上,l2fwd 单核转发性能可达 14.88 Mpps(64 字节报文),接近线速。这一基线是后续优化(如 DMA 引擎、批量收包)的起点。