Stories

Detail Return Return

InfiniBand包頭與ibverbs接口實現(一)—— RDMA WRITE分析 - Stories Detail

InfiniBand是一種高性能網絡技術,其數據包格式設計對實現高效可靠的網絡傳輸至關重要。本文將詳細介紹InfiniBand數據包的頭部結構,分析它們在實際應用中的作用和實現機制。並且我們會討論可靠連接(RC)服務類型下的傳輸頭格式,以及RDMA WRITE等典型操作場景,以及它們在ibverbs接口中的對應關係。

Table of Contents:

  1. 兩類包頭格式
  2. Routing Headers
        2.1.源碼分析
  3. Transport Headers
        3.1.Reliable Connection簡介
        3.2.InfiniBand如何保證Reliable Connection?
        3.3.RDMA WRITE
        3.4.RDMA WRITE如何實現操作完成通知?
        3.5 BTH (Base Transport Header)
        3.6 ETH (Extended Transport Header)
            3.6.1.RETH (RDMA EXTENDED TRANSPORT HEADER)
            3.6.2.AETH (ACK EXTENDED TRANSPORT HEADER)
        3.7.ACK COALESCING機制
        3.8.代碼示例分析
  4. 總結

    兩類包頭格式

    對於InfiniBand中的數據包,它的包頭主要分為兩類,一類是Routing Header,另外一類是Transport Header。我們可以通過以下的圖示瞭解包頭的結構:

圖片

如圖所示,對於Routing Header又分為兩種,分別是LRH(Local Route Header)和GRH(Global Route Header)。對於Transport Header也分為兩種,分別是BTH(Base Transport Header)和ETH(Extended Transport Header),其中ETH也可以分為幾類,稍後我們會詳細介紹。

Routing Headers

對於InfiniBand包的傳輸,通常會存在多個節點之間的傳輸,這就要求我們需要在數據包上儲存路由信息,使得交換機能夠路由數據包到正確的節點。Routing Header就用於指定數據包的路由信息,分為LRH和GRH:

  • LRH (Local Route Header)

LRH是InfiniBand數據包的本地路由頭,負責子網內的數據包傳輸,包含本地尋址、服務質量控制和包長度等基本路由信息。

  • GRH (Global Route Header)

GRH用於跨子網通信,採用IPv6格式提供全局路由能力,包含全局尋址、流量控制和跨網絡傳輸所需的路由信息。
InfiniBand採用LID和GID兩級尋址機制,其中LID用於子網內路由轉發,GID則用於跨子網路由,數據包在子網內通過交換機查詢LID轉發表進行傳輸,到達子網邊界時則使用GID進行路由。二者分別存儲於LRH和GRH中。
LID是一個16-bit的ID,在一個subnet中必須是全局唯一的。它由一箇中心化的Subnet Manager進行分配。和MAC地址類似,它也分為unicast和multicast,一個unicast的LID只能對應一個節點,而multicast的LID可以對應一個或多個節點,具體的映射關係取決於Subnet Manager的配置。
GID在InfiniBand中直接使用了128-bit的ipv6地址作為ID。對於IPv4地址,則是使用了IPv4映射到IPv6地址,使用GID最後四個字節存儲地址信息。

源碼分析

下面我們可以通過對於Linux RDMA代碼定義來具體分析GRH和LRH在實際實現中是如何指定的。在RDMA實現中,我們需要對於每個Queue Pair指定對應的路由信息。這一路由信息由Address Handle存儲,具體的結構體名稱是 ibv_ah_attr 。這個結構體就包含了LRH和GRH相關的信息,代碼如下:

struct ibv_ah_attr {
        struct ibv_global_route        grh;
        uint16_t                dlid;
        uint8_t                        sl;
        uint8_t                        src_path_bits;
        uint8_t                        static_rate;
        uint8_t                        is_global;
        uint8_t                        port_num;
};


