【RDMA】RDMA技術詳解(四):RDMA之Verbs和編程步驟


目錄

什么是Verbs

相關名詞解釋

Verbs API

Verbs API是什么

設計Verbs API的原因

Verbs API所包含的內容

使用Verbs API編寫RDMA應用程序

查看接口定義

包含頭文件

編寫應用

編譯 & 執行

官方示例程序

libibverbs

librdmacm

參考文獻

ibv_poll_cq()

ibv_req_notify_cq()

ibv_get_cq_event()

ibv_ack_cq_events()

Errors

IBV_WC_WR_FLUSH_ERR(5/0x5)

IBV_WC_RNR_RETRY_EXC_ERR(13/0xd)


原文:https://blog.csdn.net/weixin_33978451/article/details/112398245

 

什么是Verbs

 

Verbs直譯過來是“動詞”的意思,它在RDMA領域中有兩種含義:

1) 由IB規范所描述的一組抽象定義

規定了各廠商的軟硬件在各種Verbs下應該執行的動作或者表現出的行為,IB規范並未規定如何編程實現這些Verbs,在這種含義下,Verbs是與操作系統無關的。

舉個例子,IB規范要求所有RDMA設備必須支持Create QP的行為(IB 規范11.2.5.1):

描述:
​ 為指定的設備創建一個QP。
​ 用戶必須指定一組用於初始化QP的屬性。
​ 如果創建QP所需的屬性有非法值或者缺失,那么應該返回錯誤,該QP不會被創建;如果成功, 那么返回該QP的指針和QPN。
​ ……
輸入:
​ 設備指針;
​ SQ關聯到的CQ;
​ RQ關聯到的CQ,如果是XRC的INI QP,則可以不攜帶此參數;
​ ……
輸出:
​ 新創建的QP的指針;
​ QP Number;
​ SQ的最大WR容量。
​ ……

可以看出IB規范中的Verbs是對一個概念進行定義,講的是“需要支持什么,但具體怎么實現我不做規定”。

2) 由OpenFabrics推動實現的一組RDMA應用編程接口(API)

既然是API,那么必然和運行的操作系統相關。Verbs API有Linux版本以及Windows版本(Windows版很久沒有更新了)。

以Create QP為例,下文引用自Linux用戶態Verbs API的幫助文檔(ibv_create_qp(3): create/destroy queue pair):

名稱:
​ ibv_create_qp - create a queue pair (QP)
概要:

#include <infiniband/verbs.h>

struct ibv_qp ibv_create_qp(struct ibv_pd pd, struct ibv_qp_init_attr *qp_init_attr);

描述:
​ ibv_create_qp()通過一個關聯的PD創建一個QP,參數qp_init_attr是一個ibv_qp_init_attr類型的結構體,其定義在<infiniband/verbs.h>中。

struct ibv_qp_init_attr {

struct ibv_cq *send_cq; /* CQ to be associated with the Send Queue (SQ) */

struct ibv_cq *recv_cq; /* CQ to be associated with the Receive Queue (RQ) */

struct ibv_srq *srq; /* SRQ handle if QP is to be associated with an SRQ, otherwise NULL */

struct ibv_qp_cap cap; /* QP capabilities */

enum ibv_qp_type qp_type; /* QP Transport Service Type: IBV_QPT_RC, IBV_QPT_UC, or IBV_QPT_UD */

...

};

​ 函數ibv_create_qp()會更新qp_init_attr->cap struct的內容,返回創建的QP所真正支持的規格……
返回值:
​ ibv_create_qp()返回被創建的QP的指針,或者在失敗時返回NULL。QPN將在返回的指針所指向的結構體中。

可見Verbs API即是對IB規范中的Verbs定義的具體軟件實現。

Verbs的第一種語義直接查閱IB規范的第11章即可,里面做了非常詳細的描述。

本文介紹的是第二種語義,包含Verbs API是什么,如何和硬件產生交互,我們如何通過Verbs API來編寫RDMA程序。如無特殊說明,下文中的Verbs均特指Verbs API。

 

相關名詞解釋

  • rdma-core

開源RDMA用戶態軟件協議棧,包含用戶態框架、各廠商用戶態驅動、API幫助手冊以及開發自測試工具等。

rdma-core在github上維護,我們的用戶態Verbs API實際上就是它實現的。https://github.com/linux-rdma/rdma-core

 

  • kernel RDMA subsystem

開源的Linux內核中的RDMA子系統,包含RDMA內核框架及各廠商的驅動。

RDMA子系統跟隨Linux維護,是內核的的一部分。一方面提供內核態的Verbs API,一方面負責對接用戶態的接口。

 

  • OFED

全稱為OpenFabrics Enterprise Distribution,是一個開源軟件包集合,其中包含內核框架和驅動、用戶框架和驅動、以及各種中間件、測試工具和API文檔。

開源OFED由OFA組織負責開發、發布和維護,它會定期從rdma-core和內核的RDMA子系統取軟件版本,並對各商用OS發行版進行適配。除了協議棧和驅動外,還包含了perftest等測試工具。

下圖為OFA給出的OFED的概覽:

 

43666da8107d1235b94797d995820b3b.png

除了開源OFED之外,各廠商也會提供定制版本的OFED軟件包,比如華為的HW_OFED和Mellanox的MLNX_OFED。這些定制版本基於開源OFED開發,由廠商自己測試和維護,會在開源軟件包基礎上提供私有的增強特性,並附上自己的配置、測試工具等。

以上三者是包含關系。無論是用戶態還是內核態,整個RDMA社區非常活躍,框架幾乎每天都在變動,都是平均每兩個月一個版本。而OFED會定期從兩個社區中取得代碼,進行功能和兼容性測試后發布版本,時間跨度較大,以年為單位計。

Verbs API

 

Verbs API是什么

 

Verbs API是一組用於使用RDMA服務的最基本的軟件接口,也就是說業界的RDMA應用,要么直接基於這組API編寫,要么基於在Verbs API上又封裝了一層接口的各種中間件編寫。

Verbs API向用戶提供了有關RDMA的一切功能,典型的包括:注冊MR、創建QP、Post Send、Poll CQ等等。

對於Linux系統來說,Verbs的功能由rdma-core和內核中的RDMA子系統提供,分為用戶態Verbs接口和內核態Verbs接口,分別用於用戶態和內核態的RDMA應用。

結合上一部分的內容,我們給出一個OFED的全景:

 

e0eba2a4af3d4e6179e178ec3a005632.png

廣義的Verbs API主要由兩大部分組成:

1、IB_VERBS

接口以ibv_xx(用戶態)或者ib_xx(內核態)作為前綴,是最基礎的編程接口,使用IB_VERBS就足夠編寫RDMA應用了。

比如:

  • ibv_create_qp() 用於創建QP
  • ibv_post_send() 用於下發Send WR
  • ibv_poll_cq() 用於從CQ中輪詢CQE

2、RDMA_CM

以rdma_為前綴,主要分為兩個功能:

CMA(Connection Management Abstraction)

在Socket和Verbs API基礎上實現的,用於CM建鏈並交換信息的一組接口。CM建鏈是在Socket基礎上封裝為QP實現,從用戶的角度來看,是在通過QP交換之后數據交換所需要的QPN,Key等信息。

比如:

  • rdma_listen()用於監聽鏈路上的CM建鏈請求。
  • rdma_connect()用於確認CM連接。

CM VERBS

RDMA_CM也可以用於數據交換,相當於在verbs API上又封裝了一套數據交換接口。

比如:

  • rdma_post_read()可以直接下發RDMA READ操作的WR,而不像ibv_post_send(),需要在參數中指定操作類型為READ。
  • rdma_post_ud_send()可以直接傳入遠端QPN,指向遠端的AH,本地緩沖區指針等信息觸發一次UD SEND操作。

上述接口雖然方便,但是需要配合CMA管理的鏈路使用,不能配合Verbs API使用。

Verbs API除了IB_VERBS和RDMA_CM之外,還有MAD(Management Datagram)接口等。

需要注意的是,軟件棧中的Verbs API具體實現和IB規范中的描述並不是完全一致的。IB規范迭代較慢,而軟件棧幾乎每天都有變化,所以編寫應用或者驅動程序時,應以軟件棧API文檔中的描述為准。

狹義的Verbs API專指以ibv_/ib_為前綴的用戶態Verbs接口,因為RDMA的典型應用是在用戶態,下文主要介紹用戶態的Verbs API。

 

設計Verbs API的原因

 

傳統以太網的用戶,基於Socket API來編寫應用程序;而RDMA的用戶,基於Verbs API來編寫應用程序。

Verbs API支持IB/iWARP/RoCE三大RDMA協議,通過統一接口,讓同一份RDMA程序程序可以無視底層的硬件和鏈路差異運行在不同的環境中。

 

Verbs API所包含的內容

用戶態Verbs API主要包含兩個層面的功能:

為方便講解,下面對各接口的形式做了簡化,格式為"返回值1,返回值2 函數名(參數1, 參數2)"

1)控制面:

設備管理:

  • device_list ibv_get_device_list()

用戶獲取可用的RDMA設備列表,會返回一組可用設備的指針。

  • device_context ibv_open_device(device)

打開一個可用的RDMA設備,返回其上下文指針(這個指針會在以后用來對這個設備進行各種操作)。

  • device_attr, errno ibv_query_device(device_context*)

查詢一個設備的屬性/能力,比如其支持的最大QP,CQ數量等。返回設備的屬性結構體指針,以及錯誤碼。

資源的創建,查詢,修改和銷毀:

  • pd ibv_alloc_pd(device_context)

申請PD。該函數會返回一個PD的指針。(PD(內存)保護域--見:https://blog.csdn.net/bandaoyu/article/details/112859981

  • mr ibv_reg_mr(pd, addr, length, access_flag)

注冊MR。用戶傳入要注冊的內存的起始地址和長度,以及這個MR將要從屬的PD和它的訪問權限(本地讀/寫,遠端讀/寫等),返回一個MR的指針給用戶。

  • cq ibv_create_cq(device_context, cqe_depth, ...)

創建CQ。用戶傳入CQ的最小深度(驅動實際申請的可能比這個值大),然后該函數返回CQ的指針。

  • qp ibv_create_qp(pd, qp_init_attr)

創建QP。用戶傳入PD和一組屬性(包括RQ和SQ綁定到的CQ、QP綁定的SRQ、QP的能力、QP類型等),向用戶返回QP的指針。(SRQ=shared receive queue)

  • errno ibv_modiy_qp(qp, attr, attr_mask)

修改QP。用戶傳入QP的指針,以及表示要修改的屬性的掩碼和要修改值。修改的內容可以是QP狀態、對端QPN(QP的序號)、QP能力、端口號和重傳次數等等。如果失敗,該函數會返回錯誤碼。
Modify QP最重要的作用是讓QP在不同的狀態間遷移,完成RST-->INIT-->RTR-->RTS的狀態機轉移后才具備下發Send WR的能力。也可用來將QP切換到ERROR狀態。

  • errno ibv_destroy_qp(qp)

銷毀QP。即銷毀QP相關的軟件資源。其他的資源也都有類似的銷毀接口。

中斷處理:

  • event_info, errno ibv_get_async_event(device_context)

從事件隊列中獲取一個異步事件,返回異步事件的信息(事件來源,事件類型等)以及錯誤碼。

連接管理

  • rdma_xxx()

用於CM建鏈,不在本文展開講。

  • ...

2)數據面:

下發WR

  • bad_wr, errno ibv_post_send(qp, wr)

向一個QP下發一個Send WR,參數wr是一個結構體,包含了WR的所有信息。包括wr_id、sge數量、操作碼(SEND/WRITE/READ等以及更細分的類型)。WR的結構會根據服務類型和操作類型有所差異,比如RC服務的WRITE和READ操作的WR會包含遠端內存地址和R_Key,UD服務類型會包含AH,遠端QPN和Q_Key等。

WR經由驅動進一步處理后,會轉化成WQE下發給硬件。

出錯之后,該接口會返回出錯的WR的指針以及錯誤碼。

  • bad_wr, errno ibv_post_recv(qp, wr)

同ibv_post_send,只不過是專門用來下發RECV操作的WR的接口。

獲取WC

  • num, wc ibv_poll_cq(cq, max_num)

從完成隊列CQ中輪詢CQE,用戶需要提前准備好內存來存放WC,並傳入可以接收多少個WC。該接口會返回一組WC結構體(其內容包括wr_id,狀態,操作碼,QPN等信息)以及WC的數量。

 

使用Verbs API編寫RDMA應用程序

  • 查看接口定義

內核態

內核態Verbs接口沒有專門的API手冊,編程時需要參考頭文件中的函數注釋。聲明這些接口的頭文件位於內核源碼目錄中的:

.../include/rdma/ib_verbs.h

比如ib_post_send()接口:

bcd542df2b9ba4968da751fa7e71fa47.png

函數注釋中有明確介紹該函數的作用,輸入、輸出參數以及返回值。

用戶態

有多種方法查閱用戶態的Verbs API:

  • 在線查閱最新man page

用戶態的Verbs API手冊跟代碼在一個倉庫維護,手冊地址:

https://github.com/linux-rdma/rdma-core/tree/master/libibverbs/man

這里是按照Linux的man page格式編寫的源文件,直接看源文件可能不太直觀。有很多在線的man page網站可以查閱這些接口的說明,比如官方的連接:

https://man7.org/linux/man-pages/man3/ibv_post_send.3.html

也有一些其他非官方網頁,支持在線搜索:

https://linux.die.net/man/3/ibv_post_send

 

731d4842e6599fa10bc0fa7457f1231c.png

  • 查閱系統man page

如果你使用的商用OS安裝了rdma-core或者libibverbs庫,那么可以直接用man命令查詢接口:

man ibv_post_send

 

e0d2fb93b12caf6996274de5b19dd75b.png

  • 查詢Mellanox的編程手冊

《RDMA Aware Networks Programming User Manual Rev 1.7》,最新版是2015年更新的。該手冊寫的比較詳盡,並且附有示例程序,但是可能與最新的接口有一些差異。

  • 包含頭文件

按需包含以下頭文件:

#include <infiniband/verbs.h> // IB_VERBS 基礎頭文件

#include <rdma/rdma_cma.h> // RDMA_CM CMA 頭文件 用於CM建鏈

#include <rdma/rdma_verbs.h> // RDMA_CM VERBS 頭文件 用於使用基於CM的Verbs接口

編寫應用

下面附上一個簡單的RDMA程序的大致接口調用流程,Client端的程序會發送一個SEND請求給Server端的程序,圖中的接口上文中都有簡單介紹。

需要注意的是圖中的建鏈過程是為了交換對端的GID,QPN等信息,可以通過傳統的Socket接口實現,也可以通過本文中介紹的CMA接口實現。

圖中特意列出了多次modify QP的流程,一方面是把建鏈之后交互得到的信息存入QPC中(即QP間建立連接的過程),另一方面是為了使QP處於具備收/發能力狀態才能進行下一步的數據交互。具體狀態機的內容請回顧9. RDMA之Queue Pair

