Dpdk 初学(6) L2fwd
L2fwd example
本来应该是IP Fragmentation Sample的,但是前面提到说IP Fragmentation是基于L2fwd的,所以先来看这个。由于DPDK修改了代码组织,现在l2fwd example全名是L2 Forwarding Sample Application (in Real and Virtualized Environments)。
关键函数
main
承担了很多初始化工作,以下代码为按照默认规则进行转发配置。首先跳过没有被我们通过-p
参数确定使用的端口,然后根据临近的规则进行转发配对。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
RTE_ETH_FOREACH_DEV(portid) {
/* skip ports that are not enabled */
if ((l2fwd_enabled_port_mask & (1 << portid)) == 0)
continue;
if (nb_ports_in_mask % 2) {
l2fwd_dst_ports[portid] = last_port;
l2fwd_dst_ports[last_port] = portid;
} else {
last_port = portid;
}
nb_ports_in_mask++;
}
检查是否有足够的可用逻辑核进行转发,这里实际上是检查我们配置的-q
参数,通过轮询每一个端口来检查当前的rx_lcore_id
对应的lcore_queue_conf
是否已经拥有了对应的n_rx_port
。如果到达预设的上限,就将配置下一个端口。最终形成一个如下的映射:0(lcore)->0(port),1,2,3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while (rte_lcore_is_enabled(rx_lcore_id) == 0 ||
lcore_queue_conf[rx_lcore_id].n_rx_port ==
l2fwd_rx_queue_per_lcore)
{
rx_lcore_id++;
if (rx_lcore_id >= RTE_MAX_LCORE)
rte_exit(EXIT_FAILURE, "Not enough cores\n");
}
if (qconf != &lcore_queue_conf[rx_lcore_id])
{
/* Assigned a new logical core in the loop above. */
qconf = &lcore_queue_conf[rx_lcore_id];
nb_lcores++;
}
qconf->rx_port_list[qconf->n_rx_port] = portid;
qconf->n_rx_port++;
后续为常规的rte_eth_tx_queue_setup
与rte_eth_rx_queue_setup
,这两个函数在前面有提到。
rte_eth_tx_buffer_set_err_callback
该函数需要另外两个函数辅助,来创建一个在tx_buffer没有全部发送成功时的回调。rte_zmalloc_socket
用来创建后续需要的tx_buffer
,这里使用了一个宏RTE_ETH_TX_BUFFER_SIZE
,注意这个宏的参数和下面的rte_eth_tx_buffer_init
参数一致,都指的是packets number,而不是整体的size。
1
2
3
4
tx_buffer[portid] = rte_zmalloc_socket("tx_buffer",
RTE_ETH_TX_BUFFER_SIZE(MAX_PKT_BURST), 0,
rte_eth_dev_socket_id(portid));
rte_eth_tx_buffer_init(tx_buffer[portid], MAX_PKT_BURST);
rte_eth_dev_set_ptypes
看描述这个函数是用来设定指定ptype类型的数据包接收。这里使用这个函数反而是为了关闭ptype解析。
l2fwd_main_loop
间隔指定时长就清空一次tx_buffer,尝试将它们全部发送出去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cur_tsc = rte_rdtsc();
/*
* TX burst queue drain
*/
diff_tsc = cur_tsc - prev_tsc;
if (unlikely(diff_tsc > drain_tsc)) {
for (i = 0; i < qconf->n_rx_port; i++) {
portid = l2fwd_dst_ports[qconf->rx_port_list[i]];
buffer = tx_buffer[portid];
sent = rte_eth_tx_buffer_flush(portid, 0, buffer);
if (sent)
port_statistics[portid].tx += sent;
}
在间隔时间内,尝试读取rx_queue。并且将读取到的pkts_burset进行转发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
for (i = 0; i < qconf->n_rx_port; i++) {
portid = qconf->rx_port_list[i];
nb_rx = rte_eth_rx_burst(portid, 0,
pkts_burst, MAX_PKT_BURST);
if (unlikely(nb_rx == 0))
continue;
port_statistics[portid].rx += nb_rx;
for (j = 0; j < nb_rx; j++) {
m = pkts_burst[j];
rte_prefetch0(rte_pktmbuf_mtod(m, void *));
l2fwd_simple_forward(m, portid);
}
static void
l2fwd_simple_forward(struct rte_mbuf *m, unsigned portid)
{
unsigned dst_port;
int sent;
struct rte_eth_dev_tx_buffer *buffer;
dst_port = l2fwd_dst_ports[portid];
if (mac_updating)
l2fwd_mac_updating(m, dst_port);
buffer = tx_buffer[dst_port];
sent = rte_eth_tx_buffer(dst_port, 0, buffer, m);
if (sent)
port_statistics[dst_port].tx += sent;
}
rte_eth_tx_buffer
只是将pkt存入buffer,等到buffer填满或者主动触发,再一次性发出所有包,和之前的rte_eth_tx_buffer_flush
搭配。
全部代码看下来还是比较整洁的,逻辑也容易理解。但是有个地方有点没理解,用lspci -vv
查看NIC的配置后,显示如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0b:00.0 Ethernet controller: VMware VMXNET3 Ethernet Controller (rev 01)
DeviceName: Ethernet2
Subsystem: VMware VMXNET3 Ethernet Controller
Physical Slot: 192
Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
...
Capabilities: [84] MSI: Enable- Count=1/1 Maskable- 64bit+
Address: 0000000000000000 Data: 0000
Capabilities: [9c] MSI-X: Enable+ Count=25 Masked-
Vector table: BAR=2 offset=00000000
PBA: BAR=2 offset=00001000
Capabilities: [100 v1] Device Serial Number 00-0c-29-ff-ff-6d-fe-e4
Kernel driver in use: vfio-pci
Kernel modules: vmxnet3
这里显示的是该网卡支持MSI-X并且处于打开状态,可支持中断为25个。但是在代码中,获取的queue数量只有24。
1
2
3
printf("now we've %d rx_queue, %d tx_queue\n", dev_info.max_rx_queues, dev_info.max_tx_queues);
now we've 16 rx_queue, 8 tx_queue
还有一个跑哪里去了。
关键流程
运行本例子之前,请确保有两个端口可用
在这个例子中,引入了额外参数,用--
进行分隔,参数含义说明如下
- -q:每一个逻辑核上的队列数
- -p:端口掩码,0xf = 1111B 代表启用4个端口
- -portmap:”(0,2)(1,3)” 代表0、1端口配对转发,2、3端口配对转发
- 初始化环境、参数
- 如果没有portmaps进行指定,就按照默认规则进行转发配置
- 开始进行l2fwd需要的初始化工作
- 等待包,凑齐后转发(修改mac地址)