1. 程式人生 > 實用技巧 >DPDK筆記 RSS(receive side scaling)網絡卡分流機制

DPDK筆記 RSS(receive side scaling)網絡卡分流機制

DPDK 網絡卡RSS(receive side scaling)簡介
DPDK-RSS負載均衡分流
DPDK設計技巧(第1部分-RSS)
接收端縮放介紹

1. 縮略詞

  • RSS receive side scaling 接收端縮放
  • DPC Delayed procedure call 延遲過程呼叫
  • LSB Least significant bit 最低有效位
  • MSI Message signal interruption 訊息訊號中斷
  • RETA redirection table 重定向表
  • DMA Direct Memory Access 直接記憶體訪問
  • NDIS Network Driver Interface Specification 網路驅動介面型別
  • MSI Message Signaled Interrupt 訊息訊號中斷

2. RSS簡介

RSS是一種網絡卡驅動技術,能讓多核系統中跨多個處理器的網路收包處理能力高效能分配。
注意:由於同一個核的處理器超執行緒共享同一個執行引擎,這個效果跟有多個物理核的處理器不一樣。因此,RSS不能使用超執行緒處理器

為了有效的處理收包,一個miniport的驅動的接收中斷服務功能排程了一個延遲過程呼叫(DPC)如果沒有RSS,一個典型的DPC標識了所有的收包資料都在這個DPC呼叫裡。因此,所有收包處理關聯的中斷都會執行在這些中斷髮生的CPU上。

如有RSS功能,網絡卡和miniport驅動就會提供排程這些收包DPC(延遲過程呼叫)到其他處理器上的能力。同樣,RSS設計保證對於一個給定連線的處理繼續停留在一個分配好的CPU上。網絡卡實現了一個hash雜湊功能和作為結果的hash值來選擇一個CPU。

下圖說明了用於確定CPU的RSS機制。

NIC使用雜湊函式來計算接收到的網路資料內定義區域(雜湊型別)上的雜湊值。定義的區域可以是不連續的。

雜湊值的多個最低有效位(LSB)用於索引間接表。間接表中的值用於將接收到的資料分配給CPU。

有關指定間接表,雜湊型別和雜湊函式的更多詳細資訊,請參閱RSS配置。

藉助訊息訊號中斷(MSI)支援,NIC也可以中斷關聯的CPU。有關NDIS對MSI的支援的更多資訊,請參見NDIS MSI-X。

2.1. 什麼是RSS

接收方縮放(RSS)是一種網路驅動程式技術,可在多處理器系統中的多個CPU之間有效分配網路接收處理。

接收方縮放(RSS)也稱為多佇列接收,它在多個基於硬體的接收佇列之間分配網路接收處理,從而允許多個CPU處理入站網路流量。RSS可用於緩解單個CPU過載導致的接收中斷處理瓶頸,並減少網路延遲。

它的作用是在每個傳入的資料包上發出帶有預定義雜湊鍵的雜湊函式。雜湊函式將資料包的IP地址,協議(UDP或TCP)和埠(5個元組)作為鍵並計算雜湊值。(如果配置的話,RSS雜湊函式只能使用2,3或4個元組來建立金鑰)。

雜湊值的多個最低有效位(LSB)用於索引間接表。間接表中的值用於將接收到的資料分配給CPU。

下圖描述了在每個傳入資料包上完成的過程:

這意味著多程序應用程式可以使用硬體在CPU之間分配流量。在此過程中,間接表的使用對於實現動態負載平衡非常有用,因為間接表可以由驅動程式/應用程式重新程式設計。

  • 網絡卡對接收到的報文進行解析,獲取IP地址、協議和埠五元組資訊
  • 網絡卡通過配置的HASH函式根據五元組資訊計算出HASH值,也可以根據二、三或四元組進行計算。
  • 取HASH值的低幾位(這個具體網絡卡可能不同)作為RETA(redirection table)的索引
  • 根據RETA中儲存的值分發到對應的CPU

RSS通過減少如下開銷來提高網路效能:

  • 1、跨多個CPU分派一個網絡卡上的收包處理的延遲。這個也保證了不會有的CPU負載過重而另外的CPU處於空閒。
  • 2、執行在同一個CPU上的軟體演算法因共享資料帶來的增加自旋鎖開銷的可能性。自旋鎖開銷的發生,比如,當一個函式執行在CPU0上,對一個數據加了自旋鎖,但是另一個函式執行在CPU1上必須訪問這個資料,CPU1就會一直自旋等待CPU0釋放鎖。
  • 3、執行在同一個CPU上的軟體演算法因共享資料帶來的快取重新載入和其他資源開銷增加的可能性。這些重新載入的發生,比如,當一個函式執行並訪問了CPU0上的共享資料,執行在CPU1時隨之來了一箇中斷。

為了能在一個安全的環境中獲取這些效能的提升,RSS提供如下機制:

  • 1、分散式處理:RSS在DPC(延遲過程呼叫)裡分派給定網絡卡的收包處理到多個CPU上去。
  • 2、順序處理

2.2. 非RSS接收處理

下圖說明了非RSS接收處理。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-8Zyytjpj-1599801716065)(_v_images/20200910151727086_30268.png)]

在圖中,虛線路徑表示傳送和接收處理的備用路徑。由於系統控制縮放比例,因此處理並非總是在提供最佳效能的CPU上進行。僅在偶然的中斷下通過連續中斷在同一CPU上處理連線。

對於每個非RSS中斷週期,重複以下過程:

  • NIC使用DMA將接收到的資料填充到緩衝區中並中斷系統。初始化期間,微型埠驅動程式在共享記憶體中分配了接收緩衝區。
  • **在此中斷週期內,NIC可以隨時填充其他接收緩衝區。**但是,直到微型埠驅動程式啟用了中斷,NIC才會再次中斷。系統在一箇中斷週期中處理的接收緩衝區可以與許多不同的網路連線相關聯。
  • NDIS 在系統確定的CPU上呼叫微型埠驅動程式的微型埠中斷功能(ISR)。理想情況下,ISR應該轉到最不繁忙的CPU。但是,在某些系統中,系統將ISR分配給可用的CPU或與NIC關聯的CPU。
  • ISR禁用中斷並請求NDIS(網路驅動介面型別)將延遲過程呼叫(DPC)排隊以處理接收到的資料。
  • NDIS(網路驅動介面型別) 在當前CPU上呼叫MiniportInterruptDPC函式(DPC)。
  • DPC(延遲過程呼叫)為所有接收到的緩衝區建立接收描述符,並在驅動程式堆疊上指示資料。對於許多不同的連線可能有許多緩衝區,並且可能有很多處理要完成。可以在其他CPU上處理與後續中斷週期相關的接收資料。給定網路連線的傳送處理也可以在其他CPU上執行。
  • DPC使能中斷。該中斷週期完成,該過程再次開始。

2.3. 相關命令

接收端縮放(RSS)

要確定您的網路介面卡是否支援RSS,請檢查中的介面是否有多箇中斷請求佇列/proc/interrupts

例如,如果您對p1p1介面感興趣:

# egrep 'CPU|p1p1' /proc/interrupts
      CPU0    CPU1    CPU2    CPU3    CPU4    CPU5
89:   40187       0       0       0       0       0   IR-PCI-MSI-edge   p1p1-0
90:       0     790       0       0       0       0   IR-PCI-MSI-edge   p1p1-1
91:       0       0     959       0       0       0   IR-PCI-MSI-edge   p1p1-2
92:       0       0       0    3310       0       0   IR-PCI-MSI-edge   p1p1-3
93:       0       0       0       0     622       0   IR-PCI-MSI-edge   p1p1-4
94:       0       0       0       0       0    2475   IR-PCI-MSI-edge   p1p1-5

前面的輸出顯示NIC驅動程式為p1p1介面建立了6個接收佇列(p1p1-0通過p1p1-5)。它還顯示每個佇列處理了多少箇中斷,以及哪個CPU為該中斷服務。在這種情況下,有6個佇列,因為預設情況下,此特定的NIC驅動程式為每個CPU建立一個佇列,並且此係統有6個CPU。在NIC驅動程式中,這是相當普遍的模式。
或者,您可以在載入網路驅動程式後檢查的輸出。

ls -1 /sys/devices/*/*/device_pci_address/msi_irqs

例如,如果您對PCI地址為0000:01:00.0的裝置感興趣,則可以使用以下命令列出該裝置的中斷請求佇列:

# ls -1 /sys/devices/*/*/0000:01:00.0/msi_irqs
101
102
103
104
105
106
107
108
109

預設情況下啟用RSS。在適當的網路裝置驅動程式中配置RSS的佇列數(或應該處理網路活動的CPU)。對於bnx2x驅動程式,它在中配置num_queues。對於sfc驅動程式,它在rss_cpus引數中配置。無論如何,通常都在中配置它,

