天天看點

《深入淺出DPDK》—第1章1.7節執行個體

本節書摘來自華章出版社《深入淺出dpdk》一書中的第1章,第1.7節執行個體,作者朱河清,梁存銘,胡雪焜,曹水 等,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

1.7 執行個體

在對dpdk的原理和代碼展開進一步解析之前,先看一些小而簡單的例子,建立一個形象上的認知。

1)helloworld,啟動基礎運作環境,dpdk建構了一個基于作業系統的,但适合包處理的軟體運作環境,你可以認為這是個mini-os。最早期dpdk,可以完全運作在沒有作業系統的實體核(bare-metal)上,這部分代碼現在不在主流的開源包中。

2)skeleton,最精簡的單核封包收發骨架,也許這是目前世界上運作最快的封包進出測試程式。

3)l3fwd,三層轉發是dpdk用于釋出性能測試名額的主要應用。

1.7.1 helloworld

dpdk裡的helloworld是最基礎的入門程式,代碼簡短,功能也不複雜。它建立了一個多核(線程)運作的基礎環境,每個線程會列印“hello from core #”,core #是由作業系統管理的。如無特别說明,本文裡的dpdk線程與硬體線程是一一對應的關系。從代碼角度,rte是指runtime environment,eal是指environment abstraction layer。dpdk的主要對外函數接口都以rte_作為字首,抽象化函數接口是典型軟體設計思路,可以幫助dpdk運作在多個作業系統上,dpdk官方支援linux與freebsd。和多數并行處理系統類似,dpdk也有主線程、從線程的差異。

1.初始化基礎運作環境

主線程運作入口是main函數,調用了rte_eal_init入口函數,啟動基礎運作環境。

int rte_eal_init(int argc, char **argv);

入口參數是啟動dpdk的指令行,可以是長長的一串很複雜的設定,需要深入了解的讀者可以檢視dpdk相關的文檔與源代碼liblibrte_ealcommoneal_common_options.c。對于helloworld這個執行個體,最需要的參數是“-c ”,線程掩碼(core mask)指定了需要參與運作的線程(核)集合。rte_eal_init本身所完成的工作很複雜,它讀取入口參數,解析并儲存作為dpdk運作的系統資訊,依賴這些資訊,建構一個針對包處理設計的運作環境。主要動作分解如下

配置初始化

記憶體初始化

記憶體池初始化

隊列初始化

告警初始化

中斷初始化

pci初始化

定時器初始化

檢測記憶體本地化(numa)

插件初始化

主線程初始化

輪詢裝置初始化

建立主從線程通道

将從線程設定在等待模式

pci裝置的探測與初始化

對于dpdk庫的使用者,這些操作已經被eal封裝起來,接口清晰。如果需要對dpdk進行深度定制,二次開發,需要仔細研究内部操作,這裡不做詳解。

2.多核運作初始化

dpdk面向多核設計,程式會試圖獨占運作在邏輯核(lcore)上。main函數裡重要的是啟動多核運作環境,rte_lcore_foreach_slave(lcore_id)如名所示,周遊所有eal指定可以使用的lcore,然後通過rte_eal_remote_launch在每個lcore上,啟動被指定的線程。

