摘自:https://www.jianshu.com/p/86af81a10195
1. DPDK技術介紹
1) 簡介
DPDK全稱Intel Data Plane Development Kit,是intel提供的數據平面開發工具集,為Intel architecture(IA)處理器架構下用戶空間高效的數據包處理提供庫函數和驅動的支持。通俗地說,就是一個用來進行包數據處理加速的軟件庫。
DPDK不同於Linux系統以通用性設計為目的,而是專注於網絡應用中數據包的高性能處理。具體體現在DPDK應用程序是運行在用戶空間上利用自身提供的數據平面庫來收發數據包,繞過了Linux內核協議棧對數據包處理過程。它不是一個用戶可以直接建立應用程序的完整產品,不包含需要與控制層(包括內核和協議堆棧)進行交互的工具。
相比原生 Linux(Native Linux),采用Intel DPDK技術后能夠大幅提升IPV4的轉發性能,可以讓用戶在遷移包處理應用時(從基於NPU的硬件遷移到基於Intel x86的平台上),獲得更好的成本和性能優勢。同時可以采用統一的平台部署不同的服務,如應用處理,控制處理和包處理服務。
2) 技術優點
通過UIO技術將報文拷貝到應用空間處理,規避不必要的內存拷貝和系統調用,便於快速迭代優化。
通過大頁內存HUGEPAGE,降低cache miss(訪存開銷),利用內存多通道交錯訪問提高內存訪問有效帶寬,即提高命中率,進而提高cpu訪問速度。
通過CPU親和性,綁定網卡和線程到固定的core,減少cpu任務切換。特定任務可以被指定只在某個核上工作,避免線程在不同核間頻繁切換,保證更多的cache命中。
通過無鎖隊列,減少資源競爭。cache行對齊,預取數據,多元數據批量操作。
通過輪詢可在包處理時避免中斷上下文切換的開銷。
3) DPDK、網卡、用戶應用程序、內核之間的關系
PMD:Pool Mode Driver,輪詢模式驅動,通過非中斷,以及數據幀進出應用緩沖區內存的零拷貝機制,提高發送/接受數據幀的效率。
流分類:Flow Classification,為N元組匹配和LPM(最長前綴匹配)提供優化的查找算法。
環隊列:Ring Queue,針對單個或多個數據包生產者、單個數據包消費者的出入隊列提供無鎖機制,有效減少系統開銷。
MBUF緩沖區管理:分配內存創建緩沖區,並通過建立MBUF對象,封裝實際數據幀,供應用程序使用。
EAL:Environment Abstract Layer,環境抽象(適配)層,PMD初始化、CPU內核和DPDK線程配置/綁定、設置HugePage大頁內存等系統初始化。
2. 源程序包組成1) Makefile &&CONFIG
MakeFile文件主要位於位於 $(RTE_SDK)/mk 中。此處留在后面第5節進行討論
配置模板位於 $(RTE_SDK)/config。這些模板描述了為每個目標啟用的選項。 配置文件許多可以為DPDK庫啟用或禁用的選項,包括調試選項。用戶應該查看配置文件並熟悉這些選項。配置文件同樣也用於創建頭文件,創建的頭文件將位於新生成的目錄中。一般可以根據用戶編譯的編譯器和操作系統來直接選擇配置項。
2) Lib庫
庫文件源碼位於目錄$(RTE_SDK)/lib中。按照慣例,庫指的是為應用程序提供API的任何代碼。通常,它會生成一個(.a)文件,這個目錄中可能也保存一些內核模塊。
Lib常用庫文件包含以下內容
lib
+-- librte_cmdline # 命令行接口
+-- librte_distributor # 報文分發器
+-- librte_eal # 環境抽象層
+-- librte_ether # PMD通用接口
+-- librte_hash # 哈希庫
+-- librte_ip_frag # IP分片庫
+-- librte_kni # 內核NIC接口
+-- librte_kvargs # 參數解析庫
+-- librte_lpm # 最長前綴匹配庫
+-- librte_mbuf # 報文及控制緩沖區操作庫
+-- librte_mempool # 內存池管理器
+-- librte_meter # QoS metering 庫
+-- librte_net # IP相關的一些頭部
+-- librte_power # 電源管理庫
+-- librte_ring # 軟件無鎖環形緩沖區
+-- librte_sched # QoS調度器和丟包器庫
+-- librte_timer # 定時器庫
3) 應用程序
應用程序是包含 main() 函數的源文件。 他們位於 $(RTE_SDK)/app 和 $(RTE_SDK)/examples 目錄中。
常用示例文件:
examples
+-- cmdline # Example of using the cmdline library
+-- exception_path # Sending packets to and from Linux TAP device
+-- helloworld # Basic Hello World example
+-- ip_reassembly # Example showing IP reassembly
+-- ip_fragmentation # Example showing IPv4 fragmentation
+-- ipv4_multicast # Example showing IPv4 multicast
+-- kni # Kernel NIC Interface (KNI) example
+-- l2fwd # L2 forwarding with and without SR-IOV
+-- l3fwd # L3 forwarding example
+-- l3fwd-power # L3 forwarding example with power management
+-- l3fwd-vf # L3 forwarding example with SR-IOV
+-- link_status_interrupt # Link status change interrupt example
+-- load_balancer # Load balancing across multiple cores/sockets
+-- multi_process # Example apps using multiple DPDK processes
+-- qos_meter # QoS metering example
+-- qos_sched # QoS scheduler and dropper example
+-- timer # Example of using librte_timer library
+-- vmdq_dcb # Example of VMDQ and DCB receiving
+-- vmdq # Example of VMDQ receiving
+-- vhost # Example of userspace vhost and switch
3. DPDK架構分析