/sys/class/net/device/queues/rx-queue/

其中device是網路裝置的名稱(例如eth1),rx-queue是適當的接收佇列的名稱。

在配置RSS時,Red Hat建議將每個物理CPU核心的佇列數限制為一個在分析工具中通常將超執行緒表示為單獨的核心,但是尚未證明為包括邏輯核心(例如超執行緒)在內的所有核心配置佇列對網路效能沒有好處。

啟用後,RSS根據每個CPU排隊的處理量在可用CPU之間平均分配網路處理。但是,您可以使用ethtool --show-rxfh-indir--set-rxfh-indir引數來修改網路活動的分佈方式,並將某些型別的網路活動的權重設定為比其他型別重要。
該irqbalance守護程式可以與RSS結合使用,以減少跨節點記憶體傳輸和快取記憶體行反彈的可能性。這降低了處理網路資料包的延遲。如果同時irqbalance使用RSS和RSS,則通過確保irqbalance將與網路裝置關聯的中斷定向到適當的RSS佇列來實現最低的延遲。

3. RSS的作用

RSS是網絡卡提供的分流機制。用來將報表分流到不同的收包佇列,以提高收包效能。
RSS及Flow Director都是靠網絡卡上的資源來達到分類的目的,所以在初始化配置網絡卡時,我們需要傳遞相應的配置資訊去使能網絡卡的RSS及Flow Director功能。
RSS(receive side scaling)是由微軟提出的一種負載分流方法,通過計算網路資料報文中的網路層&傳輸層二/三/四元組HASH值,取HASH值的最低有效位(LSB)用於索引間接定址表RETA(Redirection Table),間接定址表RETA中的儲存索引值用於分配資料報文到不同的CPU接收處理。現階段RSS基本已有硬體實現,通過這項技術能夠將網路流量分載到多個CPU上,降低作業系統單個CPU的佔用率。



3.1. 沒有開啟 rss負載分流情況下:

  • 所有報文只會從一個硬體佇列來收包。

3.2. 開啟 rss進行負載分流情況下:

  • rss 會解釋報文的l3 層資訊ip 地址。甚至l4 層資訊tcp/udp 埠
  • 報文會經過 hash function 計算出一個uint32_trss hash。填充到struct rte_mbufhash.rss欄位中。
  • rss hash 的 低7位 會對映到 4位長 的 RSS output index。
  • 無法解釋的 報文,rss hash 和 RSS output index 設定為0



4. RSS的硬體支援

下圖說明了RSS的硬體支援級別。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-PmdvooIo-1599801716074)(_v_images/20200910150501324_7264.png)]

對RSS的硬體支援分為三種級別:

使用單個佇列進行雜湊計算
NIC計算雜湊值,並且微型埠驅動程式將接收到的資料包分配給與CPU關聯的佇列。有關更多資訊,請參閱帶有單個硬體接收佇列的RSS。

具有多個接收佇列
具有多個接收佇列的雜湊計算NIC將接收到的資料緩衝區分配給與CPU關聯的佇列。有關更多資訊,請參閱帶有硬體佇列的RSS。

訊息訊號中斷(MSI)
NIC中斷應該處理接收到的資料包的CPU。有關更多資訊,請參見帶有訊息訊號中斷的RSS。

NIC始終傳遞32位雜湊值。

4.1. 具有單個硬體接收佇列的RSS

微型埠驅動程式可以為支援RSS雜湊計算和單個接收描述符佇列的NIC支援RSS。

下圖說明了具有單個接收描述符佇列的RSS處理。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-cM8kqgjt-1599801716075)(_v_images/20200910152131066_14897.png)]

在圖中,虛線箭頭表示接收處理的備用路徑。RSS無法控制接收初始ISR呼叫的CPU。

與非RSS接收處理不同,基於RSS的接收處理分佈在多個CPU上。同樣,給定連線的處理可以繫結到給定CPU。

對每個中斷重複以下過程

  • NIC使用DMA將接收到的資料填充到緩衝區中並中斷系統。初始化期間,微型埠驅動程式在共享記憶體中分配了接收緩衝區。

  • NIC可以隨時填充其他接收緩衝區,但是直到微型埠驅動程式啟用中斷後才可以再次中斷。系統在一箇中斷中處理的接收緩衝區可以與許多不同的網路連線相關聯。

  • NDIS 在系統確定的CPU上呼叫微型埠驅動程式的微型埠中斷功能(ISR)。

  • ISR禁用中斷並請求NDIS將延遲過程呼叫(DPC)排隊以處理接收到的資料。

  • NDIS 在當前CPU上呼叫MiniportInterruptDPC函式(DPC)。在DPC中:
    a.微型埠驅動程式使用NIC為每個接收到的緩衝區計算的雜湊值,並將每個接收到的緩衝區重新分配給與CPU關聯的接收佇列。
    b.當前DPC請求NDIS為與非空接收佇列關聯的其他每個CPU的DPC排隊。
    c.如果當前DPC在與非空佇列關聯的CPU上執行,則當前DPC處理關聯的接收緩衝區,並在驅動程式堆疊上指示接收到的資料。
    d.分配佇列和排隊其他DPC需要額外的處理開銷。為了提高系統性能,必須通過更好地利用可用CPU來抵消此開銷。

  • 給定CPU上的DPC:
    a.處理與其接收佇列關聯的接收緩衝區,並在驅動程式堆疊上指示資料。
    b.如果它是最後一個要完成的DPC,則啟用中斷。該中斷已完成,該過程再次開始。驅動程式必須使用原子操作來標識要完成的最後一個DPC。例如,驅動程式可以使用NdisInterlockedDecrement函式來實現原子計數器。

4.2. 帶有硬體佇列的RSS

與具有單個硬體接收佇列解決方案的RSS相比,具有硬體排隊的RSS可以提高系統性能。支援硬體排隊的NIC將接收到的資料分配給多個接收佇列。接收佇列與CPU相關聯。NIC根據雜湊值和間接表將接收到的資料分配給CPU。

下圖說明了帶有NIC接收佇列的RSS。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-IuLzBdfq-1599801716076)(_v_images/20200910152609754_9258.png)]

在圖中,虛線箭頭表示接收處理的備用路徑。RSS無法控制接收初始ISR呼叫的CPU。驅動程式不必將資料排隊,因此它可以立即在正確的CPU上排程初始DPC。

對每個中斷重複以下過程:

  1. NIC
  • 使用DMA用接收到的資料填充緩衝區。初始化期間,微型埠驅動程式在共享記憶體中分配了接收緩衝區。
  • 計算雜湊值。
  • 對CPU的緩衝區進行排隊,並將佇列分配提供給微型埠驅動程式。例如,在接收到一定數量的資料包之後,NIC可以迴圈執行步驟1-3和DMA DMA CPU分配列表。具體機制留給NIC設計。
    中斷系統。系統在一箇中斷中處理的接收緩衝區在CPU之間分配。
  1. NDIS 在系統確定的CPU上呼叫微型埠驅動程式的微型埠中斷功能(ISR)。

  2. 微型埠驅動程式請求NDIS將具有非空佇列的每個CPU的延遲過程呼叫(DPC)排隊。請注意,所有DPC必須在驅動程式允許中斷之前完成。另外,請注意,ISR可能正在沒有緩衝區要處理的CPU上執行。

  3. NDIS 為每個排隊的DPC 呼叫MiniportInterruptDPC函式。給定CPU上的DPC:

  • 生成佇列中所有已接收緩衝區的接收描述符,並在驅動程式堆疊上指示資料。有關更多資訊,請參見指示RSS接收資料。
  • 如果它是最後一個要完成的DPC,則啟用中斷。該中斷已完成,該過程再次開始。驅動程式必須使用原子操作來標識要完成的最後一個DPC。例如,驅動程式可以使用NdisInterlockedDecrement函式來實現原子計數器。

4.3. 帶有訊息訊號中斷的RSS

Miniport驅動程式可以支援訊息訊號中斷(MSI),以提高RSS效能。MSI使NIC可以請求CPU上的中斷,該中斷將處理接收到的資料。有關NDIS對MSI的支援的更多資訊,請參見NDIS MSI-X。

下圖說明了帶有MSI-X的RSS。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-GT4V1nv6-1599801716077)(_v_images/20200910153036236_1380.png)]

在圖中,虛線箭頭表示對其他連線的處理。具有MSI-X的RSS允許NIC中斷用於連線的正確CPU。

