AF_XDP¶
概述¶
AF_XDP 是一個為高效能資料包處理而最佳化的地址族。
本文件假設讀者熟悉 BPF 和 XDP。如果不熟悉,Cilium 專案有一個優秀的參考指南:http://cilium.readthedocs.io/en/latest/bpf/。
透過 XDP 程式的 XDP_REDIRECT 動作,程式可以使用 bpf_redirect_map() 函式將入口幀重定向到其他啟用 XDP 的 netdev。AF_XDP 套接字使 XDP 程式可以將幀重定向到使用者空間應用程式中的記憶體緩衝區。
AF_XDP 套接字 (XSK) 使用普通的 socket() 系統呼叫建立。與每個 XSK 關聯的是兩個環:RX 環和 TX 環。套接字可以在 RX 環上接收資料包,並且可以在 TX 環上傳送資料包。這些環使用 setsockopts XDP_RX_RING 和 XDP_TX_RING 分別註冊和調整大小。每個套接字必須至少有一個環。RX 或 TX 描述符環指向 UMEM 記憶體區域中的資料緩衝區。RX 和 TX 可以共享同一個 UMEM,因此資料包不必在 RX 和 TX 之間複製。此外,如果一個數據包需要保留一段時間,因為可能需要重傳,則指向該資料包的描述符可以更改為指向另一個描述符,並立即重用。這再次避免了複製資料。
UMEM 由許多大小相等的塊組成。環中的描述符透過引用其 addr 來引用幀。addr 只是整個 UMEM 區域內的偏移量。使用者空間使用其認為最合適的任何方法(malloc、mmap、huge pages 等)為此 UMEM 分配記憶體。然後,使用新的 setsockopt XDP_UMEM_REG 向核心註冊此記憶體區域。UMEM 還有兩個環:FILL 環和 COMPLETION 環。FILL 環由應用程式用於向下傳送 addr,以便核心填充 RX 資料包資料。一旦收到每個資料包,對這些幀的引用將出現在 RX 環中。另一方面,COMPLETION 環包含核心已完全傳輸的幀 addr,現在可以被使用者空間再次使用,用於 TX 或 RX。因此,出現在 COMPLETION 環中的幀 addr 是先前使用 TX 環傳輸的 addr。總而言之,RX 和 FILL 環用於 RX 路徑,TX 和 COMPLETION 環用於 TX 路徑。
最後,使用 bind() 呼叫將套接字繫結到裝置以及該裝置上的特定佇列 ID,只有在繫結完成後,流量才會開始流動。
如果需要,UMEM 可以在程序之間共享。如果一個程序想要這樣做,它只需跳過 UMEM 及其相應的兩個環的註冊,在 bind 呼叫中設定 XDP_SHARED_UMEM 標誌,並提交它想要共享 UMEM 的程序的 XSK 以及它自己新建立的 XSK 套接字。然後,新程序將在其自己的 RX 環中接收指向此共享 UMEM 的幀 addr 引用。請注意,由於環結構是單消費者/單生產者(出於效能原因),因此新程序必須建立自己的帶有相關 RX 和 TX 環的套接字,因為它無法與其他程序共享。這也是每個 UMEM 只有一個 FILL 和 COMPLETION 環的原因。由單個程序負責處理 UMEM。
那麼資料包是如何從 XDP 程式分發到 XSK 的呢?有一個名為 XSKMAP 的 BPF 對映(或完整的 BPF_MAP_TYPE_XSKMAP)。使用者空間應用程式可以將 XSK 放置在此對映中的任意位置。然後,XDP 程式可以將資料包重定向到此對映中的特定索引,此時 XDP 驗證該對映中的 XSK 確實已繫結到該裝置和環號。如果不是,則資料包將被丟棄。如果該索引處的對映為空,則資料包也會被丟棄。這也意味著目前必須載入 XDP 程式(並且 XSKMAP 中有一個 XSK)才能透過 XSK 將任何流量傳送到使用者空間。
AF_XDP 可以在兩種不同的模式下執行:XDP_SKB 和 XDP_DRV。如果驅動程式不支援 XDP,或者在載入 XDP 程式時顯式選擇 XDP_SKB,則將採用 XDP_SKB 模式,該模式將 SKB 與通用 XDP 支援一起使用,並將資料複製到使用者空間。這是一種適用於任何網路裝置的後備模式。另一方面,如果驅動程式支援 XDP,則 AF_XDP 程式碼將使用它來提供更好的效能,但仍然需要將資料複製到使用者空間。
概念¶
為了使用 AF_XDP 套接字,需要設定許多相關的物件。以下部分將解釋這些物件及其選項。
有關 AF_XDP 如何工作的概述,您還可以檢視 2018 年 Linux Plumbers 會議上關於此主題的論文:http://vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf。請勿查閱 2017 年關於“AF_PACKET v4”的論文,這是 AF_XDP 的首次嘗試。自那時以來,幾乎所有內容都發生了變化。Jonathan Corbet 還在 LWN 上撰寫了一篇優秀的文章“使用 AF_XDP 加速網路”。可以在 https://lwn.net/Articles/750845/ 找到。
UMEM¶
UMEM 是虛擬連續記憶體區域,分為大小相等的幀。UMEM 與 netdev 以及該 netdev 的特定佇列 ID 相關聯。它透過使用 XDP_UMEM_REG setsockopt 系統呼叫建立和配置(塊大小、headroom、起始地址和大小)。透過 bind() 系統呼叫將 UMEM 繫結到 netdev 和佇列 ID。
AF_XDP 套接字連結到單個 UMEM,但一個 UMEM 可以有多個 AF_XDP 套接字。為了共享透過一個套接字 A 建立的 UMEM,下一個套接字 B 可以透過在 struct sockaddr_xdp 成員 sxdp_flags 中設定 XDP_SHARED_UMEM 標誌,並將 A 的檔案描述符傳遞給 struct sockaddr_xdp 成員 sxdp_shared_umem_fd 來實現。
UMEM 有兩個單生產者/單消費者環,用於在核心和使用者空間應用程式之間傳輸 UMEM 幀的所有權。
環¶
有四種不同型別的環:FILL、COMPLETION、RX 和 TX。所有環都是單生產者/單消費者,因此使用者空間應用程式需要顯式同步多個程序/執行緒才能讀取/寫入它們。
UMEM 使用兩個環:FILL 和 COMPLETION。與 UMEM 關聯的每個套接字必須有一個 RX 佇列、TX 佇列或兩者都有。假設有一個包含四個套接字的設定(所有套接字都執行 TX 和 RX)。那麼將有一個 FILL 環、一個 COMPLETION 環、四個 TX 環和四個 RX 環。
這些環是基於頭(生產者)/尾(消費者)的環。生產者在 struct xdp_ring 生產者成員指向的索引處寫入資料環,並增加生產者索引。消費者在 struct xdp_ring 消費者成員指向的索引處讀取資料環,並增加消費者索引。
這些環透過 _RING setsockopt 系統呼叫配置和建立,並使用適當的 mmap() 偏移量對映到使用者空間(XDP_PGOFF_RX_RING、XDP_PGOFF_TX_RING、XDP_UMEM_PGOFF_FILL_RING 和 XDP_UMEM_PGOFF_COMPLETION_RING)。
環的大小需要是 2 的冪。
UMEM 填充環¶
FILL 環用於將 UMEM 幀的所有權從使用者空間傳輸到核心空間。UMEM addr 在環中傳遞。例如,如果 UMEM 為 64k,每個塊為 4k,那麼 UMEM 有 16 個塊,可以傳遞 0 到 64k 之間的 addr。
傳遞給核心的幀用於入口路徑(RX 環)。
使用者應用程式將 UMEM addr 生成到此環。請注意,如果在對齊的塊模式下執行應用程式,核心將遮蔽傳入的 addr。例如,對於 2k 的塊大小,addr 的 log2(2048) LSB 將被遮蔽,這意味著 2048、2050 和 3000 指的是同一個塊。如果使用者應用程式在未對齊的塊模式下執行,則傳入的 addr 將保持不變。
UMEM 完成環¶
COMPLETION 環用於將 UMEM 幀的所有權從核心空間傳輸到使用者空間。就像 FILL 環一樣,使用 UMEM 索引。
從核心傳遞到使用者空間的幀是已傳送(TX 環)的幀,可以再次被使用者空間使用。
使用者應用程式從此環中消耗 UMEM addr。
RX 環¶
RX 環是套接字的接收端。環中的每個條目都是一個 struct xdp_desc 描述符。該描述符包含 UMEM 偏移量 (addr) 和資料長度 (len)。
如果沒有透過 FILL 環將幀傳遞給核心,則 RX 環上不會(或不能)出現任何描述符。
使用者應用程式從此環中消耗 struct xdp_desc 描述符。
TX 環¶
TX 環用於傳送幀。struct xdp_desc 描述符被填充(索引、長度和偏移量)並傳遞到環中。
要啟動傳輸,需要 sendmsg() 系統呼叫。將來可能會放寬此要求。
使用者應用程式將 struct xdp_desc 描述符生成到此環。
Libbpf¶
Libbpf 是 eBPF 和 XDP 的輔助庫,它使使用這些技術變得更加簡單。它還在 tools/lib/bpf/xsk.h 中包含特定的輔助函式,用於促進 AF_XDP 的使用。它包含兩種型別的函式:一種可以用於簡化 AF_XDP 套接字的設定,另一種可以在資料平面中使用,以安全快速地訪問環。要檢視如何使用此 API 的示例,請檢視 samples/bpf/xdpsock_usr.c 中的示例應用程式,該應用程式使用 libbpf 進行設定和資料平面操作。
我們建議您使用此庫,除非您已成為高階使用者。它將使您的程式簡單得多。
XSKMAP / BPF_MAP_TYPE_XSKMAP¶
在 XDP 方面,有一個 BPF 對映型別 BPF_MAP_TYPE_XSKMAP (XSKMAP),它與 bpf_redirect_map() 結合使用,將入口幀傳遞給套接字。
使用者應用程式透過 bpf() 系統呼叫將套接字插入到對映中。
請注意,如果 XDP 程式嘗試重定向到與佇列配置和 netdev 不匹配的套接字,則該幀將被丟棄。例如,AF_XDP 套接字繫結到 netdev eth0 和佇列 17。只有為 eth0 和佇列 17 執行的 XDP 程式才能成功地將資料傳遞到套接字。有關示例,請參閱示例應用程式 (samples/bpf/)。
配置標誌和套接字選項¶
以下是可以用來控制和監視 AF_XDP 套接字行為的各種配置標誌。
XDP_COPY 和 XDP_ZEROCOPY 繫結標誌¶
當您繫結到套接字時,核心將首先嚐試使用零複製。如果不支援零複製,它將回退到使用複製模式,即將所有資料包複製到使用者空間。但是,如果您想強制使用某種模式,可以使用以下標誌。如果您將 XDP_COPY 標誌傳遞給 bind 呼叫,核心將強制套接字進入複製模式。如果它不能使用複製模式,則 bind 呼叫將失敗並顯示錯誤。相反,XDP_ZEROCOPY 標誌將強制套接字進入零複製模式或失敗。
XDP_USE_NEED_WAKEUP 繫結標誌¶
此選項添加了對一個名為 need_wakeup 的新標誌的支援,該標誌存在於 FILL 環和 TX 環中,使用者空間是這些環的生產者。如果在 bind 呼叫中設定了此選項,則如果核心需要透過系統呼叫顯式喚醒才能繼續處理資料包,則將設定 need_wakeup 標誌。如果該標誌為零,則不需要系統呼叫。
如果在 FILL 環上設定了該標誌,則應用程式需要呼叫 poll() 才能繼續在 RX 環上接收資料包。例如,當核心檢測到 FILL 環上沒有更多緩衝區,並且 NIC 的 RX HW 環上沒有剩餘緩衝區時,可能會發生這種情況。在這種情況下,中斷被關閉,因為 NIC 無法接收任何資料包(因為沒有緩衝區可以放置它們),並且設定了 need_wakeup 標誌,以便使用者空間可以將緩衝區放置在 FILL 環上,然後呼叫 poll(),以便核心驅動程式可以將這些緩衝區放置在 HW 環上並開始接收資料包。
如果為 TX 環設定了該標誌,則意味著應用程式需要顯式通知核心傳送放置在 TX 環上的任何資料包。可以透過 poll() 呼叫(如在 RX 路徑中)或透過呼叫 sendto() 來完成此操作。
在 samples/bpf/xdpsock_user.c 中可以找到如何使用此標誌的示例。使用 libbpf 幫助程式的示例對於 TX 路徑如下所示
if (xsk_ring_prod__needs_wakeup(&my_tx_ring))
sendto(xsk_socket__fd(xsk_handle), NULL, 0, MSG_DONTWAIT, NULL, 0);
即,僅在設定了該標誌時才使用系統呼叫。
我們建議您始終啟用此模式,因為它通常會導致更好的效能,尤其是如果您在同一核心上執行應用程式和驅動程式,而且如果您為應用程式和核心驅動程式使用不同的核心,因為它減少了 TX 路徑所需的系統呼叫數量。
XDP_{RX|TX|UMEM_FILL|UMEM_COMPLETION}_RING setsockopts¶
這些 setsockopts 分別設定 RX、TX、FILL 和 COMPLETION 環應具有的描述符數量。必須設定至少一個 RX 和 TX 環的大小。如果兩者都設定了,您將能夠從應用程式接收和傳送流量,但如果您只想執行其中一項操作,則可以透過僅設定其中一個來節省資源。FILL 環和 COMPLETION 環都是必需的,因為您需要將 UMEM 繫結到您的套接字。但是,如果使用了 XDP_SHARED_UMEM 標誌,則第一個套接字之後的任何套接字都沒有 UMEM,因此在這種情況下不應建立任何 FILL 或 COMPLETION 環,因為將使用共享 UMEM 的環。請注意,環是單生產者單消費者,因此不要嘗試同時從多個程序訪問它們。請參閱 XDP_SHARED_UMEM 部分。
在 libbpf 中,您可以透過分別向 xsk_socket__create 函式的 rx 和 tx 引數提供 NULL 來建立僅 Rx 和僅 Tx 的套接字。
如果您建立了一個僅 Tx 的套接字,我們建議您不要將任何資料包放置在填充環上。如果您這樣做,驅動程式可能會認為您將接收某些內容,但實際上不會,這可能會對效能產生負面影響。
XDP_UMEM_REG setsockopt¶
此 setsockopt 將 UMEM 註冊到套接字。這是包含資料包可以駐留的所有緩衝區的區域。該呼叫需要一個指向此區域開頭的指標以及其大小。此外,它還具有一個名為 chunk_size 的引數,該引數是 UMEM 分成的塊的大小。目前它只能是 2K 或 4K。如果您有一個 128K 的 UMEM 區域和一個 2K 的塊大小,這意味著您最多可以在 UMEM 區域中儲存 128K / 2K = 64 個數據包,並且您的最大資料包大小可以是 2K。
還可以設定 UMEM 中每個緩衝區的 headroom。如果將其設定為 N 位元組,則意味著資料包將在緩衝區的 N 位元組處開始,從而為應用程式留下前 N 位元組。最後一個選項是 flags 欄位,但將在每個 UMEM 標誌的單獨部分中處理。
SO_BINDTODEVICE setsockopt¶
這是一個通用的 SOL_SOCKET 選項,可用於將 AF_XDP 套接字繫結到特定的網路介面。當套接字由特權程序建立並傳遞給非特權程序時,此選項非常有用。設定該選項後,核心將拒絕嘗試將該套接字繫結到不同的介面。更新該值需要 CAP_NET_RAW。
XDP_STATISTICS getsockopt¶
獲取套接字的丟包統計資訊,這對於除錯目的非常有用。支援的統計資訊如下所示
struct xdp_statistics {
__u64 rx_dropped; /* Dropped for reasons other than invalid desc */
__u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
__u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
};
XDP_OPTIONS getsockopt¶
從 XDP 套接字獲取選項。目前唯一支援的選項是 XDP_OPTIONS_ZEROCOPY,它告訴您零複製是否已啟用。
多緩衝區支援¶
透過多緩衝區支援,使用 AF_XDP 套接字的程式可以在複製和零複製模式下接收和傳送由多個緩衝區組成的資料包。例如,一個數據包可以由兩個幀/緩衝區組成,一個包含標頭,另一個包含資料,或者可以透過將三個 4K 幀連結在一起來構造一個 9K 乙太網巨型幀。
一些定義
一個數據包由一個或多個幀組成
AF_XDP 環中的一個描述符始終引用一個幀。如果資料包由單個幀組成,則該描述符引用整個資料包。
要為 AF_XDP 套接字啟用多緩衝區支援,請使用新的繫結標誌 XDP_USE_SG。如果未提供此標誌,則所有多緩衝區資料包將像以前一樣被丟棄。請注意,載入的 XDP 程式也需要處於多緩衝區模式。這可以透過使用“xdp.frags”作為所使用的 XDP 程式的節名稱來完成。
為了表示由多個幀組成的資料包,在 Rx 和 Tx 描述符的 options 欄位中引入了一個名為 XDP_PKT_CONTD 的新標誌。如果為真 (1),則資料包繼續到下一個描述符;如果為假 (0),則意味著這是資料包的最後一個描述符。為什麼與許多 NIC 中找到的資料包結束 (eop) 標誌的邏輯相反?只是為了保持與非多緩衝區應用程式的相容性,這些應用程式在 Rx 上將此位設定為 false,並且應用程式將 Tx 的 options 欄位設定為零,因為任何其他值都將被視為無效描述符。
以下是用於將由多個幀組成的資料包生成到 AF_XDP Tx 環上的語義
當找到無效描述符時,此資料包的所有其他描述符/幀都將被標記為無效且未完成。下一個描述符被視為新資料包的開始,即使這並不是本意(因為我們無法猜測意圖)。與以前一樣,如果您的程式正在生成無效描述符,則說明您有一個必須修復的錯誤。
零長度描述符被視為無效描述符。
對於複製模式,資料包中支援的最大幀數等於 CONFIG_MAX_SKB_FRAGS + 1。如果超過此限制,則到目前為止累積的所有描述符都將被丟棄並被視為無效。要生成一個可以在任何系統上工作的應用程式,無論此配置設定如何,請將 frags 的數量限制為 18,因為該配置的最小值是 17。
對於零複製模式,限制取決於 NIC HW 支援的限制。通常至少是我們檢查過的 NIC 上的五個。我們有意選擇不對零複製模式強制執行嚴格的限制(例如 CONFIG_MAX_SKB_FRAGS + 1),因為這會導致在底層執行複製操作以適應 NIC 支援的限制。這有點違背了零複製模式的目的。如何在“探測多緩衝區支援”部分中解釋瞭如何探測此限制。
在複製模式的 Rx 路徑中,xsk 核心會根據需要將 XDP 資料複製到多個描述符中,並如前所述設定 XDP_PKT_CONTD 標誌。零複製模式的工作方式相同,只是資料未被複制。當應用程式獲取一個 XDP_PKT_CONTD 標誌設定為 1 的描述符時,它意味著資料包由多個緩衝區組成,並且它在下一個描述符中繼續到下一個緩衝區。當收到一個 XDP_PKT_CONTD == 0 的描述符時,它意味著這是資料包的最後一個緩衝區。AF_XDP 保證只有完整的資料包(資料包中的所有幀)才會被髮送到應用程式。如果 AF_XDP Rx 環中沒有足夠的空間,則資料包的所有幀都將被丟棄。
如果應用程式讀取一批描述符,例如使用 libxdp 介面,則不能保證該批次將以完整的資料包結束。它可能會在一個數據包的中間結束,並且該資料包的其餘緩衝區將在下一批次的開頭到達,因為 libxdp 介面不會讀取整個環(除非您有一個巨大的批次大小或一個非常小的環大小)。
可以在本文件後面找到 Rx 和 Tx 多緩衝區支援的示例程式。
用法¶
為了使用 AF_XDP 套接字,需要兩個部分。使用者空間應用程式和 XDP 程式。有關完整的設定和用法示例,請參閱示例應用程式。使用者空間端是 xdpsock_user.c,XDP 端是 libbpf 的一部分。
tools/lib/bpf/xsk.c 中包含的 XDP 程式碼示例如下
SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx)
{
int index = ctx->rx_queue_index;
// A set entry here means that the corresponding queue_id
// has an active AF_XDP socket bound to it.
if (bpf_map_lookup_elem(&xsks_map, &index))
return bpf_redirect_map(&xsks_map, index, 0);
return XDP_PASS;
}
一個簡單但效能不佳的環出隊和入隊可能如下所示
// struct xdp_rxtx_ring {
// __u32 *producer;
// __u32 *consumer;
// struct xdp_desc *desc;
// };
// struct xdp_umem_ring {
// __u32 *producer;
// __u32 *consumer;
// __u64 *desc;
// };
// typedef struct xdp_rxtx_ring RING;
// typedef struct xdp_umem_ring RING;
// typedef struct xdp_desc RING_TYPE;
// typedef __u64 RING_TYPE;
int dequeue_one(RING *ring, RING_TYPE *item)
{
__u32 entries = *ring->producer - *ring->consumer;
if (entries == 0)
return -1;
// read-barrier!
*item = ring->desc[*ring->consumer & (RING_SIZE - 1)];
(*ring->consumer)++;
return 0;
}
int enqueue_one(RING *ring, const RING_TYPE *item)
{
u32 free_entries = RING_SIZE - (*ring->producer - *ring->consumer);
if (free_entries == 0)
return -1;
ring->desc[*ring->producer & (RING_SIZE - 1)] = *item;
// write-barrier!
(*ring->producer)++;
return 0;
}
但是請使用 libbpf 函式,因為它們經過最佳化且可以使用。這將使您的生活更輕鬆。
用法多緩衝區 Rx¶
這是一個簡單的 Rx 路徑虛擬碼示例(為簡單起見,使用 libxdp 介面)。為了保持簡短,已排除錯誤路徑
void rx_packets(struct xsk_socket_info *xsk)
{
static bool new_packet = true;
u32 idx_rx = 0, idx_fq = 0;
static char *pkt;
int rcvd = xsk_ring_cons__peek(&xsk->rx, opt_batch_size, &idx_rx);
xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq);
for (int i = 0; i < rcvd; i++) {
struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++);
char *frag = xsk_umem__get_data(xsk->umem->buffer, desc->addr);
bool eop = !(desc->options & XDP_PKT_CONTD);
if (new_packet)
pkt = frag;
else
add_frag_to_pkt(pkt, frag);
if (eop)
process_pkt(pkt);
new_packet = eop;
*xsk_ring_prod__fill_addr(&xsk->umem->fq, idx_fq++) = desc->addr;
}
xsk_ring_prod__submit(&xsk->umem->fq, rcvd);
xsk_ring_cons__release(&xsk->rx, rcvd);
}
用法多緩衝區 Tx¶
這是一個 Tx 路徑虛擬碼示例(為簡單起見,使用 libxdp 介面),忽略了 umem 的大小是有限的,並且我們最終會耗盡要傳送的資料包。還假設 pkts.addr 指向 umem 中的有效位置。
void tx_packets(struct xsk_socket_info *xsk, struct pkt *pkts,
int batch_size)
{
u32 idx, i, pkt_nb = 0;
xsk_ring_prod__reserve(&xsk->tx, batch_size, &idx);
for (i = 0; i < batch_size;) {
u64 addr = pkts[pkt_nb].addr;
u32 len = pkts[pkt_nb].size;
do {
struct xdp_desc *tx_desc;
tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i++);
tx_desc->addr = addr;
if (len > xsk_frame_size) {
tx_desc->len = xsk_frame_size;
tx_desc->options = XDP_PKT_CONTD;
} else {
tx_desc->len = len;
tx_desc->options = 0;
pkt_nb++;
}
len -= tx_desc->len;
addr += xsk_frame_size;
if (i == batch_size) {
/* Remember len, addr, pkt_nb for next iteration.
* Skipped for simplicity.
*/
break;
}
} while (len);
}
xsk_ring_prod__submit(&xsk->tx, i);
}
探測多緩衝區支援¶
要發現驅動程式是否在 SKB 或 DRV 模式下支援多緩衝區 AF_XDP,請使用 linux/netdev.h 中 netlink 的 XDP_FEATURES 功能來查詢 NETDEV_XDP_ACT_RX_SG 支援。這與查詢 XDP 多緩衝區支援的標誌相同。如果 XDP 在驅動程式中支援多緩衝區,則 AF_XDP 也將在 SKB 和 DRV 模式下支援該多緩衝區。
要發現驅動程式是否在零複製模式下支援多緩衝區 AF_XDP,請使用 XDP_FEATURES 並首先檢查 NETDEV_XDP_ACT_XSK_ZEROCOPY 標誌。如果設定了該標誌,則意味著至少支援零複製,您應該檢查 linux/netdev.h 中的 netlink 屬性 NETDEV_A_DEV_XDP_ZC_MAX_SEGS。將返回一個無符號整數值,說明此裝置在零複製模式下支援的最大 frags 數量。以下是可能的返回值
- 1:此裝置不支援零複製的多緩衝區,因為 max
支援一個片段意味著不可能進行多緩衝區。
- >=2:此裝置支援零複製模式下的多緩衝區。該
返回的數字表示支援的最大 frags 數量。
有關如何透過 libbpf 使用這些資訊的示例,請檢視 tools/testing/selftests/bpf/xskxceiver.c。
零複製驅動程式的多緩衝區支援¶
零複製驅動程式通常使用批處理 API 進行 Rx 和 Tx 處理。請注意,Tx 批處理 API 保證它將提供一批 Tx 描述符,這些描述符以末尾的完整資料包結束。這是為了方便使用多緩衝區支援擴充套件零複製驅動程式。
示例應用程式¶
包含一個xdpsock 基準測試/測試應用程式,演示瞭如何將 AF_XDP 套接字與私有 UMEM 結合使用。假設您希望來自埠 4242 的 UDP 流量最終進入佇列 16,我們將在該佇列上啟用 AF_XDP。在這裡,我們使用 ethtool 來實現這一點
ethtool -N p3p2 rx-flow-hash udp4 fn
ethtool -N p3p2 flow-type udp4 src-port 4242 dst-port 4242 \
action 16
然後可以使用以下命令在 XDP_DRV 模式下執行 rxdrop 基準測試
samples/bpf/xdpsock -i p3p2 -q 16 -r -N
對於 XDP_SKB 模式,請使用開關“-S”代替“-N”,並且所有選項都可以像往常一樣使用“-h”顯示。
此示例應用程式使用 libbpf 來簡化 AF_XDP 的設定和使用。如果您想了解 AF_XDP 的原始 uapi 實際上是如何用於實現更高階的功能的,請檢視 tools/lib/bpf/xsk.[ch] 中的 libbpf 程式碼。
常見問題解答¶
問:我在套接字上看不到任何流量。我做錯了什麼?
- 答:當物理網絡卡的 netdev 初始化時,Linux 通常
每個核心分配一個 RX 和 TX 佇列對。因此,在 8 核系統上,將分配佇列 ID 0 到 7,每個核心一個。在 AF_XDP bind 呼叫或 xsk_socket__create libbpf 函式呼叫中,您指定要繫結的特定佇列 ID,並且您將在套接字上獲得的只是指向該佇列的流量。因此,在上面的示例中,如果您繫結到佇列 0,您將不會獲得任何分配到佇列 1 到 7 的流量。如果您幸運的話,您會看到流量,但通常它會最終出現在您未繫結的佇列之一上。
有很多方法可以解決將您想要的流量傳送到您繫結的佇列 ID 的問題。如果您想檢視所有流量,您可以強制 netdev 僅具有 1 個佇列,佇列 ID 0,然後繫結到佇列 0。您可以使用 ethtool 來執行此操作
sudo ethtool -L <interface> combined 1
如果您只想檢視部分流量,您可以程式設計 NIC 透過 ethtool 過濾流量到您可以繫結 XDP 套接字的單個佇列 ID。這是一個示例,其中往返埠 4242 的 UDP 流量被髮送到佇列 2
sudo ethtool -N <interface> rx-flow-hash udp4 fn sudo ethtool -N <interface> flow-type udp4 src-port 4242 dst-port \ 4242 action 2
其他許多方法都是可能的,所有這些都取決於您擁有的 NIC 的功能。
- 問:我可以使用 XSKMAP 在不同的 umem 之間實現切換
在複製模式下嗎?
- 答:簡短的回答是不,目前不支援。該
XSKMAP 只能用於將佇列 ID X 上傳入的流量切換到繫結到相同佇列 ID X 的套接字。XSKMAP 可以包含繫結到不同佇列 ID 的套接字,例如 X 和 Y,但只有從佇列 ID Y 傳入的流量才能被定向到繫結到相同佇列 ID Y 的套接字。在零複製模式下,您應該使用 NIC 中的交換機或其他分配機制來將流量定向到正確的佇列 ID 和套接字。
問:我的資料包有時會損壞。怎麼回事?
- 答:必須注意不要將 UMEM 中的同一緩衝區饋送到
同時進入多個環。例如,如果您同時將同一個緩衝區饋送到 FILL 環和 TX 環,則 NIC 可能會在傳送資料的同時接收資料到緩衝區中。這將導致某些資料包損壞。同樣的事情也適用於將同一個緩衝區饋送到屬於不同佇列 ID 或使用 XDP_SHARED_UMEM 標誌繫結的 netdev 的 FILL 環中。
鳴謝¶
Björn Töpel (AF_XDP 核心)
Magnus Karlsson (AF_XDP 核心)
Alexander Duyck
Alexei Starovoitov
Daniel Borkmann
Jesper Dangaard Brouer
John Fastabend
Jonathan Corbet (LWN 報道)
Michael S. Tsirkin
Qi Z Zhang
Willem de Bruijn