(假設A節點的某個QP要跟B節點的某個QP交換信息,除了要知道B節點的QP序號——QPN之外,還需要GID,在傳統TCP-IP協議棧中,使用了家喻戶曉的IP地址來標識網絡層的每個節點。而IB協議中的這個標識被稱為GID(Global Identifier,全局ID)https://zhuanlan.zhihu.com/p/163552044

3bba5d9bae953ee8ca3519516cbfdec5.png

編譯 & 執行

不在本文討論范圍內。

官方示例程序

rdma-core的源碼目錄下,為libibverbs和librdmacm都提供了簡單的示例程序,大家編程時可以參考。

libibverbs

位於rdma-core/libibverbs/examples/目錄下,都使用最基礎的IB_VERBS接口實現,所以建鏈方式都是基於Socket的。

  • asyncwatch.c 查詢指定RDMA設備是否有異步事件上報
  • device_list.c 列出本端RDMA設備列表
  • devinfo.c 查詢並打印本端RDMA設備詳細信息,沒有雙端數據交互
  • rc_pingpong.c 基於RC服務類型雙端數據收發示例
  • srq_pingpong.c 基於RC服務類型雙端數據收發示例,與上一個示例程序的差異是使用了SRQ而不是普通的RQ。
  • ud_pingpong.c 基於UD服務類型雙端數據收發示例
  • ud_pingpong.c 基於UC服務類型雙端數據收發示例
  • xsrq_pingpong.c 基於XRC服務類型雙端數據收發示例

librdmacm

位於rdma-core/librdmacm/examples/目錄下:

  • rdma_client/server.c 基礎示例,通過CM建鏈並使用CM VERBS進行數據收發。

該目錄剩下的程序就沒有研究了。

參考文獻

[1] RDMA Aware Networks Programming User Manual Rev 1.7

[2] part1-OFA_Training_Sept_2016

[3] https://en.wikipedia.org/wiki/OpenFabrics_Alliance

 

API查詢地址:https://www.rdmamojo.com/2012/11/03/ibv_create_cq/

 

 ibv_get_cq_event,ibv_ack_cq_events-獲取和確認完成隊列(CQ)事件

概要

#include <infiniband / verbs.h>

int ibv_get_cq_event(struct ibv_comp_channel * channel, struct ibv_cq ** cq,void ** cq_context);

void ibv_ack_cq_events(struct ibv_cq * cq,unsigned int nevents);

描述

ibv_get_cq_event()等待事件通道channel中的下一個完成事件。用獲取事件的CQ填充參數cq,並用CQ的上下文填充cq_context。

ibv_ack_cq_events()確認 cq上的nevents事件CQ。

返回值

ibv_get_cq_event()成功返回0,錯誤返回-1。

ibv_ack_cq_events()不返回任何值。

提示

ibv_get_cq_events()返回的所有完成事件必須使用ibv_ack_cq_events()進行確認。

為了避免競爭,銷毀CQ將等待所有完成事件得到確認;這保證了在成功與成功之間一對一的對應關系。

在數據路徑中調用ibv_ack_cq_events()可能相對昂貴,因為它必須使用互斥量。因此,最好是通過對需要確認的事件數量進行計數並在對ibv_ack_cq_events()的一次調用中同時確認幾個完成事件來分攤此成本。

例子

下面的代碼示例演示了一種處理完成事件的可能方法。它執行以下步驟:

第一階段:准備

1.創建一個CQ

2.請求在新的(第一個)完成事件上進行通知

第二階段:完成處理程序

3.等待完成事件並確認

4.要求在下一次完成活動時發出通知

5.清空CQ

注意,可能會觸發額外事件,而無需在CQ中具有相應的完成條目。如果將完成條目添加到步驟4和步驟5之間的CQ,然后在步驟5中清空(輪詢)CQ,則會發生這種情況。

cq = ibv_create_cq(ctx, 1, ev_ctx, channel, 0);
if (!cq)
{
    fprintf(stderr, "Failed to create CQ\n");
    return 1;
}

/* Request notification before any completion can be created */
if (ibv_req_notify_cq(cq, 0))
{
    fprintf(stderr, "Couldn't request CQ notification\n");
    return 1;
}

.
.
.

/* Wait for the completion event */
if (ibv_get_cq_event(channel, &ev_cq, &ev_ctx))
{
    fprintf(stderr, "Failed to get cq_event\n");
    return 1;
}
/* Ack the event */
ibv_ack_cq_events(ev_cq, 1);

/* Request notification upon the next completion event */
if (ibv_req_notify_cq(ev_cq, 0))
{
    fprintf(stderr, "Couldn't request CQ notification\n");
    return 1;
}

/* Empty the CQ: poll all of the completions from the CQ (if any exist)
*/
do
{
    ne = ibv_poll_cq(cq, 1, &wc);
    if (ne < 0)
    {
        fprintf(stderr, "Failed to poll completions from the CQ\n");
        return 1;
    }
    /* there may be an extra event with no completion in the CQ */
    if (ne == 0)
        continue;

    if (wc.status != IBV_WC_SUCCESS)
    {
        fprintf(stderr, "Completion with status 0x%x was found\n",
                wc.status);
        return 1;
    }
}
while (ne);
The following code example demonstrates one possible way to work with 
completion events in non - blocking mode. It performs the following steps:
1. Set the completion event channel to be non - blocked
2. Poll the channel until there it has a completion event
3. Get the completion event and ack it

/* change the blocking mode of the completion channel */
flags = fcntl(channel->fd, F_GETFL);
rc = fcntl(channel->fd, F_SETFL, flags | O_NONBLOCK);
if (rc < 0)
{
    fprintf(stderr, "Failed to change file descriptor of completion event channel\n");
    return 1;
}
/*
 * poll the channel until it has an event and sleep ms_timeout
 * milliseconds between any iteration
 */
my_pollfd.fd      = channel->fd;
my_pollfd.events  = POLLIN;
my_pollfd.revents = 0;
do
{
    rc = poll(&my_pollfd, 1, ms_timeout);
}
while (rc == 0);
if (rc < 0)
{
    fprintf(stderr, "poll failed\n");
    return 1;
}
ev_cq = cq;
/* Wait for the completion event */
if (ibv_get_cq_event(channel, &ev_cq, &ev_ctx))
{
    fprintf(stderr, "Failed to get cq_event\n");
    return 1;
}
/* Ack the event */
ibv_ack_cq_events(ev_cq, 1);

 

ibv_poll_cq()

intibv_poll_cq(structibv_cq *cq,intnum_entries,structibv_wc *wc);

用於從 Completion Queue 中查詢已完成的 Work Request。

所有的 Receive Request、signaled Send Request 和出錯的 Send Request 在完成之后都會產生一個 Work Completion,Work Completion 就被放入完成隊列(Completion Queue)中。

完成隊列是 FIFO 的,ibv_poll_cq() 檢查是否有 Work Completion 在完成隊列中,如果是那么就將隊首彈出,並返回那個 Work Completion 到 *wc 中。

ibv_wc 的結構如下,描述了一個 Work Completion 的情況。

structibv_wc {
uint64_twr_id;
enumibv_wc_status status;
enumibv_wc_opcode opcode;
uint32_tvendor_err;
uint32_tbyte_len;
uint32_timm_data;
uint32_tqp_num;
uint32_tsrc_qp;
intwc_flags;
uint16_tpkey_index;
uint16_tslid;
uint8_tsl;
uint8_tdlid_path_bits;
};
  • wr_id 由產生 Work Completion 的 Request 決定

  • status 是操作的狀態,通常為 IBV_WC_SUCCESS 表示 Work Completion 成功完成,其他還有一些錯誤信息

  • opcode 表示當前的 Work Competition 是怎么產生的

  • bute_len 表示傳輸的字節數

參數說明:

Name Direction Description
*cq in 用於存放 Work Completion 的完成隊列
num_entries in 表示最大從完成隊列中讀取多少個 Work Completion
*wc out 將讀取到的 Work Completion 返回出來,如果有多個則返回的是數組

函數的返回值:成功則返回讀取到的 Work Completion 數量,為 0 表示未讀取到 Work Completion,可認為是完成隊列為空,為負值則表示讀取出錯。

ibv_req_notify_cq()

intibv_req_notify_cq(structibv_cq *cq,intsolicited_only);

用於在完成隊列中請求一個完成通知。

調用 ibv_req_notify_cq() 之后,下一個被加到 CQ 中的請求(發送請求或者接收請求)會被加上通知標記,當請求完成產生一個 Work Completion 之后就會產生通知,完成通知將被 ibv_get_cq_event() 函數讀取出來。

傳入的參數中:

  • solicited_only 為 0 時表示無論下一個加入 CQ 的請求是哪種類型的都會產生通知,否則只有 Solicited 或者出錯的 Work Completion 才會產生通知

ibv_get_cq_event()

intibv_get_cq_event(structibv_comp_channel *channel,
structibv_cq **cq,void**cq_context);

用於等待某一 channel 中的下一個通知產生。

ibv_get_cq_event() 默認是一個阻塞函數,調用之后會將當前程序阻塞在這里,直到下一個通知事件產生。

當 ibv_get_cq_event() 收到完成事件的通知之后,需要調用 ibv_get_cq_event() 來確認事件。

典型用法:

  • Stage I:准備階段

創建一個 CQ,並且將它與一個 Completion Event Channel 相關聯;

用 ibv_req_notify_cq() 對一個 Completion Work 調用通知請求;

  • Stage II:運行中

等待事件產生;

產生之后處理事件,並且調用 ibv_ack_cq_events() 來確認事件;

對下一個 Completion Work 調用 ibv_req_notify_cq() 的通知請求;

參數說明:

Name Direction Description
*channel in 關聯在 CQ 上的 Completion Event Channel
**cq out 從 Completion 事件中得到的一個 CQ
**cq_context out 從 Completion 事件中得到的 CQ context

返回值:為 0 表示成功,-1 在非阻塞模式下表示當前沒有事件可讀,阻塞模式則表示出錯。

這種機制用於避免 CPU 反復讀取 Work Completion ,若不采用事件的方法則只能通過不斷地 ibv_poll_cq() 來輪詢是否有事件完成

ibv_ack_cq_events()

voidibv_ack_cq_events(structibv_cq *cq,unsignedintnevents);

用於確認已完成 Completion events。

Errors

IBV_WC_WR_FLUSH_ERR(5/0x5)

Work Request Flushed Error

當 QP 的傳送狀態處於 Error 狀態時,任何操作都會引發該錯誤。

IBV_WC_RNR_RETRY_EXC_ERR(13/0xd)

Receiver-Not-Ready Retry Error

當接收端沒有准備好 Recv Request 時發送端產生了一個 Send Request 就會發生 RNR_RETRY 錯誤。

要求 ibv_post_recv() 必須在 ibv_post_send 之前完成,所以一種基本的思路就是一開始就 Post 一堆 Recv Request 到隊列中去,然后檢查當隊列中的 Recv Request 少於一定數量時補充,保證不管發送端什么時候 Post Send Request 時,接收端都有足夠的 Recv Request 來接收。

 

問題是如果發送端毫無顧忌地可以任意發送數據,尤其是在 RDMA_WRITE 方式,接收端這邊會不會來不及取走數據,就被發送端傳過來的新數據覆蓋掉了?

 

或者設置 ibv_modify_qp() 參數中的 min_rnr_timer 以及 rnr_retry,前者是重試間隔時間,后者是重試次數,當 rnr_retry 為 7 時表示重試無限次。這種方法可用於重試直到接收端確認取走數據,並且准備好下一次的 Recv Request,然后發送端再進行發送。

當發送端發生 RNR_RETRY 錯誤時,重新調用 ibv_post_send() 是沒用的,因為此時 QP 已經進入錯誤狀態,接下來不管什么樣的操作都會繼續引發 IBV_WC_WR_FLUSH_ERR 錯誤。

除非另外使用一種流控制的方式,不然上面的兩種解決方案都總會存在一定的局限性。

注意:本文來自Chenfan Blog。本站無法對本文內容的真實性、完整性、及時性、原創性提供任何保證,請您自行驗證核實並承擔相關的風險與后果!
CoLaBug.com遵循[CC BY-SA 4.0]分享並保持客觀立場,本站不承擔此類作品侵權行為的直接責任及連帶責任。您有版權、意見、投訴等問題,請通過[eMail]聯系我們處理,如需商業授權請聯系原作者/原網站。

  • 發布在

 

在分布式應用中,用以太網組網往往成為性能的瓶頸,所以需要低時延大帶寬,使用RDMA傳輸協議,能滿足低時延要求。目前有兩張硬件可以使用RDMA傳輸,一個是infiniband,一個是RDMA over Ethernet,由於IB的成本較高,所以RoCE成為一種趨勢。

RoCE可以在以太網上運行RDMA協議,時延比普通以太網可以提升30%以上,也可以支持雙協議棧,同時用TCP和RDMA,編程過程類似IB。

有兩張建鏈方式,一種是通過RDMA_CM建鏈,一種是先通過TCP建鏈,通過tcp通道交換雙方的設備信息,QP信息,簡歷RDMA鏈路,然后關閉tcp鏈路,第二種更常用。

 

RDMA編程流程

1)初始化RDMA設備

