學習這個例子用於理解單純的 dpdk 轉發過程,L2 和 L3 的轉發是基於此:在rte_eth_rx_burst()收包後進行解包,提取 mac、ip 等信息然後在轉發到輸出網卡。
main 函數
main函數的內容:
- 初始化 EAL
- 以太網端口數需要爲偶數(在這個程序中一個口接收,往另一個口轉發)
- 分配mbuf
rte_pktmbuf_pool_create()
- 初始化所有端口 轉入
port_init(portid, mbuf_pool)
- 調用主線程開始basicfwd。
int main(int argc, char *argv[]) { struct rte_mempool *mbuf_pool; // 指向內存池結構的指針 unsigned nb_ports; // 網口個數 uint16_t portid; // 網口號 /* Initialize the Environment Abstraction Layer (EAL). */ // 初始化 EAL int ret = rte_eal_init(argc, argv); if (ret < 0) rte_exit(EXIT_FAILURE, "Error with EAL initialization\n"); //當解析完了EAL的參數之後,argc減去EAL參數的個數同時argv後移這麼多位, //這樣就能保證後面解析程序參數的時候跳過了前面的EAL參數。 argc -= ret; argv += ret; /* Check that there is an even number of ports to send/receive on. */ //以太網端口數需要爲偶數 nb_ports = rte_eth_dev_count_avail(); // 獲取當前可用以太網設備的總數 if (nb_ports < 2 || (nb_ports & 1)) // 檢查端口個數是否小於兩個或者是奇數,則出錯。 rte_exit(EXIT_FAILURE, "Error: number of ports must be even\n"); /* Creates a new mempool in memory to hold the mbufs. */ mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports, MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); //rte_socket_id()返回正在運行的lcore所對應的物理socket。socket的文檔在 lcore中 if (mbuf_pool == NULL) rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); /* Initialize all ports. 在每個網口上初始化*/ RTE_ETH_FOREACH_DEV(portid) if (port_init(portid, mbuf_pool) != 0) rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu16 "\n", portid); if (rte_lcore_count() > 1) // basicfwd只需要使用一個邏輯核 printf("\nWARNING: Too many lcores enabled. Only 1 used.\n"); /* Call lcore_main on the master core only. */ // 僅僅一個主線程調用 // 這個程序純粹地把一個網口收到的包從另一個網口轉發出去,就像是一個repeater,中間沒有其他任何處理。 lcore_main(); return 0; // dpdk用mbuf保存packet,mempool用於操作mbuf /* rte_pktmbuf_pool_create() 創建並初始化mbuf池,是 rte_mempool_create 這個函數的封裝。 五個參數: 1. mbuf的名字 "MBUF_POOL" 2. mbuf中的元素個數。每個端口給了8191個 3. 每個核心的緩存大小,如果該參數爲0 則可以禁用緩存。本程序中是250 4. 每個mbuf中的數據緩衝區大小 5. 應分配內存的套接字標識符。 返回值:分配成功時返回指向新分配的mempool的指針。 mempool的指針會傳給 port_init 函數,用於 setup rx queue */ }
端口初始化port_init(portid, mbuf_pool)
主要內容:
- 獲取可用 eth 的個數
- 配置網卡設備
- 每個 port 1 個 rx 隊列
- 每個 port 1 個 tx 隊列
- 啓用網卡設備
- 設置網卡混雜模式
static inline int port_init(uint16_t port, struct rte_mempool *mbuf_pool) { struct rte_eth_conf port_conf = port_conf_default; // 這個rte_eth_conf結構體在配置網卡時要用到 const uint16_t rx_rings = 1, tx_rings = 1; // 每個網口有多少rx和tx隊列,這裏都爲1 uint16_t nb_rxd = RX_RING_SIZE; // 接收環大小 uint16_t nb_txd = TX_RING_SIZE; // 發送環大小 int retval; uint16_t q; struct rte_eth_dev_info dev_info; // 用於獲取以太網設備的信息,setup queue 時用到 struct rte_eth_txconf txconf; // setup tx queue 時用到 if (!rte_eth_dev_is_valid_port(port)) // 檢查設備的port_id是否已連接 return -1; rte_eth_dev_info_get(port, &dev_info); //查詢以太網設備的信息,參數port指示以太網設備的網口標識符,第二個參數指向要填充信息的類型rte_eth_dev_info的結構的指針 if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_MBUF_FAST_FREE) port_conf.txmode.offloads |= DEV_TX_OFFLOAD_MBUF_FAST_FREE; /* Configure the Ethernet device. 配置網卡*/ // rte_eth_dev_configure() /* 四個參數 1. port id 2. 要給該網卡配置多少個收包隊列 這裏是一個 3. 要給該網卡配置多少個發包隊列 也是一個 4. 結構體指針類型 rte_eth_conf * */ retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf); if (retval != 0) return retval; //檢查Rx和Tx描述符的數量是否滿足以太網設備信息中的描述符限制,否則將它們調整爲邊界。 retval = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd); if (retval != 0) return retval; /* Allocate and set up 1 RX queue per Ethernet port. */ /* rte_eth_rx_queue_setup() 配置rx隊列需要六個參數 1. port id 2. 接收隊列的索引。要在[0, rx_queue - 1] 的範圍內(先前rte_eth_dev_configure中配置的) 3. 爲接收環分配的接收描述符數。(環的大小) 4. socket id。 如果是 NUMA 架構 就使用 rte_eth_dev_socket_id(port)獲取port所對應的以太網設備所連接上的socket的id;若不是NUMA,該值可以是宏SOCKET_ID_ANY 5. 指向rx queue的配置數據的指針。如果是NULL,則使用默認配置。 6. 指向內存池mempool的指針,從中分配mbuf去操作隊列。 */ for (q = 0; q < rx_rings; q++) { retval = rte_eth_rx_queue_setup(port, q, nb_rxd, rte_eth_dev_socket_id(port), NULL, mbuf_pool); if (retval < 0) return retval; } txconf = dev_info.default_txconf; txconf.offloads = port_conf.txmode.offloads; /* Allocate and set up 1 TX queue per Ethernet port. */ /* rte_eth_tx_queue_setup() 配置tx隊列需要五個參數(不需要mempool) 1. port id 2. 發送隊列的索引。要在[0, tx_queue - 1] 的範圍內(先前rte_eth_dev_configure中配置的) 3. 爲發送環分配的接收描述符數。(自定義環的大小) 4. socket id 5. 指向tx queue的配置數據的指針,結構體是rte_eth_txconf。 */ for (q = 0; q < tx_rings; q++) { retval = rte_eth_tx_queue_setup(port, q, nb_txd, rte_eth_dev_socket_id(port), &txconf); if (retval < 0) return retval; } /* Start the Ethernet port. 啓動設備*/ retval = rte_eth_dev_start(port); if (retval < 0) return retval; /* Display the port MAC address. */ struct ether_addr addr; rte_eth_macaddr_get(port, &addr); printf("Port %u MAC: %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 "\n", port, addr.addr_bytes[0], addr.addr_bytes[1], addr.addr_bytes[2], addr.addr_bytes[3], addr.addr_bytes[4], addr.addr_bytes[5]); /* Enable RX in promiscuous mode for the Ethernet device. */ rte_eth_promiscuous_enable(port); //設置網卡爲混雜模式 // 指一臺機器能夠接收所有經過它的數據流,而不論其目的地址是否是他。 return 0; }
在單核上調用lcore_main()
- 主要內容
- 檢查收發網卡是否在同一 NUMA 節點
- 死循環收發包,Ctrl+C 退出 :
- 從網卡讀包
- 發送到網卡
static __attribute__((noreturn)) void lcore_main(void) { uint16_t port; /* * Check that the port is on the same NUMA node as the polling thread * for best performance. */ // 當有NUMA結構時,檢查網口是否在同一個NUMA node節點上,只有在一個NUMA node上時線程輪詢的效率最好 RTE_ETH_FOREACH_DEV(port) if (rte_eth_dev_socket_id(port) > 0 && rte_eth_dev_socket_id(port) != (int)rte_socket_id()) printf("WARNING, port %u is on remote NUMA node to " "polling thread.\n\tPerformance will " "not be optimal.\n", port); printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n", rte_lcore_id()); /* Run until the application is quit or killed. */ for (;;) { /* * Receive packets on a port and forward them on the paired * port. The mapping is 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2, etc. */ /* 一個端口收到包,就立刻轉發到另一個端口 0 和 1 2 和 3 …… */ RTE_ETH_FOREACH_DEV(port) { /* Get burst of RX packets, from first port of pair. */ struct rte_mbuf *bufs[BURST_SIZE]; // mbuf的結構體 收到的包存在這裏,也是要發出去的包 const uint16_t nb_rx = rte_eth_rx_burst(port, 0, bufs, BURST_SIZE); if (unlikely(nb_rx == 0)) // 返回值是實際收到的數據包數 continue; /* Send burst of TX packets, to second port of pair. */ const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0, bufs, nb_rx);// port 異或 1 --> 0就和1是一對,2就和3是一對。 // 0 收到包就從 1 轉發, 3 收到包 就從 2 口轉發。 /* Free any unsent packets. */ //手動釋放沒有發送出去的 mbuf // 用unlikely宏代表這種情況不太可能出現? if (unlikely(nb_tx < nb_rx)) { uint16_t buf; for (buf = nb_tx; buf < nb_rx; buf++) rte_pktmbuf_free(bufs[buf]); } } } }
/* 收包函數:rte_eth_rx_burst 從以太網設備的接收隊列中檢索一連串(burst收發包機制)輸入數據包。檢索到的數據包存儲在rte_mbuf結構中。 參數四個 1. port id (收到哪個網口) 2. 隊列索引 (的哪一條隊列),範圍要在[0, rx_queue - 1] 的範圍內(rte_eth_dev_configure中的) 3. 指向 rte_mbuf 結構的 指針數組 的地址。要夠容納第四個參數所表示的數目的指針。(把收到的包存在哪裏?) 4. 要檢索的最大數據包數 rte_eth_rx_burst()是一個循環函數,從RX隊列中收包達到設定的最大數量爲止。 收包操作: 1. 根據NIC的RX描述符信息,初始化rte_mbuf數據結構。 2. 將rte_mbuf(也就是數據包)存儲到第三個參數所指示的數組的下一個條目。 3. 從mempool分配新的的rte_mbuf */ /* Send burst of TX packets, to second port of pair. */ /* 發包函數:rte_eth_tx_burst 在由port id指示的以太網設備的傳輸隊列(由索引指示)發送一連串輸出數據包。 參數四個: 1. port id(從哪個網口) 2. 隊列索引(的哪條隊列發出),範圍要在[0, tx_queue - 1] 的範圍內(rte_eth_dev_configure中的) 3. 指向包含要發送的數據包的 rte_mbuf 結構的 指針數組 的地址。(要發送的包的內容在哪裏) 4. 要發送的數據包的最大數量。 返回值是發送的包的數量。 發包操作: 1. 選擇發包隊列中下一個可用的描述符 2. 使用該描述符發送包,之後釋放對應的mempool空間 3. 再根據 *rte_mbuf 初始化發送描述符 */
注意: rte_eth_tx_burst() 發送成功的話會自動釋放 mbuf,對於沒成功的,需要手動進行釋放