DPDK报文转发
参考文献:
《深入浅出DPDK》
DPDK官网
https://software.intel.com/en-us/articles/introduction-to-the-data-plane-development-kit-dpdk-packet-framework
...........................................................................................................
这一部分属于DPDK核心部分了, 对于一个报文的整个生命周期,如何从对接运营商的外部接口进入路由器,再连接计算机的网卡,发送的过程,或许这就是我们所疑惑的,也是此次学习的重点部分,只有弄清楚每个步骤每个环节,或许才能彻底弄懂网络报文处理,然后解决其中出现的问题
一. DPDK网络处理模块划分
网络报文的处理转发主要分为硬件部分和软件处理部分,由一下几个模块构成:
- packet input
- pre-processing:对报文进行粗粒度处理
- input classification:对报文进行细粒度分流
- ingress queuing:提供基于描述符的队列FIFO
- delivery/scheduling:根据队列优先级和CPU状态进行调度
- accelerator:提供加解密和压缩/解压缩等硬件功能
- egress queuing:在出口根据QoS等级进行调度
- post processing:后期报文处理释放缓存
- packet output:从硬件发送出去
图1
如图一所示,浅色和阴影部分都是对应模块和硬件相关的,所以提升这部分性能最佳的选择是尽量多的选择网卡上的或设备芯片提供的网络特定功能相关的卸载的特性,深色软件部分可以通过提高算法的效率和结合CPU相关的并行指令来提升网络性能,了解网络处理模块的基本组成部分后,我们再来看看不同的转发框架如何让这些模块协同工作完成网络包的处理的
二. 转发框架介绍
传统的Network Process (专用网络处理器)转发的模型可以分为run to comletion(运行至终结, RTC)模型和pipeline (流水线)模型
2.1 流水线模型(pipeline)
pipeline 借鉴与工业上的流水线模型,将整个功能拆分成多个独立的阶段,不同阶段通过队列传递产品。这样对于一些CPU密集和I/O密集的应用,将I/O密集的操作放在另一个微处理器引擎上执行。通过过滤器可以分为不同的操作分配不同的线程,通过队列匹配两个速度,达到最好的并发,
我们可以看到图中,TOP(Task Optimized Processor)单元,每个TOP单元都是对特定的事物进行优化处理的特殊微单元
2.2 run to completion 模型
这个模型是DPDK针对一般的程序的运行方法,一个程序分为几个不同的逻辑功能,几个逻辑功能会在一个CPU的核上运行,我们下面看下模型的视图
这个模型没有对报文特殊处理的的运算单元,只有两个NP核, 两个NP核利用已烧录的微码进行报文处理
2.3 转发模型对比
从run to completion的模型中,我们可以清楚地看出,每个IA的物理核都负责处理整个报文的生命周期从RX到TX,这点非常类似前面所提到的AMCC的nP核的作用。在pipeline模型中可以看出,报文的处理被划分成不同的逻辑功能单元A、B、C,一个报文需分别经历A、B、C三个阶段,这三个阶段的功能单元可以不止一个并且可以分布在不同的物理核上,不同的功能单元可以分布在相同的核上(也可以分布在不同的核上),从这一点可以看出,其对于模块的分类和调用比EZchip的硬件方案更加灵活。
两个的优缺点:
三. 转发算法
除了良好的转发框架之外,转发中很重要的一部分内容就是对于报文字段的匹配和识别,在DPDK
中主要用到了精确匹配(Exact Match
)算法和最长前缀匹配(Longest Prefix Matching
,LPM
)算法来进行报文的匹配从而获得相应的信息。精确匹配主要需要解决两个问题:进行数据的签名(哈希),解决哈希的冲突问题,DPDK
中主要支持CRC32
和J hash
。
最长前缀匹配(Longest Prefix Matching
,LPM
)算法是指在IP协议中被路由器用于在路由表中进行选择的一个算法。当前DPDK使用的LPM
算法就利用内存的消耗来换取LPM
查找的性能提升。当查找表条目的前缀长度小于24位时,只需要一次访存就能找到下一条,根据概率统计,这是占较大概率的,当前缀大于24位时,则需要两次访存,但是这种情况是小概率事件。
ACL
库利用N元组的匹配规则去进行类型匹配,提供以下基本操作:
Packet distributor
(报文分发)是DPDK提供给用户的一个用于包分发的API库,用于进行包分发。主要功能可以用下图进行描述:
四. 示例(参考英特尔网站:https://software.intel.com/en-us/articles/introduction-to-the-data-plane-development-kit-dpdk-packet-framework)
ip_pipline应用通过提供几个示例应用和配置文件,展示了流水线模块的使用方法
下图展示了三层转发配置应用
在这一示例中,它设置了简单的路由。
“core” 条目线程ID(socket ID,物理CPU ID和超线程ID)确定了用来运行流水线的CPU核。
“pktq_in” 和 “pktq_out”参数定义了数据包传输的接口,这里指接受和传送数据包。
可以将“encap”参数设置为“ethernet”, “qinq”或“mpls”,用来为所有出站包封装合适的包头。
最后一个参数”ip_hdr_offset”可用于设置DPDK数据包结构(mbuf)中ip-header的起始位置所需偏移字节
下图是ip_pipeline的脚本文件,该文件中的内容将作为路由表项加入到路由流水线的最长匹配表中:
p <pipeline_id> route add <ip_addr> <depth> port <port_id> ether <next hop mac_addr>
可使用以下命令来运行L3转发应用:
$./build/ip_pipeline -f l3fwd.cfg -p 0xf -s l3fwd.sh
有时,应用程序在不同的功能模块之间进行互联时具有复杂的拓扑结构, 一旦应用程序配置文件准备就绪,请运行以下命令生成拓扑图
$./diagram-generator.py -f <configuration file>
ip_pipeline示例程序生成的拓扑图:
各种使用此标准流水线模型构建的网络功能(流水线)都可作为DPDK ip_pipeline示例应用程序的一部分.
DPDK支持pipeline 的有以下几种:
1)Packet I/O
2)Flow classification
3)Firewall
4)Routing
5)Metering
6)Traffic Mgmt
下面以官l2转发实例来看下整体代码如何运行的:
源码下载:http://core.dpdk.org/download/
代码路径:dpdk/examples/l2fwd
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <stdint.h> 5 #include <inttypes.h> 6 #include <sys/types.h> 7 #include <sys/queue.h> 8 #include <netinet/in.h> 9 #include <setjmp.h> 10 #include <stdarg.h> 11 #include <ctype.h> 12 #include <errno.h> 13 #include <getopt.h> 14 #include <signal.h> 15 #include <stdbool.h> 16 17 #include <rte_common.h> 18 #include <rte_log.h> 19 #include <rte_malloc.h> 20 #include <rte_memory.h> 21 #include <rte_memcpy.h> 22 #include <rte_memzone.h> 23 #include <rte_eal.h> 24 #include <rte_per_lcore.h> 25 #include <rte_launch.h> 26 #include <rte_atomic.h> 27 #include <rte_cycles.h> 28 #include <rte_prefetch.h> 29 #include <rte_lcore.h> 30 #include <rte_per_lcore.h> 31 #include <rte_branch_prediction.h> 32 #include <rte_interrupts.h> 33 #include <rte_pci.h> 34 #include <rte_random.h> 35 #include <rte_debug.h> 36 #include <rte_ether.h> 37 #include <rte_ethdev.h> 38 #include <rte_mempool.h> 39 #include <rte_mbuf.h> 40 41 static volatile bool force_quit; 42 43 /* MAC updating enabled by default */ 44 static int mac_updating = 1; 45 46 #define RTE_LOGTYPE_L2FWD RTE_LOGTYPE_USER1 47 48 #define NB_MBUF 8192 49 50 #define MAX_PKT_BURST 32 51 #define BURST_TX_DRAIN_US 100 /* TX drain every ~100us */ 52 #define MEMPOOL_CACHE_SIZE 256 53 54 /* 55 * Configurable number of RX/TX ring descriptors 56 */ 57 #define RTE_TEST_RX_DESC_DEFAULT 128 58 #define RTE_TEST_TX_DESC_DEFAULT 512 59 static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT; 60 static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT; 61 62 /* ethernet addresses of ports */ 63 //端口的以太网地址 64 static struct ether_addr l2fwd_ports_eth_addr[RTE_MAX_ETHPORTS]; 65 66 /* mask of enabled ports */ 67 //启用端口的mask 68 static uint32_t l2fwd_enabled_port_mask = 0; 69 70 /* list of enabled ports */ 71 //启用端口列表 72 static uint32_t l2fwd_dst_ports[RTE_MAX_ETHPORTS]; 73 74 static unsigned int l2fwd_rx_queue_per_lcore = 1; 75 76 #define MAX_RX_QUEUE_PER_LCORE 16 77 #define MAX_TX_QUEUE_PER_PORT 16 78 struct lcore_queue_conf { 79 unsigned n_rx_port; 80 unsigned rx_port_list[MAX_RX_QUEUE_PER_LCORE]; 81 } __rte_cache_aligned; 82 struct lcore_queue_conf lcore_queue_conf[RTE_MAX_LCORE]; 83 84 static struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS]; 85 86 static const struct rte_eth_conf port_conf = { 87 .rxmode = { 88 .split_hdr_size = 0, 89 .header_split = 0, /**< Header Split disabled */ //报头分离 90 .hw_ip_checksum = 0, /**< IP checksum offload disabled */ //IP校验和卸载 91 .hw_vlan_filter = 0, /**< VLAN filtering disabled */ //vlan过滤 92 .jumbo_frame = 0, /**< Jumbo Frame Support disabled */ //巨星帧的支持 93 .hw_strip_crc = 0, /**< CRC stripped by hardware */ //使用硬件清除CRC 94 }, 95 .txmode = { 96 .mq_mode = ETH_MQ_TX_NONE, 97 }, 98 }; 99 100 struct rte_mempool * l2fwd_pktmbuf_pool = NULL; 101 102 /* Per-port statistics struct */ 103 struct l2fwd_port_statistics { 104 uint64_t tx; 105 uint64_t rx; 106 uint64_t dropped; 107 } __rte_cache_aligned; 108 struct l2fwd_port_statistics port_statistics[RTE_MAX_ETHPORTS]; 109 110 #define MAX_TIMER_PERIOD 86400 /* 1 day max */ 111 /* A tsc-based timer responsible for triggering statistics printout */ 112 static uint64_t timer_period = 10; /* default period is 10 seconds */ 113 114 /* Print out statistics on packets dropped */ 115 //打印的属性跳过。。 116 static void 117 print_stats(void) 118 { 119 uint64_t total_packets_dropped, total_packets_tx, total_packets_rx; 120 unsigned portid; 121 122 total_packets_dropped = 0; 123 total_packets_tx = 0; 124 total_packets_rx = 0; 125 126 const char clr[] = { 27, '[', '2', 'J', '\0' }; 127 const char topLeft[] = { 27, '[', '1', ';', '1', 'H','\0' }; 128 129 /* Clear screen and move to top left */ 130 printf("%s%s", clr, topLeft); 131 132 printf("\nPort statistics ===================================="); 133 134 for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) { 135 /* skip disabled ports */ 136 if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) 137 continue; 138 printf("\nStatistics for port %u ------------------------------" 139 "\nPackets sent: %24"PRIu64 140 "\nPackets received: %20"PRIu64 141 "\nPackets dropped: %21"PRIu64, 142 portid, 143 port_statistics[portid].tx, 144 port_statistics[portid].rx, 145 port_statistics[portid].dropped); 146 147 total_packets_dropped += port_statistics[portid].dropped; 148 total_packets_tx += port_statistics[portid].tx; 149 total_packets_rx += port_statistics[portid].rx; 150 } 151 printf("\nAggregate statistics ===============================" 152 "\nTotal packets sent: %18"PRIu64 153 "\nTotal packets received: %14"PRIu64 154 "\nTotal packets dropped: %15"PRIu64, 155 total_packets_tx, 156 total_packets_rx, 157 total_packets_dropped); 158 printf("\n====================================================\n"); 159 } 160 161 static void 162 l2fwd_mac_updating(struct rte_mbuf *m, unsigned dest_portid) 163 { 164 struct ether_hdr *eth; 165 void *tmp; 166 167 eth = rte_pktmbuf_mtod(m, struct ether_hdr *); 168 169 /* 02:00:00:00:00:xx */ 170 tmp = ð->d_addr.addr_bytes[0]; 171 *((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dest_portid << 40); 172 173 /* src addr */ 174 ether_addr_copy(&l2fwd_ports_eth_addr[dest_portid], ð->s_addr); 175 } 176 177 //处理收到的数据包 178 static void 179 l2fwd_simple_forward(struct rte_mbuf *m, unsigned portid) 180 { 181 unsigned dst_port; 182 int sent; 183 struct rte_eth_dev_tx_buffer *buffer; 184 185 //获取目的端口ID 186 dst_port = l2fwd_dst_ports[portid]; 187 188 //更新MAC地址 189 if (mac_updating) 190 l2fwd_mac_updating(m, dst_port); 191 192 //获取目的端口的 tx 缓存 193 buffer = tx_buffer[dst_port]; 194 //发送数据包,到目的端口的tx缓存 195 sent = rte_eth_tx_buffer(dst_port, 0, buffer, m); 196 197 if (sent) 198 port_statistics[dst_port].tx += sent; //如果发包成功,发包计数+1 199 } 200 201 /* main processing loop */ 202 static void 203 l2fwd_main_loop(void) 204 { 205 struct rte_mbuf *pkts_burst[MAX_PKT_BURST]; 206 struct rte_mbuf *m; 207 int sent; 208 unsigned lcore_id; 209 uint64_t prev_tsc, diff_tsc, cur_tsc, timer_tsc; 210 unsigned i, j, portid, nb_rx; 211 struct lcore_queue_conf *qconf; 212 const uint64_t drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) / US_PER_S * 213 BURST_TX_DRAIN_US; 214 struct rte_eth_dev_tx_buffer *buffer; 215 216 prev_tsc = 0; 217 timer_tsc = 0; 218 219 //获取当前核心的ID 220 lcore_id = rte_lcore_id(); 221 //获取当前核心的配置 222 qconf = &lcore_queue_conf[lcore_id]; 223 224 //如果 rx_port的个数为0,则 记录日志。(其实是出错了) 225 if (qconf->n_rx_port == 0) { 226 RTE_LOG(INFO, L2FWD, "lcore %u has nothing to do\n", lcore_id); 227 return; 228 } 229 230 //记录日志, 231 RTE_LOG(INFO, L2FWD, "entering main loop on lcore %u\n", lcore_id); 232 233 //遍历所有的port 234 for (i = 0; i < qconf->n_rx_port; i++) { 235 //获取到portID 236 portid = qconf->rx_port_list[i]; 237 //记录日志 238 RTE_LOG(INFO, L2FWD, " -- lcoreid=%u portid=%u\n", lcore_id, 239 portid); 240 241 } 242 243 //如果没超时(最开始设置的那个二层转发的运行时间) 244 while (!force_quit) { 245 246 //获取时间戳 247 cur_tsc = rte_rdtsc(); 248 249 /* 250 * TX burst queue drain 251 */ 252 //对比时间戳 253 diff_tsc = cur_tsc - prev_tsc; 254 if (unlikely(diff_tsc > drain_tsc)) { 255 256 for (i = 0; i < qconf->n_rx_port; i++) { 257 //获得portid和buffer 258 portid = l2fwd_dst_ports[qconf->rx_port_list[i]]; 259 buffer = tx_buffer[portid]; 260 261 //把buffer中的数据发送到portid对应的port 262 sent = rte_eth_tx_buffer_flush(portid, 0, buffer); 263 if (sent) 264 port_statistics[portid].tx += sent; //如果发包成功,发包计数+1 265 266 } 267 268 /* if timer is enabled */ 269 //如果计时器开启 270 if (timer_period > 0) { 271 272 /* advance the timer */ 273 //调整计时器 274 timer_tsc += diff_tsc; 275 276 /* if timer has reached its timeout */ 277 //如果计时器超时 278 if (unlikely(timer_tsc >= timer_period)) { 279 280 /* do this only on master core */ 281 //主线程打印一些属性,仅有主线程会执行print_stats 282 if (lcore_id == rte_get_master_lcore()) { 283 print_stats(); //打印属性, 284 /* reset the timer */ 285 timer_tsc = 0; //设置计时器为0 286 } 287 } 288 } 289 290 prev_tsc = cur_tsc; 291 } 292 293 /* 294 * Read packet from RX queues //收包模块 295 */ 296 for (i = 0; i < qconf->n_rx_port; i++) { 297 //获取portID 298 portid = qconf->rx_port_list[i]; 299 //从portID对应的Port收到nb_rx个包 300 nb_rx = rte_eth_rx_burst((uint8_t) portid, 0, 301 pkts_burst, MAX_PKT_BURST); 302 303 port_statistics[portid].rx += nb_rx; //收包计数+=nb_rx 304 305 for (j = 0; j < nb_rx; j++) { //遍历收到的包 306 m = pkts_burst[j]; 307 rte_prefetch0(rte_pktmbuf_mtod(m, void *)); //预取到pktmbuf 308 l2fwd_simple_forward(m, portid); //调用函数,处理收到的包 309 } 310 } 311 } 312 } 313 314 //__attribute__((unused))表示可能没有这个参数,编译器不会报错 315 static int 316 l2fwd_launch_one_lcore(__attribute__((unused)) void *dummy) 317 { 318 l2fwd_main_loop(); //二层转发主要循环 319 return 0; 320 } 321 322 /* display usage */ 323 static void 324 l2fwd_usage(const char *prgname) 325 { 326 printf("%s [EAL options] -- -p PORTMASK [-q NQ]\n" 327 " -p PORTMASK: hexadecimal bitmask of ports to configure\n" 328 " -q NQ: number of queue (=ports) per lcore (default is 1)\n" 329 " -T PERIOD: statistics will be refreshed each PERIOD seconds (0 to disable, 10 default, 86400 maximum)\n" 330 " --[no-]mac-updating: Enable or disable MAC addresses updating (enabled by default)\n" 331 " When enabled:\n" 332 " - The source MAC address is replaced by the TX port MAC address\n" 333 " - The destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID\n", 334 prgname); 335 } 336 337 static int 338 l2fwd_parse_portmask(const char *portmask) 339 { 340 char *end = NULL; 341 unsigned long pm; 342 343 /* parse hexadecimal string */ 344 //解析十六进制字符串,将十六进制字符串转换为unsigned long 345 pm = strtoul(portmask, &end, 16); 346 if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0')) 347 return -1; 348 349 if (pm == 0) 350 return -1; 351 352 return pm; 353 } 354 355 static unsigned int 356 l2fwd_parse_nqueue(const char *q_arg) 357 { 358 char *end = NULL; 359 unsigned long n; 360 361 /* parse hexadecimal string */ 362 n = strtoul(q_arg, &end, 10); 363 if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0')) 364 return 0; 365 if (n == 0) 366 return 0; 367 if (n >= MAX_RX_QUEUE_PER_LCORE) 368 return 0; 369 370 return n; 371 } 372 373 static int 374 l2fwd_parse_timer_period(const char *q_arg) 375 { 376 char *end = NULL; 377 int n; 378 379 /* parse number string */ 380 n = strtol(q_arg, &end, 10); 381 if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0')) 382 return -1; 383 if (n >= MAX_TIMER_PERIOD) 384 return -1; 385 386 return n; 387 } 388 389 //解析命令行参数 390 /* Parse the argument given in the command line of the application */ 391 static int 392 l2fwd_parse_args(int argc, char **argv) 393 { 394 int opt, ret, timer_secs; 395 char **argvopt; 396 int option_index; 397 char *prgname = argv[0]; 398 static struct option lgopts[] = { 399 { "mac-updating", no_argument, &mac_updating, 1}, 400 { "no-mac-updating", no_argument, &mac_updating, 0}, 401 {NULL, 0, 0, 0} 402 }; 403 404 argvopt = argv; //复制argv指针 405 406 //解析命令行,getopt_long()可以解析命令行参数 407 while ((opt = getopt_long(argc, argvopt, "p:q:T:", 408 lgopts, &option_index)) != EOF) { 409 410 //命令行有三个参数, p:portmask,n:nqueue,T:timer period 411 switch (opt) { 412 /* portmask */ 413 //port的个数,以十六进制表示的,如0x0f表示15 414 case 'p': 415 //l2fwd_parse_portmask()函数为自定义函数 416 //把十六进制的mask字符串转换成unsigned long。 417 //端口使能情况 418 l2fwd_enabled_port_mask = l2fwd_parse_portmask(optarg); 419 if (l2fwd_enabled_port_mask == 0) { //mask为0表示出错 420 printf("invalid portmask\n"); 421 l2fwd_usage(prgname); //打印用户选项,类似于 --help 422 return -1; //参数传递错误,会返回-1,其结果是退出程序 423 } 424 break; 425 426 /* nqueue */ 427 //队列的个数,同样也是用十六进制字符串表示的。 428 case 'q': 429 //每个核心配置几个 rx 队列。 430 l2fwd_rx_queue_per_lcore = l2fwd_parse_nqueue(optarg); 431 if (l2fwd_rx_queue_per_lcore == 0) { //lcore 0表示出错 432 printf("invalid queue number\n"); 433 l2fwd_usage(prgname); 434 return -1; //返回-1,其结果是退出程序。 435 } 436 break; 437 438 /* timer period */ //定时器周期 439 440 case 'T': 441 //配置定时器周期,单位为秒 442 //即l2fwd运行多少秒,使用十六进制表示的。 443 timer_secs = l2fwd_parse_timer_period(optarg); 444 if (timer_secs < 0) { 445 printf("invalid timer period\n"); 446 l2fwd_usage(prgname); 447 return -1; 448 } 449 //timer_period表示二层转发测试时间,默认为10秒,可通过-T来调节时间 450 timer_period = timer_secs; 451 break; 452 453 /* long options */ 454 //--打头的选项不处理 455 case 0: 456 break; 457 458 default: 459 //参数传递错误,打印--help 460 l2fwd_usage(prgname); 461 return -1; 462 } 463 } 464 465 if (optind >= 0) 466 argv[optind-1] = prgname; 467 468 ret = optind-1; 469 optind = 0; /* reset getopt lib */ 470 return ret; 471 } 472 473 /* Check the link status of all ports in up to 9s, and print them finally */ 474 static void 475 check_all_ports_link_status(uint8_t port_num, uint32_t port_mask) 476 { 477 #define CHECK_INTERVAL 100 /* 100ms */ 478 #define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */ 479 uint8_t portid, count, all_ports_up, print_flag = 0; 480 struct rte_eth_link link; 481 482 printf("\nChecking link status"); 483 fflush(stdout); 484 for (count = 0; count <= MAX_CHECK_TIME; count++) { 485 if (force_quit) 486 return; 487 all_ports_up = 1; 488 for (portid = 0; portid < port_num; portid++) { 489 if (force_quit) 490 return; 491 if ((port_mask & (1 << portid)) == 0) 492 continue; 493 memset(&link, 0, sizeof(link)); 494 rte_eth_link_get_nowait(portid, &link); 495 /* print link status if flag set */ 496 if (print_flag == 1) { 497 if (link.link_status) 498 printf("Port %d Link Up - speed %u " 499 "Mbps - %s\n", (uint8_t)portid, 500 (unsigned)link.link_speed, 501 (link.link_duplex == ETH_LINK_FULL_DUPLEX) ? 502 ("full-duplex") : ("half-duplex\n")); 503 else 504 printf("Port %d Link Down\n", 505 (uint8_t)portid); 506 continue; 507 } 508 /* clear all_ports_up flag if any link down */ 509 if (link.link_status == ETH_LINK_DOWN) { 510 all_ports_up = 0; 511 break; 512 } 513 } 514 /* after finally printing all link status, get out */ 515 if (print_flag == 1) 516 break; 517 518 if (all_ports_up == 0) { 519 printf("."); 520 fflush(stdout); 521 rte_delay_ms(CHECK_INTERVAL); 522 } 523 524 /* set the print_flag if all ports up or timeout */ 525 if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) { 526 print_flag = 1; 527 printf("done\n"); 528 } 529 } 530 } 531 532 static void 533 signal_handler(int signum) 534 { 535 if (signum == SIGINT || signum == SIGTERM) { 536 printf("\n\nSignal %d received, preparing to exit...\n", 537 signum); 538 force_quit = true; //强制退出按钮为真 539 } 540 } 541 542 int 543 main(int argc, char **argv) 544 { 545 /* 546 //程序前面自定义的结构 547 // __rte_cache_aligned 是一个宏,表示内存对其 548 struct lcore_queue_conf { 549 unsigned n_rx_port; //rx_port个数 550 unsigned rx_port_list[MAX_RX_QUEUE_PER_LCORE]; //rx_port列表 551 } __rte_cache_aligned; 552 struct lcore_queue_conf lcore_queue_conf[RTE_MAX_LCORE]; 553 //一个计算机,包含多个核心,一个核心处理多个rx_port 554 */ 555 struct lcore_queue_conf *qconf; 556 557 558 struct rte_eth_dev_info dev_info; //设备信息 559 int ret; //返回值 560 uint8_t nb_ports; //总port个数 561 uint8_t nb_ports_available; //可用port个数 562 uint8_t portid, last_port; //当前portid,前一个portid。 563 unsigned lcore_id, rx_lcore_id; //核心id,rx_lcore_id 564 unsigned nb_ports_in_mask = 0; //?? 565 566 /* init EAL */ 567 //初始化环境 568 ret = rte_eal_init(argc, argv); 569 if (ret < 0) 570 rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n"); 571 argc -= ret; //??未知,貌似没用,不明所以 572 argv += ret; //??未知,貌似没用,不明所以 573 574 force_quit = false; //强制推出按钮为假 575 //当捕获到SIGINT或SIGTERM信号时,强制推出按钮为真,详见signal_handler 576 //signal_handler为自定义函数 577 signal(SIGINT, signal_handler); //信号捕获处理1 578 signal(SIGTERM, signal_handler); //信号捕获处理2 579 580 /* parse application arguments (after the EAL ones) */ 581 //解析传参(必须在eal_init之后),此为自定义函数,详见函数定义 582 ret = l2fwd_parse_args(argc, argv); 583 if (ret < 0) 584 rte_exit(EXIT_FAILURE, "Invalid L2FWD arguments\n"); 585 586 printf("MAC updating %s\n", mac_updating ? "enabled" : "disabled"); 587 588 /* convert to number of cycles */ 589 //获取定时器的时钟周期 590 timer_period *= rte_get_timer_hz(); 591 592 /* create the mbuf pool */ 593 //创建缓存池,用于存储数据包。 594 l2fwd_pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF, 595 MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, 596 rte_socket_id()); 597 if (l2fwd_pktmbuf_pool == NULL) 598 rte_exit(EXIT_FAILURE, "Cannot init mbuf pool\n"); 599 600 //获取Eth口的个数 601 nb_ports = rte_eth_dev_count(); 602 if (nb_ports == 0) 603 rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n"); 604 605 /* reset l2fwd_dst_ports */ 606 //复位(清0)目的端口,此代码块作用是初始化l2fwd_dst_ports 607 //个人认为写的比较low 608 for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) 609 l2fwd_dst_ports[portid] = 0; 610 last_port = 0; 611 612 /* 613 * Each logical core is assigned a dedicated TX queue on each port. 614 */ 615 //设置每个 port 接收的数据包要 转发 到的目的port。 616 for (portid = 0; portid < nb_ports; portid++) { 617 /* skip ports that are not enabled */ 618 //跳过没使能的端口 619 if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) 620 continue; 621 622 //每两个port为一对,互相作二层转发 623 //单数发到单数+1,双数发到双数-1 624 if (nb_ports_in_mask % 2) { 625 l2fwd_dst_ports[portid] = last_port; 626 l2fwd_dst_ports[last_port] = portid; 627 } 628 else 629 last_port = portid; //对last_port赋值。 630 631 nb_ports_in_mask++; //对标记 ++ 632 633 rte_eth_dev_info_get(portid, &dev_info); //获取对应portid的dev信息 634 } 635 //如果剩下一个网口,则该网口自己转发给自己 636 if (nb_ports_in_mask % 2) { 637 printf("Notice: odd number of ports in portmask.\n"); 638 l2fwd_dst_ports[last_port] = last_port; 639 } 640 641 rx_lcore_id = 0; 642 qconf = NULL; 643 644 //初始化每个核心的队列配置 645 /* Initialize the port/queue configuration of each logical core */ 646 //遍历所有port 647 for (portid = 0; portid < nb_ports; portid++) { 648 /* skip ports that are not enabled */ 649 //跳过没使能的端口 650 if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) 651 continue; 652 653 /* get the lcore_id for this port */ 654 //port分配一个 lcore_id。 655 //rte_lcore_is_enabled(rx_lcore_id) 测试core是否使能 656 //lcore_queue_conf是一个自定义的结构,包含port个数和portid 657 //意思是一个核心,对多个port的rx队列 658 //在这里,一个核心只能匹配一个port 659 while (rte_lcore_is_enabled(rx_lcore_id) == 0 || 660 lcore_queue_conf[rx_lcore_id].n_rx_port == 661 l2fwd_rx_queue_per_lcore) { 662 rx_lcore_id++; 663 if (rx_lcore_id >= RTE_MAX_LCORE) 664 rte_exit(EXIT_FAILURE, "Not enough cores\n"); 665 } 666 667 //赋值qconf,qconf最开始为NULL 668 if (qconf != &lcore_queue_conf[rx_lcore_id]) 669 /* Assigned a new logical core in the loop above. */ 670 qconf = &lcore_queue_conf[rx_lcore_id]; 671 672 //当前lcore添加一个port 673 //当前lcore管理的rx_port总数+1 674 qconf->rx_port_list[qconf->n_rx_port] = portid; 675 qconf->n_rx_port++; 676 printf("Lcore %u: RX port %u\n", rx_lcore_id, (unsigned) portid); 677 } 678 679 //使能的port总数 680 nb_ports_available = nb_ports; 681 682 /* Initialise each port */ 683 //获取可用的port个数 684 for (portid = 0; portid < nb_ports; portid++) { 685 /* skip ports that are not enabled */ 686 //不可用的会剪掉。 687 if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) { 688 printf("Skipping disabled port %u\n", (unsigned) portid); 689 nb_ports_available--; 690 continue; 691 } 692 /* init port */ 693 //初始化port 694 printf("Initializing port %u... ", (unsigned) portid); 695 fflush(stdout); 696 //配置port,其中port_conf是一个结构,包含关于port的配置 697 //vlan分离,crc硬件校验,认为这两个有用。 698 ret = rte_eth_dev_configure(portid, 1, 1, &port_conf); 699 if (ret < 0) 700 rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%u\n", 701 ret, (unsigned) portid); 702 703 //获取port的mac地址 704 //l2fwd_ports_eth_addr[] 的 类型为一个结构,是全局数组 705 rte_eth_macaddr_get(portid,&l2fwd_ports_eth_addr[portid]); 706 707 /* init one RX queue */ 708 //初始化RX队列 709 fflush(stdout); 710 //rx队列会绑定到当前port上,l2fwd_pktmbuf_pool为内存池 711 ret = rte_eth_rx_queue_setup(portid, 0, nb_rxd, 712 rte_eth_dev_socket_id(portid), 713 NULL, 714 l2fwd_pktmbuf_pool); 715 if (ret < 0) 716 rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup:err=%d, port=%u\n", 717 ret, (unsigned) portid); 718 719 /* init one TX queue on each port */ 720 //初始化tx队列 721 fflush(stdout); 722 //tx队列会绑定到port上 723 ret = rte_eth_tx_queue_setup(portid, 0, nb_txd, 724 rte_eth_dev_socket_id(portid), 725 NULL); 726 if (ret < 0) 727 rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup:err=%d, port=%u\n", 728 ret, (unsigned) portid); 729 730 /* Initialize TX buffers */ 731 //malloc tx缓存,tx_buffer为指针数组,每个port对应一个tx缓存 732 tx_buffer[portid] = rte_zmalloc_socket("tx_buffer", 733 RTE_ETH_TX_BUFFER_SIZE(MAX_PKT_BURST), 0, 734 rte_eth_dev_socket_id(portid)); 735 if (tx_buffer[portid] == NULL) 736 rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx on port %u\n", 737 (unsigned) portid); 738 739 //初始化tx缓存 740 rte_eth_tx_buffer_init(tx_buffer[portid], MAX_PKT_BURST); 741 742 //tx缓存满了的时候,设置回调函数 743 ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[portid], 744 rte_eth_tx_buffer_count_callback, 745 &port_statistics[portid].dropped); 746 if (ret < 0) 747 rte_exit(EXIT_FAILURE, "Cannot set error callback for " 748 "tx buffer on port %u\n", (unsigned) portid); 749 750 /* Start device */ 751 //启动设备 752 ret = rte_eth_dev_start(portid); 753 if (ret < 0) 754 rte_exit(EXIT_FAILURE, "rte_eth_dev_start:err=%d, port=%u\n", 755 ret, (unsigned) portid); 756 757 printf("done: \n"); 758 759 //设置网卡为混杂模式 760 rte_eth_promiscuous_enable(portid); 761 762 //打印port的MAC地址 763 printf("Port %u, MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n\n", 764 (unsigned) portid, 765 l2fwd_ports_eth_addr[portid].addr_bytes[0], 766 l2fwd_ports_eth_addr[portid].addr_bytes[1], 767 l2fwd_ports_eth_addr[portid].addr_bytes[2], 768 l2fwd_ports_eth_addr[portid].addr_bytes[3], 769 l2fwd_ports_eth_addr[portid].addr_bytes[4], 770 l2fwd_ports_eth_addr[portid].addr_bytes[5]); 771 772 /* initialize port stats */ 773 //初始化port属性 774 memset(&port_statistics, 0, sizeof(port_statistics)); 775 } 776 777 //如果可以使能的网卡为0个,则报错。 778 if (!nb_ports_available) { 779 rte_exit(EXIT_FAILURE, 780 "All available ports are disabled. Please set portmask.\n"); 781 } 782 783 //检查所有port的link属性 784 check_all_ports_link_status(nb_ports, l2fwd_enabled_port_mask); 785 786 ret = 0; 787 /* launch per-lcore init on every lcore */ 788 //启动所有的lcore,回调l2fwd_launch_one_lcore函数,参数为NULL 789 rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, NULL, CALL_MASTER); 790 //遍历所有lcore_id 791 RTE_LCORE_FOREACH_SLAVE(lcore_id) { 792 //等所有线程结束 793 if (rte_eal_wait_lcore(lcore_id) < 0) { 794 ret = -1; 795 break; 796 } 797 } 798 799 //停止并且关闭所有的port 800 for (portid = 0; portid < nb_ports; portid++) { 801 if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) 802 continue; 803 printf("Closing port %d...", portid); 804 rte_eth_dev_stop(portid); 805 rte_eth_dev_close(portid); 806 printf(" Done\n"); 807 } 808 printf("Bye...\n"); 809 810 return ret; 811 }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步