int rte_eal_remote_launch(int (f)(void ),

第一個參數是從線程,是被征召的線程;

第二個參數是傳給從線程的參數;

第三個參數是指定的邏輯核,從線程會執行在這個core上。

具體來說,int rte_eal_remote_launch(lcore_hello, null, lcore_id);

參數lcore_id指定了從線程id,運作入口函數lcore_hello。

運作函數lcore_hello,它讀取自己的邏輯核編号(lcore_id),列印出“hello from core #”

這是個簡單示例,從線程很快就完成了指定工作,在更真實的場景裡,這個從線程會是一個循環運作的處理過程。

1.7.2 skeleton

dpdk為多核設計,但這是單核執行個體,設計初衷是實作一個最簡單的封包收發示例,對收入封包不做任何處理直接發送。整個代碼非常精簡,可以用于平台的單核封包出入性能測試。

主要處理函數main的處理邏輯如下(僞碼),調用rte_eal_init初始化運作環境,檢查網絡接口數,據此配置設定記憶體池rte_pktmbuf_pool_create,入口參數是指定rte_socket_id(),考慮了本地記憶體使用的範例。調用port_init(portid, mbuf_pool)初始化網口的配置,最後調用lcore_main()進行主處理流程。

網口初始化流程:

port_init(uint8_t port, struct rte_mempool *mbuf_pool)

首先對指定端口設定隊列數,基于簡單原則,本例隻指定單隊列。在收發兩個方向上,基于端口與隊列進行配置設定,緩沖區進行關聯設定。如不指定配置資訊,則使用預設配置。

網口設定:對指定端口設定接收、發送方向的隊列數目,依據配置資訊來指定端口功能

int rte_eth_dev_configure(uint8_t port_id, uint16_t nb_rx_q,

隊列初始化:對指定端口的某個隊列,指定記憶體、描述符數量、封包緩沖區,并且對隊列進行配置

網口設定:初始化配置結束後,啟動端口int rte_eth_dev_start(uint8_t port_id);

完成後,讀取mac位址,打開網卡的混雜模式設定,允許所有封包進入。

網口收發封包循環收發在lcore_main中有個簡單實作,因為是示例,為保證性能,首先檢測cpu與網卡的socket是否最優适配,建議使用本地cpu就近操作網卡,後續章節有詳細說明。資料收發循環非常簡單,為高速封包進出定義了burst的收發函數如下,4個參數意義非常直覺:端口,隊列,封包緩沖區以及收發包數。

基于端口隊列的封包收發函數:

這就構成了最基本的dpdk封包收發展示。可以看到,此處不涉及任何具體網卡形态,軟體接口對硬體沒有依賴。

1.7.3 l3fwd

這是dpdk中最流行的例子,也是釋出dpdk性能測試的例子。如果将pcie插槽上填滿高速網卡,将網口與大流量測試儀表連接配接,它能展示在雙路伺服器平台具備200gbit/s的轉發能力。資料包被收入系統後,會查詢ip封包頭部,依據目标位址進行路由查找,發現目的端口,修改ip頭部後,将封包從目的端口送出。路由查找有兩種方式,一種方式是基于目标ip位址的完全比對(exact match),另一種方式是基于路由表的最長掩碼比對(longest prefix match,lpm)。三層轉發的執行個體代碼檔案有2700多行(含空行與注釋行),整體邏輯其實很簡單,是前續helloworld與skeleton的結合體。

啟動這個例子,指定指令參數格式如下:

./build/l3fwd [eal options] -- -p portmask [-p]

--config(port,queue,lcore)[,(port,queue,lcore)]

指令參數分為兩個部分,以“--”為分界線,分界線右邊的參數是三層轉發的私有指令選項。左邊則是dpdk的eal options。

[eal options]是dpdk運作環境的輸入配置選項,輸入指令會交給rte_eal_init處理;

portmask依據掩碼選擇端口,dpdk啟動時會搜尋系統認識的pcie裝置,依據黑白名單原則來決定是否接管,早期版本可能會接管所有端口,斷開網絡連接配接。

config選項指定(port,queue,lcore),用指定線程處理對應的端口的隊列。要實作200gbit/s的轉發,需要大量線程(核)參與,并行轉發。

《深入淺出DPDK》—第1章1.7節執行個體

先來看主線程流程main的處理流程,因為和helloworld與skeleton類似,不詳細叙述。

初始化運作環境: rte_eal_init(argc, argv);

分析入參: parse_args(argc, argv)

初始化lcore與port配置

端口與隊列初始化,類似skeleton處理

端口啟動,使能混雜模式

啟動從線程,令其運作main_loop()

從線程執行main_loop()的主要步驟如下:

讀取自己的lcore資訊完成配置;

讀取關聯的接收與發送隊列資訊;

進入循環處理:

{

  向指定隊列批量發送封包;

  從指定隊列批量接收封包;

  批量轉發接收到封包;

}

向指定隊列批量發送封包,從指定隊列批量接收封包,此前已經介紹了dpdk的收發函數。批量轉發接收到的封包是處理的主體,提供了基于hash的完全比對轉發,也可以基于最長比對原則(lpm)進行轉發。轉發路由查找方式可以由編譯配置選擇。除了路由轉發算法的差異,下面的例子還包括基于multi buffer原理的代碼實作。在#if (enable_multi_buffer_optimize == 1)的路徑下,一次處理8個封包。和普通的軟體程式設計不同,初次見到的程式員會覺得奇怪。它的實作有效利用了處理器内部的亂序執行和并行處理能力,能顯著提高轉發性能。

依據ip頭部的五元組資訊,利用rte_hash_lookup來查詢目标端口。

這段代碼在讀取封包頭部資訊時,将整個頭部導入了基于sse的矢量寄存器(128位寬),并對内部進行了掩碼mask0運算,得到key,然後把key作為入口參數送入rte_hash_lookup運算。同樣的操作運算還展示在對ipv6的處理上,可以在代碼中參考。

我們并不計劃在本節将讀者帶入代碼陷阱中,實際上本書總體上也沒有偏重代碼講解,而是在原理上進行解析。如果讀者希望了解詳細完整的程式設計指南,可以參考dpdk的網站。

繼續閱讀