Post

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_setuprte_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地址)
This post is licensed under CC BY 4.0 by the author.