對每個中斷重複以下過程:

  1. NIC:
  • 使用DMA用接收到的資料填充緩衝區。初始化期間,微型埠驅動程式在共享記憶體中分配了接收緩衝區。
  • 計算雜湊值。
  • 將緩衝區排隊到CPU,並將佇列分配提供給微型埠驅動程式。例如,在接收到一定數量的資料包之後,NIC可以迴圈執行步驟1-3和DMA DMA CPU分配列表。具體機制留給NIC設計。
  • 使用MSI-X,中斷與非空佇列關聯的CPU。
  1. NIC可以隨時填充其他接收緩衝區,並將它們新增到佇列中,但是直到微型埠驅動程式為該CPU啟用中斷之前,它才不會再次中斷該CPU。

  2. NDIS 在當前CPU上呼叫微型埠驅動程式的ISR(MiniportInterrupt)。

  3. ISR禁用當前CPU上的中斷,並使DPC在當前CPU上排隊。當DPC在當前CPU上執行時,其他CPU上仍然可能發生中斷。

  4. NDIS 為每個排隊的DPC 呼叫MiniportInterruptDPC函式。每個DPC:

  • 生成佇列中所有已接收緩衝區的接收描述符,並在驅動程式堆疊上指示資料。有關更多資訊,請參見指示RSS接收資料。
  • 為當前CPU啟用中斷。該中斷已完成,該過程再次開始。請注意,不需要原子操作即可跟蹤其他DPC的進度。

5. RSS如何提高系統性能

RSS如何提高系統性能

RSS可以通過減少以下方面來提高網路系統的效能:

  • 通過將來自NIC的接收處理分配到多個CPU中來處理延遲。這有助於確保在另一個CPU空閒時沒有CPU重負荷。
  • 通過增加共享資料的軟體演算法在同一CPU上執行的可能性來增加自旋鎖開銷。例如,當在CPU0上執行的功能擁有對CPU1上執行的功能必須訪問的資料的旋轉鎖時,就會發生旋轉鎖開銷。CPU1旋轉(等待),直到CPU0釋放鎖定。
  • 通過增加共享資料的軟體演算法在同一CPU上執行的可能性來重新載入快取和其他資源。例如,當正在執行並訪問CPU0上的共享資料的功能在隨後的中斷中在CPU1上執行時,會發生這種重新載入。

為了在安全的環境中實現這些效能改進,RSS提供了以下機制:

  • 分散式處理:RSS將來自DPC中給定NIC的接收指示的處理分配到多個CPU。
  • 有序處理:RSS保留接收到的資料包的傳遞順序。對於每個網路連線,RSS程序在關聯的CPU上接收指示。有關RSS接收處理的更多資訊,請參閱指示RSS接收資料。
  • 動態負載平衡:RSS提供了一種隨著主機系統負載變化而在CPU之間重新平衡網路處理負載的方法。為了重新平衡負載,上層驅動程式可以更改間接表。有關指定間接表,雜湊型別和雜湊函式的更多資訊,請參見RSS Configuration。
  • 傳送端縮放:RSS使驅動程式堆疊可以處理同一CPU上給定連線的傳送和接收方資料。通常,上層驅動程式(例如TCP)傳送資料塊的一部分,並在傳送剩餘資料之前等待確認。然後,確認會觸發後續的傳送請求。RSS間接表標識用於接收資料處理的特定CPU。預設情況下,如果傳送處理由接收確認觸發,則在同一CPU上執行。驅動程式還可以指定CPU(例如,如果使用計時器)。
  • 安全雜湊:RSS包含一個可提供更高安全性的簽名。此簽名可保護系統免受可能試圖迫使系統進入不平衡狀態的惡意遠端主機的侵害。
  • MSI-X支援:支援MSI-X(訊息訊號中斷)的RSS在隨後執行DPC的同一CPU上執行中斷服務程式(ISR)。這減少了自旋鎖開銷和快取的重新載入。

PCIe系列第八講、MSI和MSI-X中斷機制

6. RSS雜湊型別

RSS雜湊型別

RSS雜湊型別指定NIC必須用來計算RSS雜湊值的接收網路資料部分。

上層驅動程式設定雜湊型別,函式和間接表。上層驅動程式設定的雜湊型別可以是微型埠驅動程式可以支援的型別的子集。

雜湊型別是以下標誌的有效組合的或:

  • NDIS_HASH_IPV4
  • NDIS_HASH_TCP_IPV4
  • NDIS_HASH_UDP_IPV4
  • NDIS_HASH_IPV6
  • NDIS_HASH_TCP_IPV6
  • NDIS_HASH_UDP_IPV6
  • NDIS_HASH_IPV6_EX
  • NDIS_HASH_TCP_IPV6_EX
  • NDIS_HASH_UDP_IPV6_EX

/* hash type DPDK 20.05 */
#define	NDIS_HASH_IPV4			0x00000100
#define	NDIS_HASH_TCP_IPV4		0x00000200
#define	NDIS_HASH_IPV6			0x00000400
#define	NDIS_HASH_IPV6_EX		0x00000800
#define	NDIS_HASH_TCP_IPV6		0x00001000
#define	NDIS_HASH_TCP_IPV6_EX		0x00002000

這些是有效標誌組合的集合:

  • IPv4(NDIS_HASH_IPV4,NDIS_HASH_TCP_IPV4和NDIS_HASH_UDP_IPV4的組合)
  • IPv6(NDIS_HASH_IPV6,NDIS_HASH_TCP_IPV6和NDIS_HASH_UDP_IPV6的組合)
  • 具有擴充套件頭的IPv6(NDIS_HASH_IPV6_EX,NDIS_HASH_TCP_IPV6_EX和NDIS_HASH_UDP_IPV6_EX的組合)

NIC必須支援IPv4集中的一種組合。其他集合和組合是可選的。NIC一次可以支援多個集。在這種情況下,接收到的資料型別將確定NIC使用哪種雜湊型別。

通常,如果NIC無法正確解釋接收到的資料,則它一定不能計算雜湊值。例如,如果NIC僅支援IPv4並且接收到無法正確解釋的IPv6資料包,則它不得計算雜湊值。如果NIC收到其不支援的傳輸型別的資料包,則它不得計算雜湊值。例如,如果NIC在計算TCP資料包的雜湊值時接收到UDP資料包,則它不得計算雜湊值。在這種情況下,像在非RSS情況下一樣處理分組。有關非RSS接收處理的更多資訊,請參見非RSS接收處理。

6.1. IPv4雜湊型別組合

IPv4集中的有效雜湊型別組合為:

  • NDIS_HASH_IPV4
  • NDIS_HASH_TCP_IPV4
  • NDIS_HASH_UDP_IPV4
  • NDIS_HASH_TCP_IPV4 | NDIS_HASH_IPV4
  • NDIS_HASH_UDP_IPV4 | NDIS_HASH_IPV4
  • NDIS_HASH_TCP_IPV4 | NDIS_HASH_UDP_IPV4 | NDIS_HASH_IPV4

6.1.1. NDIS_HASH_IPV4

如果僅設定此標誌,則NIC應該在以下IPv4標頭欄位上計算雜湊值:

  • 源IPv4地址
  • 目的IPv4地址

注意:如果NIC收到同時具有IP和TCP標頭的資料包,則不應始終使用NDIS_HASH_TCP_IPV4。對於分段IP資料包,必須使用NDIS_HASH_IPV4。這包括同時包含IP和TCP標頭的第一個片段。

6.1.2. NDIS_HASH_TCP_IPV4

如果僅設定此標誌,則NIC應該解析接收到的資料以標識包含TCP段的IPv4資料包。

NIC必須識別並跳過存在的任何IP選項。如果NIC無法跳過任何IP選項,則不應計算雜湊值。

NIC應該在以下欄位上計算雜湊值:

  • 源IPv4地址
  • 目的IPv4地址
  • 源TCP埠
  • 目標TCP埠

6.1.3. NDIS_HASH_UDP_IPV4

如果僅設定此標誌,則NIC應該解析收到的資料,以識別包含UDP資料報的IPv4資料包。

NIC必須識別並跳過存在的任何IP選項。如果NIC無法跳過任何IP選項,則不應計算雜湊值。

NIC應該在以下欄位上計算雜湊值:

  • 源IPv4地址
  • 目的IPv4地址
  • 源UDP埠
  • 目的UDP埠

6.1.4. NDIS_HASH_TCP_IPV4 | NDIS_HASH_IPV4

如果設定了此標誌組合,則NIC應該執行鍼對NDIS_HASH_TCP_IPV4情況指定的雜湊計算。但是,如果資料包不包含TCP頭,則NIC應該按照NDIS_HASH_IPV4情況指定的方式計算雜湊值。

6.1.5. NDIS_HASH_UDP_IPV4 | NDIS_HASH_IPV4

如果設定了此標誌組合,則NIC應該執行鍼對NDIS_HASH_UDP_IPV4情況指定的雜湊計算。但是,如果資料包不包含UDP標頭,則NIC應該按照NDIS_HASH_IPV4情況指定的方式計算雜湊值。

6.1.6. NDIS_HASH_TCP_IPV4 | NDIS_HASH_UDP_IPV4 | NDIS_HASH_IPV4

如果設定了此標誌組合,則NIC應該按照資料包中的傳輸指定的方式執行雜湊計算。但是,如果資料包不包含TCP或UDP標頭,則NIC應該按照NDIS_HASH_IPV4情況指定的方式計算雜湊值。

6.2. IPv6雜湊型別組合

IPv6集中的有效雜湊型別組合為:

  • NDIS_HASH_IPV6
  • NDIS_HASH_TCP_IPV6
  • NDIS_HASH_UDP_IPV6
  • NDIS_HASH_TCP_IPV6 | NDIS_HASH_IPV6
  • NDIS_HASH_UDP_IPV6 | NDIS_HASH_IPV6
  • NDIS_HASH_TCP_IPV6 | NDIS_HASH_UDP_IPV6 | NDIS_HASH_IPV6

6.2.1. NDIS_HASH_IPV6

如果僅設定此標誌,則NIC應該在以下欄位上計算雜湊:

  • 源-IPv6-地址
  • 目的IPv6地址

6.2.2. NDIS_HASH_TCP_IPV6

如果僅設定此標誌,則NIC應該解析接收到的資料以標識包含TCP段的IPv6資料包。NIC必須識別並跳過資料包中存在的所有IPv6擴充套件標頭。如果NIC無法跳過任何IPv6擴充套件頭,則不應計算雜湊值。

NIC應該在以下欄位上計算雜湊值:

  • 源IPv6地址
  • 目的IPv6地址
  • 源TCP埠
  • 目標TCP埠

6.2.3. NDIS_HASH_UDP_IPV6

如果僅設定此標誌,則NIC應該解析接收到的資料,以識別包含UDP資料報的IPv6資料包。NIC必須識別並跳過資料包中存在的所有IPv6擴充套件標頭。如果NIC無法跳過任何IPv6擴充套件頭,則不應計算雜湊值。

NIC應該在以下欄位上計算雜湊值:

  • 源-IPv6-地址
  • 目的IPv6地址
  • 源UDP埠
  • 目的UDP埠

6.2.4. NDIS_HASH_TCP_IPV6 | NDIS_HASH_IPV6

如果設定了此標誌組合,則NIC應該執行鍼對NDIS_HASH_TCP_IPV6情況指定的雜湊計算。但是,如果資料包不包含TCP頭,則NIC應該按照NDIS_HASH_IPV6情況指定的方式計算雜湊值。

6.2.5. NDIS_HASH_UDP_IPV6 | NDIS_HASH_IPV6

如果設定了此標誌組合,則NIC應該執行鍼對NDIS_HASH_UDP_IPV6情況指定的雜湊計算。但是,如果資料包不包含UDP標頭,則NIC應該按照NDIS_HASH_IPV6情況指定的方式計算雜湊值。

6.2.6. NDIS_HASH_TCP_IPV6 | NDIS_HASH_UDP_IPV6 | NDIS_HASH_IPV6

如果設定了此標誌組合,則NIC應該按照資料包中的傳輸指定的方式執行雜湊計算。但是,如果資料包不包含TCP或UDP標頭,則NIC應該按照NDIS_HASH_IPV6情況下指定的方式計算雜湊值。

6.3. 具有擴充套件標頭雜湊型別組合的IPv6

IPv6中設定了擴充套件頭的有效組合為:

  • NDIS_HASH_IPV6_EX
  • NDIS_HASH_TCP_IPV6_EX
  • NDIS_HASH_UDP_IPV6_EX
  • NDIS_HASH_TCP_IPV6_EX | NDIS_HASH_IPV6_EX
  • NDIS_HASH_UDP_IPV6_EX | NDIS_HASH_IPV6_EX
  • NDIS_HASH_TCP_IPV6_EX | NDIS_HASH_UDP_IPV6_EX | NDIS_HASH_IPV6_EX

6.3.1. NDIS_HASH_IPV6_EX

如果僅設定此標誌,則NIC應該在以下欄位上計算雜湊:

  • IPv6目標選項標頭中的“家庭地址”選項中的家庭地址。如果擴充套件頭不存在,請使用源IPv6地址。
  • 關聯的擴充套件頭中包含在Routing-Header-Type-2中的IPv6地址。如果擴充套件頭不存在,請使用目標IPv6地址。

6.3.2. NDIS_HASH_TCP_IPV6_EX

如果僅設定此標誌,則NIC應該在以下欄位上計算雜湊:

  • IPv6目標選項標頭中的“家庭地址”選項中的家庭地址。如果擴充套件頭不存在,請使用源IPv6地址。
  • 關聯的擴充套件頭中包含在Routing-Header-Type-2中的IPv6地址。如果擴充套件頭不存在,請使用目標IPv6地址。
  • 源TCP埠
  • 目標TCP埠

6.3.3. NDIS_HASH_UDP_IPV6_EX

如果僅設定此標誌,則NIC應該在以下欄位上計算雜湊:

  • IPv6目標選項標頭中的“家庭地址”選項中的家庭地址。如果擴充套件頭不存在,請使用源IPv6地址。
  • 關聯的擴充套件頭中包含在Routing-Header-Type-2中的IPv6地址。如果擴充套件頭不存在,請使用目標IPv6地址。
  • 源UDP埠
  • 目的UDP埠

6.3.4. NDIS_HASH_TCP_IPV6_EX | NDIS_HASH_IPV6_EX

如果設定了此標誌組合,則NIC應該執行鍼對NDIS_HASH_TCP_IPV6_EX情況指定的雜湊計算。但是,如果資料包不包含TCP頭,則NIC應該按照NDIS_HASH_IPV6_EX情況指定的方式計算雜湊值。

6.3.5. NDIS_HASH_UDP_IPV6_EX | NDIS_HASH_IPV6_EX

如果設定了此標誌組合,則NIC應該執行鍼對NDIS_HASH_UDP_IPV6_EX情況指定的雜湊計算。但是,如果資料包不包含UDP標頭,則NIC應該按照NDIS_HASH_IPV6_EX情況指定的方式計算雜湊值。

6.3.6. NDIS_HASH_TCP_IPV6_EX | NDIS_HASH_UDP_IPV6_EX | NDIS_HASH_IPV6_EX

如果設定了此標誌組合,則NIC應該執行由資料包傳輸指定的雜湊計算。但是,如果資料包不包含TCP或UDP標頭,則NIC應該按照NDIS_HASH_IPV6_EX情況指定的方式計算雜湊值。

  • 注意:如果微型埠驅動程式報告NIC的NDIS_RSS_CAPS_HASH_TYPE_TCP_IPV6_EX和/或NDIS_RSS_CAPS_HASH_TYPE_UDP_IPV6_EX功能,則NIC必須根據協議驅動程式設定的IPv6擴充套件雜湊型別來計算雜湊值(通過IPv6擴充套件頭中的欄位)。NIC可以將擴充套件雜湊型別或常規雜湊型別儲存在IPv6資料包的NET_BUFFER_LIST結構中,為其計算雜湊值。

微型埠驅動程式在指示接收到的資料之前,先在NET_BUFFER_LIST結構中設定雜湊型別。有關更多資訊,請參見指示RSS接收資料。

7. RSS雜湊函式

RSS雜湊函式

NIC或其微型埠驅動程式使用RSS雜湊函式來計算RSS雜湊值。

上層驅動程式設定雜湊型別,函式和表以將連線分配給CPU。有關更多資訊,請參見RSS配置。

雜湊函式可以是以下之一:

  • NdisHashFunctionToeplitz
  • NdisHashFunctionReserved1
  • NdisHashFunctionReserved2
  • NdisHashFunctionReserved3
/* hash function */
#define	NDIS_HASH_FUNCTION_TOEPLITZ	0x00000001

*** 注意:當前,NdisHashFunctionToeplitz是唯一可用於微型埠驅動程式的雜湊函式。其他雜湊函式保留給NDIS使用。 ***

微型埠驅動程式應在驅動程式指示接收到的資料之前,確定其在每個NET_BUFFER_LIST結構中使用的雜湊函式和值。有關更多資訊,請參見指示RSS接收資料。

7.1. 例子

以下四個虛擬碼示例顯示瞭如何計算NdisHashFunctionToeplitz雜湊值。這些示例表示NdisHashFunctionToeplitz可用的四種可能的雜湊型別。有關雜湊型別的更多資訊,請參見RSS雜湊型別。

為了簡化示例,需要一種處理輸入位元組流的通用演算法。稍後在四個示例中定義位元組流的特定格式。

上層驅動程式將金鑰(K)提供給微型埠驅動程式,以供雜湊計算中使用。金鑰的長度為40個位元組(320位)。有關金鑰的更多資訊,請參見RSS配置。

給定一個包含n個位元組的輸入陣列,位元組流的定義如下:

input[0] input[1] input[2] ... input[n-1]

最左邊的位元組是輸入[0],輸入[0]的最高有效位是最左邊的位。最右邊的位元組是輸入[n-1],而輸入[n-1]的最低有效位是最右邊的位。

