參考文獻:
《深入淺出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 }