4. Dpdk基礎庫介紹
1) EAL 環境適配層
環境抽象層為底層資源如硬件和內存空間的訪問提供了接口。 這些通用的接口為APP和庫隱藏了不同環境的特殊性。 EAL負責初始化及分配資源(內存、PCI設備、定時器、控制台等等)。
典型函數:rte_eal_init
抄自dpdk網站:
EAL提供的典型服務有:
• DPDK的加載和啟動:DPDK和指定的程序鏈接成一個獨立的進程,並以某種方式加載
• CPU親和性和分配處理:DPDK提供機制將執行單元綁定到特定的核上,就像創建一個執行程序一樣。
• 系統內存分配:EAL實現了不同區域內存的分配,例如為設備接口提供了物理內存。
• PCI地址抽象:EAL提供了對PCI地址空間的訪問接口
• 跟蹤調試功能:日志信息,堆棧打印、異常掛起等等。
• 公用功能:提供了標准libc不提供的自旋鎖、原子計數器等。
• CPU特征辨識:用於決定CPU運行時的一些特殊功能,決定當前CPU支持的特性,以便編譯對應的二進制文件。
• 中斷處理:提供接口用於向中斷注冊/解注冊回掉函數。
• 告警功能:提供接口用於設置/取消指定時間環境下運行的毀掉函數。
2) Ring 庫
環形緩沖區支持隊列管理。rte_ring並不是具有無限大小的鏈表,它具有如下屬性:
先進先出(FIFO)
最大大小固定,指針存儲在表中
無鎖實現
多消費者或單消費者出隊操作
多生產者或單生產者入隊操作
批量出隊 - 如果成功,將指定數量的元素出隊,否則什么也不做
批量入隊 - 如果成功,將指定數量的元素入隊,否則什么也不做
突發出隊 - 如果指定的數目出隊失敗,則將最大可用數目對象出隊
突發入隊 - 如果指定的數目入隊失敗,則將最大可入隊數目對象入隊
單生產者入隊:

單消費者出隊