struct ibv_global_route {
      union ibv_gid                dgid;
      uint32_t                flow_label;
      uint8_t                        sgid_index;
      uint8_t                        hop_limit;
      uint8_t                        traffic_class;

可以看到GRH對應的全局路由信息被封裝在單獨的 ibv_global_route 結構體中。其中源GID(sgid)使用索引形式存儲,這是因為每個RDMA設備端口都會分配一個或多個GID。RDMA設備維護一個GID表,通過索引就能快速查找到對應的源GID。
LRH對應的本地路由相關的信息則直接存儲在 ibv_ah_attr 結構體中。每個端口(port_num)都對應一個源LID(slid)。為了支持多路徑傳輸,InfiniBand引入了LID Mask Control機制:通過 src_path_bits 字段,可以在基礎LID(base_lid)的基礎上,計算出實際使用的LID。此外, static_rate 字段用於靜態速率控制,確保發送方的傳輸速率不會超過接收方的處理能力。這些參數都由Subnet Manager統一管理和配置。

Transport Headers

InfiniBand的Transport Headers是IB網絡協議中的關鍵組成部分。它包含了數據包的基本信息,包括尋址,確保可靠傳輸、網絡分區管理、以及QoS等控制信息。這些信息是支持InfiniBand支持可靠和非可靠傳輸等多種服務類型的基石。下面我會介紹幾種常見的Transport Header和它們的用途。由於篇幅關係,我們只介紹RC(Reliable Connection)服務類型中所使用的包頭信息。

Reliable Connection簡介

首先我們需要了解RDMA中的服務類型。InfiniBand提供了RC、UC、RD和UD等傳輸服務類型。RC提供可靠的點對點連接;UC提供不可靠的點對點連接;RD提供可靠的數據報服務,支持多播;UD支持不可靠的數據報服務,同樣支持多播。RC因其完整的功能支持和可靠性保證而使用最廣泛,和TCP類似,它設計的目的就是確保數據可靠傳輸以簡化應用層的處理邏輯,即數據被按順序處理並且只交付一次。因此對於RC來説,InfiniBand設備需要追蹤每一個數據包和它們之間的順序。在RC中,我們需要顯式地建立連接,需要在進行數據傳輸前顯式建立連接。每個連接由一對Queue Pair(QP)組成,分別位於通信的兩端。

InfiniBand如何保證Reliable Connection?

InfiniBand網絡通常是部署在單個數據中心中,並且節點間通過高質量的線纜進行連接,但即便如此,在InfiniBand網絡中仍然可能 出現丟包和亂序的問題,例如因為硬件故障或者網絡擁塞導致交換機緩衝區溢出。這就需要我們引入其他機制來確保Reliable Connection。和TCP類似,InfiniBand引入了ACK(Acknowledgment)機制使得接收端向發送端確認收到的數據包。
在InfiniBand中,ACK機制的工作方式如下:

  1. 發送端為每個發出的包分配一個序列號(PSN, Packet Sequence Number)
  2. 接收端按序接收數據包,丟棄PSN重複的包,並通過發送ACK包來確認收到的最大連續序列號
  3. 如果發送端在超時時間內沒有收到某個包的ACK,會重傳該數據包

同時,InfiniBand還實現了NAK(Negative Acknowledgment)機制,即接收端如果發現收到的包序列號不連續,會發送NAK(Negative Acknowledgment)通知發送端重傳丟失的包,以加速重傳的性能。
這樣基於序列號和確認的機制可以有效地檢測和恢復丟包,並且保證數據包按順序到達,並且避免重複。

RDMA WRITE

使用RDMA的目的之一就是能夠直接操作遠端節點的內存實現零拷貝。RDMA WRITE操作就是實現這一目的的基礎。對於一次WRITE操作,InfiniBand設備(HCA)會直接從本地應用程序註冊的內存區域(Memory Region)中讀取數據,然後通過網絡將數據傳輸並直接寫入到遠端節點預先指定的內存位置。這個過程完全繞過了操作系統和CPU的參與,避免了內存拷貝。為了實現這種直接內存訪問,WRITE操作需要以下關鍵要素:

  • 本地端的源數據內存地址和長度
  • 遠端節點的目標內存地址
  • 遠端內存區域的訪問權限

當應用程序發起WRITE請求時,這些信息會被封裝在工作請求(Work Request)中提交給HCA,HCA硬件接管後續所有操作。

圖片

RDMA WRITE如何實現操作完成通知?

RDMA WRITE會直接將數據寫入遠端內存,繞過了操作系統和CPU,然而我們要使得這一操作真正有用,就必須通過某種機制通知用户操作已經完成了,否則用户就無法知道這片內存區域的數據是否完整。因此在InfiniBand中實現了兩種通知機制,一種是接收端HCA通知用户程序一個WRITE操作已經成功接收(WRITE_WITH_IMM),另外一種是發送端HCA通知用户程序一個WRITE操作已經成功發送(SEND_SIGNALED)。這兩種機制使得發送端和接收端用户程序能夠及時獲知操作的完成情況。

  • WRITE_WITH_IMM

當用户需要一個WRITE操作在接收端產生完成事件時,就需要使用WRITE_WITH_IMM操作。IMM指的是一個可選的4bytes的值,會一併包括在完成事件中,它的內容可以用户自定義的各類元數據,方便應用層處理。

