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_t 的 rss hash。填充到 struct rte_mbuf的 hash.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體系結構

在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM