轉載自
DPDK 技術分為基本技術和優化技術兩類。其中,前者名額準的 DPDK 資料平面開發包和 I/O 轉發實作技術。

核心協定棧(左邊):網卡 -> 驅動 -> 協定棧 -> Socket 接口 -> 業務。
DPDK 基于 UIO(User Space I/O)的核心旁路(右邊):網卡 -> DPDK 輪詢模式-> DPDK 基礎庫 -> 業務。
NOTE:說 DPDK 依賴網卡裝置不如說 DPDK 依賴的是網卡裝置對應的驅動程式。支援 DPDK 的 NIC Kernel Driver 可以轉換為 UIO Driver 模式。由此,如有需要,DPDK 實際上是可以在虛拟機上使用的,前提是網卡裝置通過 Passthrough 的方式給到虛拟機。是以,該場景中 SR-IOV 網卡會是一個不錯的選擇。
在最底部的核心态(Linux Kernel),DPDK 擁有兩個子產品:KNI 與 IGB_UIO。而 DPDK 的上層使用者态由很多庫組成,主要包括核心部件庫(Core Libraries)、平台相關子產品(Platform)、網卡輪詢模式驅動子產品(PMD-Natives&Virtual)、QoS 庫、封包轉發分類算法(Classify)等幾大類,使用者應用程式可以使用這些庫進行二次開發。下面我們逐一介紹這些元件的功能和作用。
傳統的收發資料包方式,首先網卡通過中斷方式通知核心協定棧對資料包進行處理,核心協定棧先會對資料包進行合法性進行必要的校驗,然後判斷資料包目标是否為本機的 Socket,滿足條件則會将資料包拷貝一份向上遞交到使用者态 Socket 來處理。不僅處理路徑冗長,還需要從核心到應用層的一次拷貝過程。
為了使得網卡驅動(e.g. PMD Driver)運作在使用者态,實作核心旁路。Linux 提供了 UIO(User Space I/O)機制。使用 UIO 可以通過 <code>read()</code> 感覺中斷,通過 <code>mmap()</code> 實作和網卡裝置的通訊。
簡單來說,UIO 是使用者态的一種 I/O 技術,DPDK 能夠繞過核心協定棧,提供了使用者态 PMD Driver 的支援,根本上是得益于 UIO 技術。DPDK 架構在 Linux 核心中安裝了 IGB_UIO(igb_uio.ko 和 kni.ko.IGB_UIO)子產品,以此借助 UIO 技術來截獲中斷,并重設中斷回調行為,進而繞過核心協定棧後續的處理流程。并且 IGB_UIO 會在核心初始化的過程中将網卡硬體寄存器映射到使用者态。
UIO 的實作機制是:對使用者态暴露檔案接口。當注冊一個 UIO 裝置 uioX 時,就會出現系統檔案 /dev/uioX,對該檔案的讀寫就是對網卡裝置記憶體的讀寫。除此之外,對網卡裝置的控制還可以通過 /sys/class/uio 下的各個檔案的讀寫來完成。如下圖:
此外,DPDK 還在使用者态實作了一套精巧的記憶體池技術,核心态和使用者态之間的的記憶體互動不進行拷貝,隻做控制權轉移。這樣,當收發資料包時,就減少了記憶體拷貝的開銷。
我們知道,Linux 核心在收包時有兩種方式可供選擇,一種是中斷方式,另外一種是輪詢方式。
從哲學的角度來說,中斷是外界強加給你的信号,你必須被動應對,而輪詢則是你主動地處理事情。前者最大的影響就是打斷你目前工作的連續性,而後者則不會,事務的安排自在掌握。
中斷對性能的影響有多大?在 x86 體系結構中,一次中斷處理需要将 CPU 的狀态寄存器儲存到堆棧,并運作中斷服務程式,最後再将儲存的狀态寄存器資訊從堆棧中恢複。整個過程需要至少 300 個處理器時鐘周期。
輪詢對性能的提升有多大?網卡收到封包後,可以借助 DDIO(Direct Data I/O)技術直接将封包儲存到 CPU 的 Cache 中,或者儲存到記憶體中(沒有 DDIO 技術的情況下),并設定封包到達的标志位。應用程式則可以周期性地輪詢封包到達的标志位,檢測是否有新封包需要處理。整個過程中完全沒有中斷處理過程,是以應用程式的網絡封包處理能力得以極大提升。
故此,想要 CPU 執行始終高效,就必然需要一個核心線程去主動 Poll(輪詢)網卡,而這種行為與目前的核心協定棧是不相容的,即便目前核心協定棧可以使用 NAPI 中斷+輪詢的方式,但依舊沒有根本上解決問題。除非再重新實作一套全新的核心協定棧,顯然這并不現實,但幸運的是,我們可以在使用者态實作這一點。
針對 Intel 網卡,DPDK 實作了基于輪詢方式的 PMD(Poll Mode Drivers)網卡驅動。該驅動由使用者态的 API 以及 PMD Driver 構成,核心态的 UIO Driver 屏蔽了網卡發出的中斷信号,然後由使用者态的 PMD Driver 采用主動輪詢的方式。除了鍊路狀态通知仍必須采用中斷方式以外,均使用無中斷方式直接操作網卡裝置的接收和發送隊列。
PMD Driver 從網卡上接收到資料包後,會直接通過 DMA 方式傳輸到預配置設定的記憶體中,同時更新無鎖環形隊列中的資料包指針,不斷輪詢的應用程式很快就能感覺收到資料包,并在預配置設定的記憶體位址上直接處理資料包,這個過程非常簡潔。
PMD 極大提升了網卡 I/O 性能。此外,PMD 還同時支援實體和虛拟兩種網絡接口,支援 Intel、Cisco、Broadcom、Mellanox、Chelsio 等整個行業生态系統的網卡裝置,以及支援基于 KVM、VMware、 Xen 等虛拟化網絡接口。PMD 實作了 Intel 1GbE、10GbE 和 40GbE 網卡下基于輪詢收發包。
UIO+PMD,前者旁路了核心,後者主動輪詢避免了硬中斷,DPDK 進而可以在使用者态進行收發包的處理。帶來了零拷貝(Zero Copy)、無系統調用(System call)的優化。同時,還避免了軟中斷的異步處理,也減少了上下文切換帶來的 Cache Miss。
值得注意的是,運作在 PMD 的 Core 會處于使用者态 CPU 100% 的狀态,如下圖:
由于,網絡空閑時 CPU 會長期處于空轉狀态,帶來了電力能耗的問題。是以,DPDK 引入了 Interrupt DPDK(中斷
DPDK)模式。Interrupt DPDK 的原理和 NAPI 很像,就是 PMD
在沒資料包需要處理時自動進入睡眠,改為中斷通知,接收到收包中斷信号後,**主動輪詢。這就是所謂的鍊路狀态中斷通知。并且 Interrupt
DPDK 還可以和其他程序共享一個 CPU Core,但 DPDK 程序仍具有更高的排程優先級。
雖然 PMD 是在使用者态實作的驅動程式,但實際上還是會依賴于核心提供的政策。其中 UIO 核心子產品,是核心提供的使用者态驅動架構,而
igb_uio 是 DPDK 用于與 UIO 互動的核心子產品,通過 igb_uio 來 bind 指定的 PCI 網卡裝置到 DPDK 使用。
igb_uio 核心子產品主要功能之一就是用于注冊一個 PCI 裝置。實際上這是由 DPDK 提供個一個
Python 腳本 dpdk-devbind 來完成的,當執行 dpdk-devbind 來 bind 網卡時,會通過 sysfs
與核心互動,讓核心使用指定的驅動程式來比對網卡。具體的行為是向檔案 /sys/bus/pci/devices/(pci
id)/driver_override 寫入指定驅動的名稱,或者向
/sys/bus/pci/drivers/igb_uio(驅動名稱)/new_id 寫入要 bind 的網卡裝置的 PCI
ID。前者是配置裝置,讓其選擇驅動;後者是是配置驅動,讓其支援新的 PCI 裝置。按照核心的文檔
https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci
中提到,這兩個動作都會促使驅動程式 bind 新的網卡裝置。
dpdk-devbind 具體的步驟如下:
擷取腳本執行參數指定的網卡(e.g. eth1)裝置的 PCI 資訊。實際是執行指令 <code>lspci–Dvmmn</code> 檢視,主要關注 Slot、Vendor 以及 Device 資訊。
unbind 網卡裝置之前的 igb 子產品,将 Step 1 中擷取到的 eth1 對應的 Slot 資訊 <code>0000:06:00.1</code> 值寫入 igb 驅動的 unbind 檔案。e.g. <code>echo 0000:06:00.1 > /sys/bus/pci/drivers/igb/unbind</code>。
bind 網卡裝置到新的 igb_uio 子產品,将 eth1 的 Vendor 和 Device ID 資訊寫入 igb_uio 驅動的 new_id 檔案。e.g. <code>echo 0x8086 0x1521 > /sys/bus/pci/drivers/igb_uio/new_id</code>。
igb_uio 核心子產品的另一個主要功能就是讓用于态的 PMD 驅動程式得以與 UIO 進行互動:
調用 igbuio_setup_bars,設定 uio_info 的 uio_mem 和 uio_port。
設定 uio_info 的其他成員。
調用 uio_register_device,注冊 UIO 裝置。
打開 UIO 裝置并注冊中斷。
調用 uio_event_notify,将注冊的 UIO 裝置的 “記憶體空間” 映射到使用者态的應用空間。其 mmap 的函數為 uio_mmap。至此,UIO 就可以讓 PMD 驅動程式在使用者态應用層通路裝置的大部分資源了。
應用層 UIO 初始化。同時,DPDK 還需要把 PCI 裝置的 BAR 映射到應用層。在 pci_uio_map_resource 函數中會調用 pci_uio_map_resource_by_index 做資源映射。
在 PMD 驅動程式中,DPDK 應用程式,會調用 rte_eth_rx_burst 讀取資料封包。如果網卡接收 Buffer 的描述符表示已經完成一個封包的接收(e.g. 有 E1000_RXD_STAT_DD 标志),則 rte_mbuf_raw_alloc 一個 mbuf 進行處理。
對應 RTC 模型的 DPDK 應用程式來說,就是不斷的調用 rte_eth_rx_burst 去詢問網卡是否有新的封包。如果有,就取走所有的封包或達到參數 nb_pkts 的上限。然後進行封包處理,處理完畢,再次循環。
KNI(Kernel NIC Interface,核心網卡接口),是 DPDK 允許使用者态和核心态交換封包的解決方案,模拟了一個虛拟的網口,提供 DPDK 應用程式和 Linux 核心之間通訊沒接。即 KNI 接口允許封包從使用者态接收後轉發到 Linux 核心協定棧中去。
雖然 DPDK 的高速轉發性能很出色,但是也有自己的一些缺點,比如沒有标準協定棧就是其中之一,當然也可能當時設計時就将沒有将協定棧考慮進去,畢竟協定棧需要将封包轉發處理,可能會使處理封包的能力大大降低。
上圖是 KNI 的 mbuf 的使用流程,也可以看出封包的流向,因為封包在代碼中其實就是一個個記憶體指針。其中 rx_q 右邊是使用者态,左邊是核心态。最後通過調用 netif_rx 将封包送入 Linux 核心協定棧,這其中需要将 DPDK 的 mbuf 轉換成标準的 skb_buf 結構體。當 Linux 核心向 KNI 端口發送封包時,調用回調函數 kni_net_tx,然後封包經過轉換之後發送到端口上。
核心部件庫(Core Libraries)是 DPDK 面向使用者态協定棧應用程式員開發的子產品。
EAL(Environment Abstraction Layer,環境抽象層):對 DPDK 的運作環境(e.g. Linux 作業系統)進行初始化,包括:HugePage 記憶體配置設定、記憶體/緩沖區/隊列配置設定、原子性無鎖操作、NUMA 親和性、CPU 綁定等,并通過 UIO 或 VFIO 技術将 PCI/PCIe 裝置位址映射到使用者态,友善了使用者态的 DPDK 應用程式調用。同時為應用程式提供了一個通用接口,隐藏了其與底層庫以及裝置打交道的相關細節。
MALLOC(堆記憶體管理元件):為 DPDK 應用程式提供從 HugePage 内配置設定堆記憶體的接口。當需要為 SKB(Socket Buffer,本質是若幹個資料包的緩存區)配置設定大量的小塊記憶體時(如:配置設定用于存儲 Buffer descriptor table 中每個表項指針的記憶體)可以調用該接口。由于堆記憶體是從 HugePage 記憶體配置設定的,是以可以減少 TLB 缺頁。
NOTE:堆,是由開發人員主動配置設定和釋放的存儲空間, 若開發人員不釋放,則程式結束時由 OS 回收,配置設定方式類似于連結清單;與堆不同,棧,是由作業系統自動配置設定和釋放的存儲空間 ,用于存放函數的參數值、局部變量等,其操作方式類似于資料結構中的棧。
MBUF(網絡封包緩存塊管理元件):為 DPDK 應用程式提供建立和釋放用于存儲資料封包資訊的緩存塊的接口。提供了兩種類型的 MBUF,一種用于存儲一般資訊,一種用于存儲實際的封包資料。這些 MBUF 存儲在一個記憶體池中。
MEMPOOL(記憶體池管理元件):為 DPDK 應用程式和其它元件提供配置設定記憶體池的接口,記憶體池是一個由固定大小的多個記憶體塊組成的記憶體容器,可用于存儲不同的對像實體,如:資料封包緩存塊等。記憶體池由記憶體池的名稱(一個字元串)進行唯一辨別,它由一個 Ring 緩沖區和一組本地緩存隊列組成,每個 CPU Core 優先從自身的緩存隊列中配置設定記憶體塊,當本地緩存隊列減少到一定程度時,開始從記憶體環緩沖區中申請記憶體塊來進行補充。
RING(環緩沖區管理元件):為 DPDK 應用程式和其它元件提供一個無鎖的多生産者多消費者 FIFO 隊列。
NOTE:DPDK 基于 Linux 核心的無鎖環形緩沖 kfifo 實作了一套自己的無鎖機制。支援單生産者入列/單消費者出列和多生産者入列/多消費者出列操作,在資料傳輸的時候,降低性能的同時還能保證資料的同步。
TIMER(定時器元件):提供一些異步周期執行的接口(也可以隻執行一次),可以指定某個函數在規定時間内的異步執行,就像 LIBC 中的 timer 定時器。但是這裡的定時器需要 DPDK 應用程式在主循環中周期内調用 <code>rte_timer_manage</code> 來使能定時器,使用起來不那麼友善。TIMER 的時間參考來自 EAL 層提供的時間接口。
注:
RTE:Run-Time Environment
EAL:Environment Abstraction Layer
PMD:Poll-Mode Driver
Memory Manager(librte_malloc,堆記憶體管理器):提供一組 API,用于從 HugePages 記憶體建立的 memzones 中配置設定記憶體。
Ring Manager(librte_ring,環形隊列管理器):在一個大小有限的頁表中,Ring 資料結構提供了一個無鎖的多生産者-多消費者 FIFO API。相較于無鎖隊列,它有一些的優勢,如:更容易實作,适應于大容量操作,而且速度更快。 Ring 在 Memory Pool Manager 中被使用,而且 Ring 還用于不同 CPU Core 之間或是 Processor 上處理單元之間的通信。
Memory Pool Manager(librte_mempool,記憶體池管理器):記憶體池管理器負責配置設定的記憶體中的 Pool 對象。Pool 由名稱唯一辨別,并使用一個 Ring 來存儲空閑對象。它提供了其他一些可選的服務,例如:每個 CPU Core 的對象緩存和對齊方式幫助,以確定将填充的對象在所有記憶體通道上得到均勻分布。
Network Packet Buffer Management(librte_mbuf,網絡封包緩沖管理):提供了建立和銷毀資料封包緩沖區的能力。DPDK 應用程式中可以使用這些緩存區來存儲消息以及封包資料。
Timer Manager(librte_timer,定時器管理):為 DPDK 應用程式的執行單元提供了定時服務,為函數異步執行提供支援。定時器可以設定周期調用或隻調用一次。DPDK 應用程式可以使用 EAL 提供的接口擷取高精度時鐘,并且能在每個核上根據需要進行初始化。
平台相關子產品(Platform)包括 KNI、POWER(能耗管理)以及 IVSHMEM 接口。
KNI:主要通過 Linux 核心中的 kni.ko 子產品将資料封包從使用者态傳遞給核心态協定棧處理,以便正常的使用者程序(e.g. Container)可以使用 Linux 核心協定棧傳統的 Socket 接口對相關封包進行處理。
POWER:提供了一些 API,讓 DPDK 應用程式可以根據收包速率動态調整 CPU 頻率或讓 CPU 進入不同的休眠狀态。
IVSHMEM:子產品提供了虛拟機與虛拟機之間,或者虛拟機與主機之間的零拷貝共享記憶體機制。當 DPDK 應用程式運作時,IVSHMEM 子產品會調用 Core Libraries 的 API,把幾個 HugePage 記憶體映射為一個 IVSHMEM 裝置池,并通過參數傳遞給 QEMU,這樣,就實作了虛拟機之間的零拷貝記憶體共享。
Buffer Manager API:通過預先從 EAL 上配置設定固定大小的多個記憶體對象,避免了在運作過程中動态進行記憶體配置設定和回收,以此來提高效率,用于資料包 Buffer 的管理。
Queue/Ring Manager API:以高效的方式實作了無鎖的 FIFO 環形隊列,适用于一個生産者多個消費者、一個消費者多個生産者模型。支援批量無鎖操作,可避免鎖沖突導緻的等待。
Packet Flow Classification API:通過 Intel SSE 基于多元組的方式實作了高效的 HASH 算法,以便快速對資料包進行分類處理。該 API 一般用于路由查找過程中的最長字首比對。此外,安全産品場景中,可以根據 DataFlow 五元組來标記不同的使用者。
http代理伺服器(3-4-7層代理)-網絡事件庫公共元件、核心kernel驅動 攝像頭驅動 tcpip網絡協定棧、netfilter、bridge 好像看過!!!!
但行好事 莫問前程
--身高體重180的胖子