ibv_get_device_list()獲取使用可以使用RDMA傳輸的設備個數,可以根據ibv_get_device_list結構中的dev_name找到需要使用的設備;

struct ibv_device **ibv_get_device_list(int *num_devices);

ibv_open_device()打開設備,獲取設備句柄;

ibv_query_device()查詢設備,獲取設備屬性

ibv_query_port()查詢設備端口屬性

如果類型為Ethernet,bv_query_gid()獲取設備GID,用於交換雙方信息使用

 

2)創建QP信息

ibv_alloc_pd()用於創建qp接口的參數

ibv_create_cq()創建CQ,一個CQ可以完成的CQE的個數,CQE與隊列個數有關,隊列多,CQE個數就設置多,否則設置少,一個CQ可以對應一個QP,也可以兩個CQ對應一個QP。一個CQ包含發送和接收隊列。

ibv_create_qp()創建QP。類似tcp的socket

3)注冊MR信息

ibv_reg_mr()注冊網卡內存信息,把操作系統物理內存注冊到網卡

4)交換QP信息

ibv_modify_qp()交換雙方QP信息,修改QP信息狀態級

Client端:先創建QP,修改狀態級reset到INIT,修改INIT到RTR,然后發送到server端,server端創建QP,修改狀態機有INIT到RTR,然后發送到客戶端,客戶端修改狀態機有RTR到RTS,發送到server端,server端修改狀態機有RTR到RTS,這樣rmda鏈路簡建立成功。

5)發送和接收

ibv_post_recv()接收消息接口

ibv_post_send()發送消息接口

ibv_poll_cq()用於查詢cq隊列是否有事件產生,如果有調用recv接口接收。

(fellow0305)


免責聲明!

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



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