3) Mempool 庫
DPDK提供了內存池機制,使得內存的管理的使用更加簡單安全。在設計大的數據結構時,都可以使用mempool分配內存,同時,mempool也提供了內存的獲取和釋放等操作接口。對於數據包mempool甚至提供了更加詳細的接口-rte_pktmbuf_pool_create()
mempool的創建
內存池的創建使用的接口是rte_mempool_create()。在仔細分析代碼之前,先說明一下mempool的設計思路:在DPDK中,總體來說,mempool的組織是通過3個部分實現的
mempool頭結構。mempool由名字區分,掛接在struct rte_tailq_elem rte_mempool_tailq全局隊列中,可以根據mempool的名字進行查找,使用rte_mempool_lookup()接口即可。這只是個mempool的指示結構,mempool分配的內存區並不在這里面,只是通過物理和虛擬地址指向實際的內存地址。
mempool的實際空間。這就是通過內存分配出來的地址連續的空間,用來存儲mempool的obj對象。主要利用rte_mempool_populate_default()進行創建
ring隊列。其作用就是存放mempool中的對象指針,提供了方便存取使用mempool的空間的辦法。
mempool的常見使用是獲取元素空間和釋放空間。
rte_mempool_get可以獲得池中的元素,其實就是從ring取出可用元素的地址。
rte_mempool_put可以釋放元素到池中。
rte_mempool_in_use_count查看池中已經使用的元素個數
rte_mempool_avail_count 查看池中可以使用的元素個數
4) 定時器庫
定時器庫為DPDK執行單元提供定時器服務,使得執行單元可以為異步操作執行回調函數。定時器庫的特性如下:
定時器可以周期執行,也可以執行一次。
need-to-insert-img
定時器可以在一個核心加載並在另一個核心執行。但是必須在調用rte_timer_reset()中指定它。
定時器提供高精度(取決於檢查本地核心的定時器到期的rte_timer_manage()的調用頻率)。
如果應用程序不需要,可以在編譯時禁用定時器,並且程序中不調用rte_timer_manage()來提高性能。
具體使用參考:http://blog.csdn.net/linzhaolover/article/details/9410529
5. 庫的編譯(Makefile)及使用(API使用方案)
need-to-insert-img
makefile位於目錄: /examples/xxx/Makefile。 文件中真正必須的為兩個變量APP和SRCS-y,前者為示例程序編譯生成的目標文件名稱,后者為要編譯的源文件。
另外兩個必須的為makefile文件rte.vars.mk和rte.extapp.mk。前者定義一些全局的編譯打包鏈接用到的選項,如CFLAGS、ASFLAGS、LDFLAGS等變量,頭文件位置和庫路徑等和體系架構相關編譯選項。后者rte.extapp.mk內部又包含了重要的mk/rte.app.mk文件,首先變量LDLIBS初始化為DPDK核心編譯生成的所有靜態庫文件。
makefile編寫
在DPDK中,Makefiles的套路是
1.在文件開頭,包含$(RTE_SDK)/mk/rte.vars.mk
2.設置RTE構建系統的變量,比如設置RTE_SDK和RTE_TARGET環境變量
3.包含指定的 $(RTE_SDK)/mk/rte.XYZ.mk,其中XYZ 可以填寫app, lib, extapp, extlib, obj,依賴構建的目標類型.
3.1 應用程序類型(application)
- rte.app.mk
- rte.extapp.mk
- rte.hostapp.mk
3.2 庫類型 (library)
- rte.lib.mk
- rte.extlib.mk
- rte.hostlib.mk
3.3 安裝類型(install)
rte.install.mk
沒有生成任何文件,僅僅用於創建鏈接和將文件拷貝到安裝目錄.
3.4 內核模塊(Kernel Module )
rte.module.mk
用於構建內核模塊,在dpdk開發包框架中.
3.5 Objects類型
- rte.obj.mk
- rte.extobj.mk
3.6 Misc類型
- rte.doc.mk
- rte.gnuconfigure.mk
- rte.subdir.mk
4.包含用戶自定義的規則和變量
常用的變量
系統構建變量
RTE_SDK
RTE_TARGET
編譯變量(庫文件,庫目錄,C編譯標志,C++編譯標志)
CFLAGS: C編譯標志. 使用 += 為變量添加內容
LDFLAGS: 鏈接標志
need-to-insert-img
CPPFLAGS: C++編譯標志. 使用 += 為變量添加內容
LDLIBS: 待添加的庫文件的列表
SRC-y: 源文件列表
警告變量
WERROR_CFLAGS:在dpdk示例makefile中是加上此標志的.
CFLAGS += $(WERROR_CFLAGS)
6. 性能優化1) 內存
內存拷貝:不要在數據面程序中使用libc
通過Linux應用程序環境,DPDK中可以使用許多libc函數。 這可以簡化應用程序的移植和控制平面的開發。 但是,這些功能中有許多不是為了性能而設計的。 諸如memcpy() 或 strcpy() 之類的函數不應該在數據平面中使用。 要復制小型結構體,首選方法是編譯器可以優化一個更簡單的技術。
對於經常調用的特定函數,提供一個自制的優化函數也是一個好主意,該函數應聲明為靜態內聯。DPDK API提供了一個優化的rte_memcpy() 函數。
內存申請
need-to-insert-img
libc的其他功能,如malloc(),提供了一種靈活的方式來分配和釋放內存。 在某些情況下,使用動態分配是必要的,但是建議不要在數據層面使用類似malloc的函數,因為管理碎片堆可能代價高昂,並且分配器可能無法針對並行分配進行優化。
如果您確實需要在數據平面中進行動態分配,最好使用固定大小對象的內存池。 這個API由librte_mempool提供。 這個數據結構提供了一些提高性能的服務,比如對象的內存對齊,對對象的無鎖訪問,NUMA感知,批量get/put和percore緩存。 rte_malloc() 函數對mempools使用類似的概念。
內存區域的並發訪問
need-to-insert-img
幾個lcore對同一個內存區域進行的讀寫(RW)訪問操作可能會產生大量的數據高速緩存未命中,這代價非常昂貴。 通常可以使用per-lcore變量來解決這類問題。例如,在統計的情況下。 至少有兩個解決方案:
使用 RTE_PER_LCORE 變量。注意,在這種情況下,處於lcore x的數據在lcore y上是無效的。
使用一個表結構(每個lcore一個)。在這種情況下,每個結構都必須緩存對齊。
如果在同一緩存行中沒有RW變量,那么讀取主要變量可以在不損失性能的情況下在內核之間共享。
NUMA
need-to-insert-img
在NUMA系統上,由於遠程內存訪問速度較慢,所以最好訪問本地內存。 在DPDK中,memzone,ring,rte_malloc和mempool API提供了在特定內存槽上創建內存池的方法。
有時候,復制數據以優化速度可能是一個好主意。 對於經常訪問的大多數讀取變量,將它們保存在一個socket中應該不成問題,因為數據將存在於緩存中。
跨存儲器通道分配
need-to-insert-img
現代內存控制器具有許多內存通道,可以支持並行數據讀寫操作。 根據內存控制器及其配置,通道數量和內存在通道中的分布方式會有所不同。 每個通道都有帶寬限制,這意味着如果所有的存儲器訪問都在同一通道上完成,則存在潛在的性能瓶頸。
默認情況下, Mempool Library 分配對象在內存通道中的地址。
2) lcore之間的通信
need-to-insert-img
為了在內核之間提供基於消息的通信,建議使用提供無鎖環實現的DPDK ring API。
該環支持批量訪問和突發訪問,這意味着只需要一次昂貴的原子操作即可從環中讀取多個元素(請參閱 Ring 庫 )。
使用批量訪問操作時,性能會大大提高。
出隊消息的代碼算法可能類似於以下內容:
#define MAX_BULK 32
while (1) {
/* Process as many elements as can be dequeued. */
count = rte_ring_dequeue_burst(ring, obj_table, MAX_BULK, NULL);
if (unlikely(count == 0))
continue;
my_process_bulk(obj_table, count);
}
3) PMD 驅動
DPDK輪詢模式驅動程序(PMD)也能夠在批量/突發模式下工作,允許在發送或接收功能中對每個呼叫的一些代碼進行分解。
避免部分寫入。 當PCI設備通過DMA寫入系統存儲器時,如果寫入操作位於完全緩存行而不是部分寫入操作,則其花費較少。 在PMD代碼中,已采取了盡可能避免部分寫入的措施。
低報文延遲
need-to-insert-img
傳統上,吞吐量和延遲之間有一個折衷。 可以調整應用程序以實現高吞吐量,但平均數據包的端到端延遲通常會因此而增加。 類似地,可以將應用程序調整為平均具有低端到端延遲,但代價是較低的吞吐量。
為了實現更高的吞吐量,DPDK嘗試通過突發處理數據包來合並單獨處理每個數據包的成本。
以testpmd應用程序為例,突發大小可以在命令行上設置為16(也是默認值)。 這允許應用程序一次從PMD請求16個數據包。 然后,testpmd應用程序立即嘗試傳輸所有接收到的數據包,在這種情況下是全部16個數據包。
在網絡端口的相應的TX隊列上更新尾指針之前,不發送分組。 當調整高吞吐量時,這種行為是可取的,因為對RX和TX隊列的尾指針更新的成本可以分布在16個分組上, 有效地隱藏了寫入PCIe 設備的相對較慢的MMIO成本。 但是,當調優為低延遲時,這不是很理想,因為接收到的第一個數據包也必須等待另外15個數據包才能被接收。 直到其他15個數據包也被處理完畢才能被發送,因為直到TX尾指針被更新,NIC才知道要發送數據包,直到所有的16個數據包都被處理完畢才被發送。
為了始終如一地實現低延遲,即使在系統負載較重的情況下,應用程序開發人員也應避免處理數據包。 testpmd應用程序可以從命令行配置使用突發值1。 這將允許一次處理單個數據包,提供較低的延遲,但是增加了較低吞吐量的成本。
4) 鎖和原子操作
need-to-insert-img
原子操作意味着在指令之前有一個鎖定前綴,導致處理器的LOCK#信號在執行下一條指令時被斷言。 這對多核環境中的性能有很大的影響。
可以通過避免數據平面中的鎖定機制來提高性能。 它通常可以被其他解決方案所取代,比如percore變量。 而且,一些鎖定技術比其他鎖定技術更有效率。 例如,Read-Copy-Update(RCU)算法可以經常替換簡單的rwlock
7. 遇到的問題及解決方法
https://www.xuebuyuan.com/1149388.html
https://blog.csdn.net/hz5034/article/details/78811445