給定上述定義,用於處理一般輸入位元組流的虛擬碼定義如下:

ComputeHash(input[], n)

result = 0
For each bit b in input[] from left to right
{
if (b == 1) result ^= (left-most 32 bits of K)
shift K left 1 bit position
}

return result

虛擬碼包含@nm形式的條目。這些條目標識TCP資料包中每個元素的位元組範圍。

7.2. 使用TCP標頭對IPv4進行雜湊計算的示例

將資料包的SourceAddress,DestinationAddress,SourcePort和DestinationPort欄位串聯到一個位元組陣列中,保留它們在資料包中出現的順序:

Input[12] = @12-15, @16-19, @20-21, @22-23
Result = ComputeHash(Input, 12)

7.3. 僅適用於IPv4的示例雜湊計算

將資料包的SourceAddress和DestinationAddress欄位串聯到一個位元組陣列中。

Input[8] = @12-15, @16-19
Result = ComputeHash(Input, 8)

7.4. 使用TCP標頭對IPv6進行雜湊計算的示例

將資料包的SourceAddress,DestinationAddress,SourcePort和DestinationPort欄位串聯成一個位元組陣列,以保留它們在資料包中的出現順序。

Input[36] = @8-23, @24-39, @40-41, @42-43
Result = ComputeHash(Input, 36)

7.5. 僅適用於IPv6的示例雜湊計算

將資料包的SourceAddress和DestinationAddress欄位串聯到一個位元組陣列中。

Input[32] = @8-23, @24-39
Result = ComputeHash(Input, 32)

8. 驗證RSS雜湊計算

驗證RSS雜湊計算
您應該驗證您對RSS雜湊計算的實現。要驗證對NdisHashFunctionToeplitz雜湊函式的計算,請使用以下金鑰資料:

0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa

下表提供了NdisHashFunctionToeplitz雜湊函式的IPv4版本的驗證資料。Destination和Source列包含輸入資料,而IPv4列包含結果雜湊值。

下表包含RSS雜湊演算法的IPv6版本的驗證資料。Destination和Source列包含輸入資料,而IPv6列包含結果雜湊值。請注意,提供的IPv6地址僅用於驗證演算法。它們作為實際地址可能沒有意義。



9. RSS配置

RSS配置

要獲取RSS配置資訊,上層驅動程式可以將OID_GEN_RECEIVE_SCALE_CAPABILITIES的OID查詢傳送到微型埠驅動程式。在初始化期間,NDIS還向NDIS_BIND_PARAMETERS結構中的上層協議驅動程式提供RSS配置資訊。

上層驅動程式選擇雜湊函式,型別和間接表。要設定這些配置選項,驅動程式將OID_GEN_RECEIVE_SCALE_PARAMETERS的OID設定請求傳送到微型埠驅動程式。上層驅動程式也可以查詢此OID以獲取當前的RSS設定。OID_GEN_RECEIVE_SCALE_PARAMETERS OID的資訊緩衝區包含一個指向NDIS_RECEIVE_SCALE_PARAMETERS結構的指標。

上層驅動程式可以禁用NIC上的RSS。在這種情況下,驅動程式在NDIS_RECEIVE_SCALE_PARAMETERS結構的Flags成員中設定NDIS_RSS_PARAM_FLAG_DISABLE_RSS標誌。設定此標誌後,微型埠驅動程式應忽略所有其他標誌和設定,並禁用NIC上的RSS。

NDIS在將OID_GEN_RECEIVE_SCALE_PARAMETERS傳遞給微型埠驅動程式之前會對其進行處理,並根據需要更新微型埠介面卡的* RSS標準化關鍵字。有關* RSS關鍵字的更多資訊,請參見RSS的標準化INF關鍵字。

收到設定了NDIS_RSS_PARAM_FLAG_DISABLE_RSS標誌的OID_GEN_RECEIVE_SCALE_PARAMETERS設定請求後,微型埠驅動程式應在初始化後將NIC的RSS狀態設定為NIC的初始狀態。因此,如果微型埠驅動程式收到隨後清除了NDIS_RSS_PARAM_FLAG_DISABLE_RSS標誌的後續OID_GEN_RECEIVE_SCALE_PARAMETERS設定請求,則所有引數都應具有與微型埠驅動程式在微型埠介面卡首次初始化後收到OID_GEN_RECEIVE_SCALE_PARAMETERS設定請求之後設定的相同值。

上層驅動程式可以使用OID_GEN_RECEIVE_HASH OID啟用和配置接收幀上的雜湊計算,而無需啟用RSS。上層驅動程式也可以查詢此OID以獲取當前的接收雜湊設定。

OID_GEN_RECEIVE_HASH OID的資訊緩衝區包含一個指向NDIS_RECEIVE_HASH_PARAMETERS結構的指標。對於設定請求,OID指定微型埠介面卡應使用的雜湊引數。對於查詢請求,OID返回微型埠介面卡正在使用的雜湊引數。對於支援RSS的驅動程式,此OID是可選的。

注意 如果啟用了接收雜湊計算,則NDIS在啟用RSS之前會禁用接收雜湊計算。如果啟用了RSS,則NDIS在啟用接收雜湊計算之前將禁用RSS。

微型埠驅動程式支援的所有微型埠介面卡必須為所有後續協議繫結提供相同的雜湊配置設定。該OID還包括微型埠驅動程式或NIC必須用於雜湊計算的金鑰。金鑰長320位(40位元組),可以包含上層驅動程式選擇的任何資料,例如,隨機位元組流。

為了重新平衡處理負載,上層驅動程式可以設定RSS引數並修改間接表。通常,除間接表外,所有引數均保持不變。但是,初始化RSS後,上層驅動程式可能會更改其他RSS初始化引數。如有必要,微型埠驅動程式可以重置NIC硬體,以更改雜湊函式,雜湊金鑰,雜湊型別,基本CPU編號或用於索引間接表的位數。

注意 上層驅動程式可以隨時設定這些引數。這可能會導致亂序接收指示。在這種情況下,不需要支援TCP的Miniport驅動程式清除其接收佇列。

下圖提供了間接表兩個例項的示例內容。

上圖假定為四個處理器配置,並且從雜湊值使用的最低有效位的數量為6位。因此,間接表包含64個條目。

在圖中,表A列出了初始化後立即在間接表中的值。後來,隨著正常流量負載的變化,處理器負載會變得不平衡。上層驅動程式檢測到不平衡狀況,並通過定義新的間接表來嘗試重新平衡負載。表B列出了新的間接表值。在表B中,來自CPU 2的部分負載已移至CPU 1和3。

注意 更改間接表後,在短時間內(正在處理當前接收描述符佇列時),可以在錯誤的CPU上處理資料包。這是正常的瞬態情況。

間接表的大小通常是系統中處理器數量的2至8倍。

當微型埠驅動程式將資料包分發給CPU時,如果CPU太多,則分配負載所花費的精力可能會變得過高。在這種情況下,上層驅動程式應選擇在其上進行網路資料處理的一部分CPU。

在某些情況下,可用硬體接收佇列的數量可能少於系統上的CPU數量。微型埠驅動程式必須檢查間接表以確定與硬體佇列關聯的CPU號。如果出現在間接表中的不同CPU數量的總數大於NIC支援的硬體佇列的數量,則微型埠驅動程式必須從間接表中選擇CPU數量的子集。該子集的數量等於硬體佇列的數量。微型埠驅動程式從OID_GEN_RECEIVE_SCALE_PARAMETERS獲得了IndirectionTableSize引數。微型埠驅動程式為響應OID_GEN_RECEIVE_SCALE_CAPABILITIES 指定了NumberOfReceiveQueues值。

10. 指示RSS接收資料

指示RSS接收資料

微型埠驅動程式通過從其MiniportInterruptDPC函式呼叫NdisMIndicateReceiveNetBufferLists函式來指示接收到的資料。

void NdisMIndicateReceiveNetBufferLists(
  NDIS_HANDLE      MiniportAdapterHandle,
  PNET_BUFFER_LIST NetBufferList,
  NDIS_PORT_NUMBER PortNumber,
  ULONG            NumberOfNetBufferLists,
  ULONG            ReceiveFlags
);

NIC成功計算RSS雜湊值之後,驅動程式應使用以下巨集在NET_BUFFER_LIST結構中儲存雜湊型別,雜湊函式和雜湊值:

typedef struct _NET_BUFFER_LIST {
  union {
    struct {
      PNET_BUFFER_LIST Next;
      PNET_BUFFER      FirstNetBuffer;
    };
    SLIST_HEADER           Link;
    NET_BUFFER_LIST_HEADER NetBufferListHeader;
  };
  PNET_BUFFER_LIST_CONTEXT Context;
  PNET_BUFFER_LIST         ParentNetBufferList;
  NDIS_HANDLE              NdisPoolHandle;
  PVOID                    NdisReserved[2];
  PVOID                    ProtocolReserved[4];
  PVOID                    MiniportReserved[2];
  PVOID                    Scratch;
  NDIS_HANDLE              SourceHandle;
  ULONG                    NblFlags;
  LONG                     ChildRefCount;
  ULONG                    Flags;
  union {
    NDIS_STATUS Status;
    ULONG       NdisReserved2;
  };
  PVOID                    NetBufferListInfo[MaxNetBufferListInfo];
} NET_BUFFER_LIST, *PNET_BUFFER_LIST;

NET_BUFFER_LIST_SET_HASH_TYPE

void NET_BUFFER_LIST_SET_HASH_TYPE(
  PNET_BUFFER_LIST _NBL,
  volatile ULONG   _HashType
);

NET_BUFFER_LIST_SET_HASH_FUNCTION

void NET_BUFFER_LIST_SET_HASH_FUNCTION(
  PNET_BUFFER_LIST _NBL,
  volatile ULONG   _HashFunction
);

NET_BUFFER_LIST_SET_HASH_VALUE

void NET_BUFFER_LIST_SET_HASH_VALUE(
   _NBL,
   _HashValue
);

雜湊型別標識了應在其上計算雜湊的接收資料包區域。有關雜湊型別的更多資訊,請參見RSS雜湊型別。雜湊函式標識用於計算雜湊值的函式。有關雜湊函式的更多資訊,請參見RSS雜湊函式。協議驅動程式在初始化時選擇雜湊型別和功能。有關更多資訊,請參見RSS配置。

如果NIC無法識別雜湊型別指定的資料包區域,則不應進行任何雜湊計算或縮放。在這種情況下,微型埠驅動程式或NIC應該將接收到的資料分配給預設CPU。

如果NIC用完接收緩衝區,則必須在原始接收DPC返回後立即返回每個緩衝區。微型埠驅動程式可以使用NDIS_STATUS_RESOURCES狀態指示接收到的資料。在這種情況下,上層驅動程式必須經過一條緩慢的路徑,即複製緩衝區描述符並立即放棄對原始描述符的所有權。

有關接收網路資料的更多資訊,請參閱接收網路資料。

11. 使用DPDK配置RSS

DPDK支援RSS功能,靜態雜湊鍵和間接表的配置。每個埠都配置RSS,分發取決於埠上配置的RX佇列的數量。

DPDK要做的是獲取埠的RX佇列,並開始在間接表中重複寫入它們。

例如,如果我們有一個配置了RSS的埠和3個配置了索引0、1和2的RX佇列,那麼大小為128的間接表將如下所示:

{0,1,2,0,1,2,0……}(索引0..127)

流量分佈在這些RX佇列之間,應用程式負責(如果選擇的話)負責輪詢不同CPU中的每個佇列。

  • 要在DPDK中配置RSS,必須在埠rte_eth_conf結構中啟用它。
  • 設定rx_mode.mq_mode = ETH_MQ_RX_RSS
  • 編輯RSS配置結構:rx_adv_conf.rss_conf(更改雜湊鍵或將NULL保留為預設值,然後選擇RSS模式)

啟用RSS時,每個傳入的資料包(rte_mbuf)的元資料結構中都具有RSS雜湊值結果,因此可以在mbuf.hash.rss中對其進行訪問,因為其他應用程式(提示:流表)以後可以使用,所以將其全部使用此雜湊值,而無需重新計算雜湊。

可以在執行時重新配置間接表(稱為RETA),這意味著應用程式可以動態更改每個間接表索引發送流量的佇列。

RETA配置功能是針對每個輪詢模式驅動程式實現的,例如,對於ixgbe驅動程式,請查詢以下功能:

ixgbe_dev_rss_reta_updateixgbe_dev_rss_reta_query

12. DPDK的RSS資料結構

/**
 * A structure used to configure the Receive Side Scaling (RSS) feature
 * of an Ethernet port.
 * If not NULL, the *rss_key* pointer of the *rss_conf* structure points
 * to an array holding the RSS key to use for hashing specific header
 * fields of received packets. The length of this array should be indicated
 * by *rss_key_len* below. Otherwise, a default random hash key is used by
 * the device driver.
 *
 * The *rss_key_len* field of the *rss_conf* structure indicates the length
 * in bytes of the array pointed by *rss_key*. To be compatible, this length
 * will be checked in i40e only. Others assume 40 bytes to be used as before.
 *
 * The *rss_hf* field of the *rss_conf* structure indicates the different
 * types of IPv4/IPv6 packets to which the RSS hashing must be applied.
 * Supplying an *rss_hf* equal to zero disables the RSS feature.
 */
struct rte_eth_rss_conf {
	uint8_t *rss_key;    /**< If not NULL, 40-byte hash key. */
	uint8_t rss_key_len; /**< hash key length in bytes. */
	uint64_t rss_hf;     /**< Hash functions to apply - see below. */
};
  • rss_key:rss_key 陣列。如果 為 NULL,留給網絡卡設定 rss_key。
  • rss_key_len:rss_key陣列的位元組數。
  • rss_hf:需要對報文的分析的元組型別。常用的組合有 l3: ETH_RSS_IP, l3+l4: ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP。

13. RSS在port_init的配置

// 埠的配置資訊
struct rte_eth_conf port_conf =
{
#if 1
	.rxmode = {
    	//.split_hdr_size = 0,
    	.mq_mode = ETH_MQ_RX_RSS,	// 使用RSS分流
	},
	.rx_adv_conf = {
		.rss_conf = {
			.rss_key = NULL,		// 留給網絡卡設定 rss_key	
			// .rss_key_len = 0,		// rss_key陣列的位元組數
			.rss_hf = ETH_RSS_IP	// 通過l3層 tuple計算rss hash
					| ETH_RSS_UDP	// 通過l4層 UDP tuple計算rss hash
					| ETH_RSS_TCP,
					// | ETH_RSS_SCTP,	// 通過l4層 TCP tuple計算rss hash
			// .rss_hf  = ETH_RSS_IP ,	// l3層tuple計算rss hash
		},
	},
#endif
#if 0		
		.fdir_conf = {
		.mode = RTE_FDIR_MODE_PERFECT,
		.pballoc = RTE_FDIR_PBALLOC_64K,
		.status = RTE_FDIR_REPORT_STATUS,
		.drop_queue = 127,
		.mask = {
			.vlan_tci_mask = 0x0,
			.ipv4_mask = {
				.src_ip = 0xFFFFFFFF,
				.dst_ip = 0xFFFFFFFF,
			},
			.src_port_mask = 0xFFFF,
			.dst_port_mask = 0xFF00,

			.mac_addr_byte_mask = 0xFF,
			.tunnel_type_mask = 1,
			.tunnel_id_mask = 0xFFFFFFFF,

		},
		.drop_queue = 127,
	},
#endif

#if 0
	.txmode = {
		.offloads =
			DEV_TX_OFFLOAD_VLAN_INSERT |
			DEV_TX_OFFLOAD_IPV4_CKSUM |
			DEV_TX_OFFLOAD_UDP_CKSUM |
			DEV_TX_OFFLOAD_TCP_CKSUM |
			DEV_TX_OFFLOAD_SCTP_CKSUM |
			DEV_TX_OFFLOAD_TCP_TSO,
	},
#endif
};

DPDK 20.05rte_eth_conf結構如下:

/**
 * A structure used to configure an Ethernet port.
 * Depending upon the RX multi-queue mode, extra advanced
 * configuration settings may be needed.
 */
struct rte_eth_conf {
	uint32_t link_speeds; /**< bitmap of ETH_LINK_SPEED_XXX of speeds to be
				used. ETH_LINK_SPEED_FIXED disables link
				autonegotiation, and a unique speed shall be
				set. Otherwise, the bitmap defines the set of
				speeds to be advertised. If the special value
				ETH_LINK_SPEED_AUTONEG (0) is used, all speeds
				supported are advertised. */
	struct rte_eth_rxmode rxmode; /**< Port RX configuration. */
	struct rte_eth_txmode txmode; /**< Port TX configuration. */
	uint32_t lpbk_mode; /**< Loopback operation mode. By default the value
			         is 0, meaning the loopback mode is disabled.
				 Read the datasheet of given ethernet controller
				 for details. The possible values of this field
				 are defined in implementation of each driver. */
	struct {
		struct rte_eth_rss_conf rss_conf; /**< Port RSS configuration */
		struct rte_eth_vmdq_dcb_conf vmdq_dcb_conf;
		/**< Port vmdq+dcb configuration. */
		struct rte_eth_dcb_rx_conf dcb_rx_conf;
		/**< Port dcb RX configuration. */
		struct rte_eth_vmdq_rx_conf vmdq_rx_conf;
		/**< Port vmdq RX configuration. */
	} rx_adv_conf; /**< Port RX filtering configuration. */
	union {
		struct rte_eth_vmdq_dcb_tx_conf vmdq_dcb_tx_conf;
		/**< Port vmdq+dcb TX configuration. */
		struct rte_eth_dcb_tx_conf dcb_tx_conf;
		/**< Port dcb TX configuration. */
		struct rte_eth_vmdq_tx_conf vmdq_tx_conf;
		/**< Port vmdq TX configuration. */
	} tx_adv_conf; /**< Port TX DCB configuration (union). */
	/** Currently,Priority Flow Control(PFC) are supported,if DCB with PFC
	    is needed,and the variable must be set ETH_DCB_PFC_SUPPORT. */
	uint32_t dcb_capability_en;
	struct rte_fdir_conf fdir_conf; /**< FDIR configuration. DEPRECATED */
	struct rte_intr_conf intr_conf; /**< Interrupt mode configuration. */
};