  • SEND_SIGNALED

當用户需要在發送端產生一個完成事件時,就需要設置 IBV_SEND_SIGNALED 這一個flag。對於向HCA下發的每一個Work Request,用户需要指定一個ID(wr_id),當操作完成時,HCA會產生一個完成事件(Completion Event),完成事件中包括這個ID幫助用户識別具體完成的操作。

BTH (Base Transport Header)

瞭解了RC服務類型和RDMA WRITE操作的背景,我們可以進一步解析傳輸頭的細節。首先我會介紹BTH傳輸頭, 顧名思義,BTH包含了數據包最基本的信息,對於所有InfiniBand數據包都存在BTH(RAW數據包除外)。BTH中基礎的幾個字段包括:

  • Opcode: 用於指定RDMA操作的類型,例如前面所述的RDMA WRITE操作,除了之前介紹的WRITE/WRITE_WITH_IMM操作以外,還包括READ/SEND/RECV等,每個操作都對應不同的Opcode,使得HCA能夠分別進行處理。
  • Solicited Event: 控制數據包接收端是否應該產生一個完成通知,用於我們所述的WRITE_WITH_IMM操作。
  • FECN/BECN (Forward ECN/Backward ECN): ECN標識。如果置為1,則表示通知對端發生了網絡擁塞。
  • Destination QP: 用於區分數據包不同的目的Queue Pair。
  • Acknowledge Request: 一個flag表示接收端是否需要對這個數據包立即返回一個ACK,用於SEND_SIGNALED機制,因為在發送端的完成事件中,僅當操作中所有發送的數據被ACK後才能夠認為該操作已經完成。
  • Packet Sequence Number: 表示當前包的編號,用於我們剛才介紹的數據包順序追蹤。

ETH (Extended Transport Header)

除了BTH之外,InfiniBand還定義了不同種類的數據包用來實現不同的功能,對於這些不同的數據包的基礎信息,就作為擴展儲存在ETH中。下面我主要介紹RETH和AETH兩種不同的擴展包頭。

RETH (RDMA EXTENDED TRANSPORT HEADER)
回顧我們之前介紹的RDMA WRITE操作,需要有幾個要素,這幾個要素就對應了RETH擴展包頭的字段,用於通知接收端硬件進行相關的寫入操作,包括:

  • Virtual Address: 目標寫入地址
  • Remote Key: 訪問的Key,標識訪問權限
  • DMA Length: 寫入的長度

其中源數據地址僅由本地硬件處理,不包含在傳輸頭中。這樣通過RETH擴展傳輸頭的引入,InfiniBand實現了RDMA WRITE操作的支持。這體現了InfiniBand架構的可擴展性設計理念, 新功能的實現只需定義接收端必要的字段信息,並將其封裝在相應的擴展傳輸頭中即可。

AETH (ACK EXTENDED TRANSPORT HEADER)
在我們之前的討論中,InfiniBand為了實現可靠傳輸,使用了ACK的機制,而AETH就專門用於ACK包的包頭。ACK包不包含payload,它作為控制數據包僅作為通知機制使用。它包括以下字段:

  • Syndrome: 標識是ACK還是NAK,並且攜帶用於流量控制相關的信息。
  • Message Sequence Number: 表示最後一個完成接收的Message對應的序號。這樣發送端如果收到這樣一個MSN,它就能夠認為之前所有的MSN都已經被成功接收。

ACK COALESCING機制

在前面我們介紹了RDMA WRITE操作和SEND_SIGNALED機制,我們可以考慮以下問題:如果用户發送了很多WRITE操作,並且每個操作都需要知道操作已經完成了,那應該怎麼做?
一個顯而易見的方法是我們對於所有WRITE操作都加上一個SEND_SIGNALED的標誌,這樣我們可以知道所有操作的完成情況。然而,這會造成額外的資源佔用,主要包括兩點:

  • 接收端需要對每個操作都回復對應的ACK包,佔用網絡帶寬
  • 發送端需要對於每個操作都產生一個完成事件,佔用硬件資源

當我們發送大量數據時,這顯然會帶來巨大的開銷。那麼InfiniBand是如何解決這個問題的呢?
InfiniBand通過ACK Coalesing 機制來優化這一過程。具體來説,用户可以將多個WRITE操作標記為unsignaled(即普通的WRITE),然後標記最後一個操作是signaled(設置SEND_SIGNALED)。這樣,只有最後一個操作會要求接收端返回ACK,並且產生完成事件。由於在RC中,所有的數據都是有序處理的,因此當發送端收到這個ACK時,就可以認為之前所有的unsignaled操作也都已經成功完成。
然而,需要注意的是,使用這個機制時用户程序必須確保定期發送signaled的操作,用於HCA清除發送隊列。否則,如果發送隊列被unsignaled的操作佔滿, 而沒有任何完成事件產生, 用户無法發送新的signaled的操作清除發送隊列, 就會導致發送隊列無法繼續使用。

代碼示例分析

我們可以通過分析一段RDMA用户程序代碼將前面介紹的概念具象化。這段代碼展示瞭如何構造一個RDMA WRITE請求,以及這些字段是如何對應到傳輸頭中的。這是來自libibverbs庫的API使用示例,libibverbs是用户態程序訪問RDMA設備的標準接口。
首先列出主要的數據結構:

// 描述要發送的數據緩衝區
struct ibv_sge {
    uint64_t addr;    // 本地內存地址
    uint32_t length;  // 數據長度
    uint32_t lkey;    // 本地內存區域的訪問密鑰
};


// 描述一個RDMA操作請求
struct ibv_send_wr {
    uint64_t wr_id;      // 用户定義的請求ID
    struct ibv_sge *sg_list;  // scatter/gather列表,對於單個WRITE請求,我們只使用一個列表元素
    int num_sge;         // sg_list中的元素數量
    enum ibv_wr_opcode opcode;  // 操作類型(WRITE/READ等)
    int send_flags;     // 操作標誌位
    union {
        struct {
            uint64_t remote_addr;  // 遠程內存地址
            uint32_t rkey;         // 遠程內存區域的訪問密鑰
        } rdma;                    // RDMA操作特有字段
    } wr;                         
};

下面是構造一個WRITE請求的示例:

struct ibv_send_wr wr = {};
struct ibv_sge sge = {};


// 設置本地數據緩衝區信息
sge.addr = (uint64_t)local_buffer;  // 本地數據源地址
sge.length = 4096;                  // 傳輸4KB數據
sge.lkey = mr->lkey;               // 本地內存區域的訪問密鑰


// 配置WRITE操作請求
wr.wr_id = 0x1234;                 // 用於標識這個請求
wr.opcode = IBV_WR_RDMA_WRITE;     // 指定為WRITE操作
wr.sg_list = &sge;                 // 指定數據源
wr.num_sge = 1;                    
wr.send_flags = IBV_SEND_SIGNALED; // 設置完成通知標誌位
wr.wr.rdma.remote_addr = remote_addr; // 目標地址
wr.wr.rdma.rkey = remote_key;        // 遠程內存區域的訪問密鑰

這些字段會被HCA硬件轉換為對應的傳輸頭:

  1. BTH (Base Transport Header):
  • opcode字段會被設置為RDMA WRITE
  • send_flags中的IBV_SEND_SIGNALED會設置Acknowledge Request位
  • PSN會由HCA自動分配
  1. RETH (RDMA Extended Transport Header):
  • remote_addr會被填入Virtual Address字段
  • rkey會被填入Remote Key字段
  • length會被填入DMA Length字段

當這個請求被提交給HCA後,HCA會:

  1. 從local_buffer讀取4KB數據
  2. 構造包含BTH和RETH的數據包
  3. 通過網絡發送給接收節點
  4. 接收節點HCA向本地 remote_addr 中寫入對應的數據,並返回ACK
  5. 發送節點等待接收節點節點的ACK (因為設置了IBV_SEND_SIGNALED)
  6. 收到ACK後生成完成事件

這樣就完成了一次RDMA WRITE操作。我們可以看到用户程序只需要通過API調用來使用RDMA功能,而複雜的傳輸頭處理都由硬件自動完成。

總結

InfiniBand傳輸頭的設計體現了其作為高性能網絡的特點。通過包頭的分層設計,既保證了基礎功能的穩定性,又提供了良好的擴展性。深入理解這些機制有助於RDMA編程實踐,也能夠啓發我們在設計分佈式系統時如何權衡各種技術選擇。對這個主題感興趣的讀者可以參考IBTA規範以及Linux源碼獲取更多細節。

關於作者

作者是達坦科技的RDMA軟件工程師,目前負責Blue RDMA驅動的開發和維護工作。歡迎通過Github(@bsbds)與我交流。

關於達坦科技

達坦科技始終致力於打造高性能 Al+ Cloud 基礎設施平台,積極推動 AI 應用的落地。達坦科技通過軟硬件深度融合的方式,提供高性能存儲和高性能網絡。為 AI 應用提供彈性、便利、經濟的基礎設施服務,以此滿足不同行業客户對 AI+Cloud 的需求。
公眾號:達坦科技DatenLord
DatenLord官網:
https://datenlord.github.io/zh-cn/
知乎賬號:
https://www.zhihu.com/org/da-tan-ke-ji
B站:
https://space.bilibili.com/2017027518
郵箱:info@datenlord.com
如果您有興趣加入達坦科技Rust前沿技術交流羣或硬件相關的羣 ,請添加小助手微信:DatenLord_Tech

user avatar u_15714439 Avatar ssbunny Avatar niandb Avatar mamaster777 Avatar 8848_62c77d4bb2532 Avatar rtedevcomm Avatar chongdongdeludeng Avatar fabarta Avatar
Favorites 8 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.