14. rss_key條件

  • 對稱rss_key條件下,一共四個佇列,結果rss通過hash之後分流到其中兩個佇列 0和3,分流效果不好(相同的IP在同一個佇列)。
  • 非對稱rss_key條件下,rss通過hash之後分流到四個佇列,分流效果均衡(相同的IP分到不同的佇列)

15. 對稱RSS

對稱RSS
DPDK 之 Symmetric Receive-side Scaling

在網路應用程式中,具有相同CPU處理連線的兩側(稱為對稱流)非常重要。許多網路應用程式需要儲存有關連線的資訊,並且您不希望在兩個CPU之間共享此資訊,這會引入鎖定,這是效能不佳的選擇。

RSS演算法通常使用Toeplitz雜湊函式,此函式需要兩個輸入:靜態雜湊金鑰和從資料包中提取的元組

問題在於,DPDK中使用的預設雜湊金鑰(也是Microsoft推薦的金鑰)不會將對稱流分配給同一CPU。

例如,如果我們有以下資料包

{src ip = 1.1.1.1,dst ip = 2.2.2.2,src port = 123,dst port = 88},

則對稱資料包

{src ip = 2.2.2.2,dst ip = 1.1.1.1,src port = 88,dst port = 123}

可能沒有相同的雜湊結果。

我不想過多地瞭解雜湊計算的內部原理,但是可以通過更改雜湊鍵(如我先前所示,可以在DPDK配置中對其進行更改)來實現對稱的RSS,因此鍵的前32位必須相同到第二個32位,隨後的16位應與接下來的16位相同。

使用該金鑰可實現對稱的RSS,問題在於更改此金鑰會導致不同核心之間的流量分配不正確。

但是不要害怕!因為有解決這個問題的方法。一群聰明的人發現,有一個特定的雜湊鍵既可以為您提供對稱的流量分佈,又可以為您提供統一的雜湊鍵(與預設鍵相同)

我可以說我做了一些測試,以檢查此金鑰與隨機ip流量的均勻分佈,發現它是好的(對稱的)。雜湊鍵(以防您不想閱讀文件)是:

/*
 * It is used to allocate the memory for hash key.
 * The hash key size is NIC dependent.
 */
#define RSS_HASH_KEY_LENGTH 64

static uint8_t hash_key [RSS_HASH_KEY_LENGTH] = {
    0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6,0x6,0x6,0x6A 0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,0x6D,0x5A,}

您可以將DPDK配置為在RSS高階配置結構中使用它,如上所示。

15.1. DNA中基於硬體的對稱流平衡

Hardware-based Symmetric Flow Balancing in DNA

多年前,Microsoft定義了RSS(接收方縮放),其目標是通過使多個核心同時處理資料包來改善資料包處理。如今,RSS已在現代的1-10 Gbit網路介面卡中實現,作為一種跨RX佇列分發資料包的方式。當接收到傳入的資料包時,網路介面卡(在硬體中)對資料包進行解碼,並對主要資料包頭欄位(例如IP地址和埠)進行雜湊處理。雜湊結果用於標識資料包將排隊進入哪個入口RX佇列

為了均勻地平衡RX佇列上的流量,RSS實現了非對稱雜湊。這意味著屬於主機A和主機B之間的TCP連線的資料包將進入兩個不同的佇列:A到B進入佇列X,B到A進入佇列Y。此機制保證流量儘可能在所有可用佇列上分配,但是它有一些缺點,因為需要分析雙向流量的應用程式(例如,網路監視和安全應用程式)將需要從所有佇列中讀取資料包同時接收兩個指示。這意味著非對稱RSS限制了應用程式的可伸縮性,因為每個RX佇列無法啟動一個應用程式(因此,您擁有的佇列更多,您可以啟動的應用越多),有必要從所有佇列中讀取資料包,以便同時接收兩個通路資訊。相反,在可伸縮系統中,應用程式必須能夠獨立執行,以便每個應用程式都是一個獨立的系統,如下圖所示。

在PF_RING DNA中,我們添加了通過軟體重新配置RSS機制的功能,以便DNA / libzero應用程式可以確定所需的RSS型別(非DNA應用程式尚不能重新配置RSS)。通常,非對稱RSS對於按分組執行的應用程式(例如,網橋)就足夠了,而對稱RSS是IDS / IPS和網路監控應用程式等需要完整流可見性的應用程式的理想解決方案。

對稱RSS的優點在於,現在可以通過將應用程式繫結到各個佇列來實現可伸縮性。例如,假設您有一個8佇列可識別DNA的網路介面卡,則可以啟動8個snort例項,並將每個例項繫結到不同的佇列(即dna0 @ 0,dna0 @ 1…,dna0 @ 7)和核心。然後,每個例項都獨立於其他例項,並且可以看到流量的兩個方向,因此可以正常執行。

對於那些需要不基於資料包頭的高階流量平衡的使用者(例如,您要根據呼叫者的電話號碼來平衡VoIP呼叫),可以使用libzero。在PF_RING演示應用程式中,我們基於libzero建立了幾個示例(pfdbacluster_master和pfdnacluster_multithread),演示瞭如何實現靈活的資料包平衡(請參見兩個應用程式的-m命令列選項)。

16. 軟體RSS

RSS的一大優勢是,它可以在硬體中完成,當然也可以在不支援RSS的情況下完成軟體實現,或者也可以在TX端執行統一分發。

在本系列的後面部分中,我將描述動態負載分配的實現,但是與此同時,您可以看看toeplitz hash的這種軟體實現作為參考。

17. DPDK中的多佇列和RSS

Multiple queue and RSS in DPDK

17.1. 接收佇列

rte_eth_dev->data(對應結構體rte_eth_dev_data)儲存裝置的(接收/傳送)佇列資訊:

struct rte_eth_dev_data {
	char name[RTE_ETH_NAME_MAX_LEN]; /**< Unique identifier name */

	void **rx_queues; /**< Array of pointers to RX queues. */
	void **tx_queues; /**< Array of pointers to TX queues. */
	uint16_t nb_rx_queues; /**< Number of RX queues. */
	uint16_t nb_tx_queues; /**< Number of TX queues. */
///...

rx_queues為接收佇列指標陣列,每個指標指向和一個具體的接收佇列,以igb驅動(drivers/net/e1000)為例:

/**
 * Structure associated with each RX queue.
 */
struct igb_rx_queue {
	struct rte_mempool  *mb_pool;   /**< mbuf pool to populate RX ring. */
	volatile union e1000_adv_rx_desc *rx_ring; /**< RX ring virtual address. */
	uint64_t            rx_ring_phys_addr; /**< RX ring DMA address. */
	volatile uint32_t   *rdt_reg_addr; /**< RDT register address. */
	volatile uint32_t   *rdh_reg_addr; /**< RDH register address. */
	struct igb_rx_entry *sw_ring;   /**< address of RX software ring. */
	struct rte_mbuf *pkt_first_seg; /**< First segment of current packet. */
	struct rte_mbuf *pkt_last_seg;  /**< Last segment of current packet. */
	uint16_t            nb_rx_desc; /**< number of RX descriptors. */
	uint16_t            rx_tail;    /**< current value of RDT register. */
	uint16_t            nb_rx_hold; /**< number of held free RX desc. */
	uint16_t            rx_free_thresh; /**< max free RX desc to hold. */
	uint16_t            queue_id;   /**< RX queue index. */
	uint16_t            reg_idx;    /**< RX queue register index. */
	uint8_t             port_id;    /**< Device port identifier. */
	uint8_t             pthresh;    /**< Prefetch threshold register. */
	uint8_t             hthresh;    /**< Host threshold register. */
	uint8_t             wthresh;    /**< Write-back threshold register. */
	uint8_t             crc_len;    /**< 0 if CRC stripped, 4 otherwise. */
	uint8_t             drop_en;  /**< If not 0, set SRRCTL.Drop_En. */
};

每個佇列包含一個硬體描述符ring(rx_ring)和一個軟體描述符ring(sw_ring)rx_ring主要由驅動與硬體使用,sw_ring實際上是是一個mbuf指標,主要由DPDK應用程式使用。

  • e1000_adv_rx_desc

硬體描述符,所有的e1000_adv_rx_desc構成一個環形DMA緩衝區。對於接收資料時,pkt_addr指向rte_mbuf->buf_physaddr,從而使得網絡卡收到資料時,將資料寫到mbuf對應的資料緩衝區。

/* Receive Descriptor - Advanced */
union e1000_adv_rx_desc {
	struct {
		__le64 pkt_addr; /* Packet buffer address */
		__le64 hdr_addr; /* Header buffer address */
	} read;
	struct {
		struct {
			union {
				__le32 data;
				struct {
					__le16 pkt_info; /*RSS type, Pkt type*/
					/* Split Header, header buffer len */
					__le16 hdr_info;
				} hs_rss;
			} lo_dword;
			union {
				__le32 rss; /* RSS Hash */
				struct {
					__le16 ip_id; /* IP id */
					__le16 csum; /* Packet Checksum */
				} csum_ip;
			} hi_dword;
		} lower;
		struct {
			__le32 status_error; /* ext status/error */
			__le16 length; /* Packet length */
			__le16 vlan; /* VLAN tag */
		} upper;
	} wb;  /* writeback */
};
  • igb_rx_entry
    每個硬體描述符都有一個對應的軟體描述符,它是DPDK應用程式與DPDK驅動之間進行資料傳遞的橋樑,它實際上是一個rte_mbuf的指標,rte_mbuf->buf_physaddr為DMA的實體地址,由網絡卡硬體使用,rte_mbuf->buf_addr為buffer的虛擬地址,由DPDK應用程式使用。
/**
 * Structure associated with each descriptor of the RX ring of a RX queue.
 */
struct igb_rx_entry {
	struct rte_mbuf *mbuf; /**< mbuf associated with RX descriptor. */
};

/**
 * The generic rte_mbuf, containing a packet mbuf.
 */
struct rte_mbuf {
	MARKER cacheline0;

	void *buf_addr;           /**< Virtual address of segment buffer. */
	/**
	 * Physical address of segment buffer.
	 * Force alignment to 8-bytes, so as to ensure we have the exact
	 * same mbuf cacheline0 layout for 32-bit and 64-bit. This makes
	 * working on vector drivers easier.
	 */
	phys_addr_t buf_physaddr __rte_aligned(sizeof(phys_addr_t));
///...

17.2. 配置佇列

DPDK應用程式可以呼叫rte_eth_dev_configure設定Port的佇列數量:

ret = rte_eth_dev_configure(portid, nb_rx_queue,
					(uint16_t)n_tx_queue, &port_conf);

rte_eth_dev_configure會呼叫rte_eth_dev_rx_queue_config和rte_eth_dev_tx_queue_config設定接收佇列和傳送佇列:

rte_eth_dev_configure
|---rte_eth_dev_rx_queue_config
|---rte_eth_dev_tx_queue_config

配置接收佇列:

static int
rte_eth_dev_rx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues)
{
	uint16_t old_nb_queues = dev->data->nb_rx_queues;
	void **rxq;
	unsigned i;

	if (dev->data->rx_queues == NULL && nb_queues != 0) { /* first time configuration */
		dev->data->rx_queues = rte_zmalloc("ethdev->rx_queues",
				sizeof(dev->data->rx_queues[0]) * nb_queues,
				RTE_CACHE_LINE_SIZE);
		if (dev->data->rx_queues == NULL) {
			dev->data->nb_rx_queues = 0;
			return -(ENOMEM);
		}
	}
///...

接收佇列建立:rte_eth_rx_queue_setup

DPDK application都會呼叫rte_eth_rx_queue_setup初始化接收佇列。

int
rte_eth_rx_queue_setup(uint8_t port_id, uint16_t rx_queue_id,
		       uint16_t nb_rx_desc, unsigned int socket_id,
		       const struct rte_eth_rxconf *rx_conf,
		       struct rte_mempool *mp)
{
///...
	ret = (*dev->dev_ops->rx_queue_setup)(dev, rx_queue_id, nb_rx_desc,
					      socket_id, rx_conf, mp); ///eth_igb_ops, eth_igb_rx_queue_setup
}

eth_igb_rx_queue_setup會建立接收佇列igb_rx_queue,分配RX ring hardware descriptors(e1000_adv_rx_desc)和software ring(igb_rx_entry):

int
eth_igb_rx_queue_setup(struct rte_eth_dev *dev,
			 uint16_t queue_idx,
			 uint16_t nb_desc,
			 unsigned int socket_id,
			 const struct rte_eth_rxconf *rx_conf,
			 struct rte_mempool *mp)
{
	const struct rte_memzone *rz;
	struct igb_rx_queue *rxq;
	struct e1000_hw     *hw;
	unsigned int size;

	hw = E1000_DEV_PRIVATE_TO_HW(dev->data->dev_private);
///...
	/* First allocate the RX queue data structure. */
	rxq = rte_zmalloc("ethdev RX queue", sizeof(struct igb_rx_queue),
			  RTE_CACHE_LINE_SIZE);
///...
	/*
	 *  Allocate RX ring hardware descriptors. A memzone large enough to
	 *  handle the maximum ring size is allocated in order to allow for
	 *  resizing in later calls to the queue setup function.
	 */
	size = sizeof(union e1000_adv_rx_desc) * E1000_MAX_RING_DESC;
	rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx, size,
				      E1000_ALIGN, socket_id);
///...
	rxq->rdt_reg_addr = E1000_PCI_REG_ADDR(hw, E1000_RDT(rxq->reg_idx));
	rxq->rdh_reg_addr = E1000_PCI_REG_ADDR(hw, E1000_RDH(rxq->reg_idx));
	rxq->rx_ring_phys_addr = rte_mem_phy2mch(rz->memseg_id, rz->phys_addr);
	rxq->rx_ring = (union e1000_adv_rx_desc *) rz->addr;

	/* Allocate software ring. */
	rxq->sw_ring = rte_zmalloc("rxq->sw_ring",
				   sizeof(struct igb_rx_entry) * nb_desc,
				   RTE_CACHE_LINE_SIZE);
}

eth_igb_rx_queue_setup主要完成DMA描述符環形佇列的初始化。

17.3. RSS

通過rx_mode.mq_mode = ETH_MQ_RX_RSS(rte_eth_dev_configure)可以開啟Port的RSS,以l3fwd為例:

static struct rte_eth_conf port_conf = {
	.rxmode = {
		.mq_mode = ETH_MQ_RX_RSS,
		.max_rx_pkt_len = ETHER_MAX_LEN,
		.split_hdr_size = 0,
		.header_split   = 0, /**< Header Split disabled */
		.hw_ip_checksum = 1, /**< IP checksum offload enabled */
		.hw_vlan_filter = 0, /**< VLAN filtering disabled */
		.jumbo_frame    = 0, /**< Jumbo Frame Support disabled */
		.hw_strip_crc   = 1, /**< CRC stripped by hardware */
	},
	.rx_adv_conf = {
		.rss_conf = {
			.rss_key = NULL,
			.rss_hf = ETH_RSS_IP,
		},
	},
	.txmode = {
		.mq_mode = ETH_MQ_TX_NONE,
	},
};

驅動igb的RSS配置

eth_igb_start -> eth_igb_rx_init -> igb_dev_mq_rx_configure

//drivers/net/e1000/igb_rxtx.c
static int
igb_dev_mq_rx_configure(struct rte_eth_dev *dev)
{
	struct e1000_hw *hw =
		E1000_DEV_PRIVATE_TO_HW(dev->data->dev_private);
	uint32_t mrqc;

	if (RTE_ETH_DEV_SRIOV(dev).active == ETH_8_POOLS) {
		/*
		 * SRIOV active scheme
		 * FIXME if support RSS together with VMDq & SRIOV
		 */
		mrqc = E1000_MRQC_ENABLE_VMDQ;
		/* 011b Def_Q ignore, according to VT_CTL.DEF_PL */
		mrqc |= 0x3 << E1000_MRQC_DEF_Q_SHIFT;
		E1000_WRITE_REG(hw, E1000_MRQC, mrqc);
	} else if(RTE_ETH_DEV_SRIOV(dev).active == 0) { ///disable SRIOV
		/*
		 * SRIOV inactive scheme
		 */
		switch (dev->data->dev_conf.rxmode.mq_mode) {
			case ETH_MQ_RX_RSS:
				igb_rss_configure(dev); ///RSS
				break;
///...
}

static void
igb_rss_configure(struct rte_eth_dev *dev)
{
///...
	if (rss_conf.rss_key == NULL)
		rss_conf.rss_key = rss_intel_key; /* Default hash key */
	igb_hw_rss_hash_set(hw, &rss_conf);
}

18. 知識擴充套件

Windows 10體系結構