1. 程式人生 > >RDMA傳輸中的SEND/RECEIVE和READ/WRITE

RDMA傳輸中的SEND/RECEIVE和READ/WRITE

最近在做RDMA傳輸相關的專案,現分析和對比傳統TCP/IP通訊和RDMA傳輸在資料互動中的不同之處。

概念解讀

    傳統的TCP/IP通訊,傳送和接收資料的過程中,都是在源端應用層資料從上向下逐層拷貝封裝,目的端從下向上拷貝和解封裝,所以比較慢,而且需要CPU參與的次數很多。RDMA通訊過程中,傳送和接收,讀/寫操作中,都是RNIC直接和參與資料傳輸的已經註冊過的記憶體區域直接進行資料傳輸,速度快,不需要CPU參與,RDMA網絡卡接替了CPU的工作,節省下來的資源可以進行其它運算和服務。由此可以看出,RDMA可以提供低延遲、高吞吐量、低CPU佔用率,適用於高效能運算。

    兩種通訊方式中都有send、write方法,對應的也有receive、read方法。在傳統的TCP/IP通訊過程中,SEND/RECEIVE和READ/WRITE操作除了引數不同之外,沒有本質的區別,都是進行資料的傳送和接收,都是雙邊操作,即C/S都需要參與,這個過程也需要CPU的參與,並且需要記憶體拷貝,帶來很大的網路延遲。但是在RDMA傳輸過程中,SEND和WRITE是完全不同的概念,同樣,RECEIVE和READ也是完全不同的概念。

    在RDMA傳輸中,SEND/RECEIVE是雙邊操作,即需要通訊雙方的參與,並且RECEIVE要先於SEND執行,這樣對方才能傳送資料,當然如果對方不需要傳送資料,可以不執行RECEIVE操作,因此該過程和傳統通訊相似,區別在於RDMA的零拷貝網路技術和核心旁路,延遲低,多用於傳輸短的控制訊息。WRITE/READ是單邊操作,顧名思義,讀/寫操作是一方在執行,在實際的通訊過程中,WRITE/READ操作是由active即客戶端來執行的,而passive即伺服器不需要執行任何操作。RDMA WRITE操作中,由客戶端把資料從本地buffer中直接push到遠端QP的虛擬空間的連續記憶體塊中(實體記憶體不一定連續),因此需要知道目的地址(remote_addr)和訪問許可權(remote_key)。RDMA READ操作中,是客戶端直接到遠端的QP的虛擬空間的連續記憶體塊中獲取資料poll到本地目的buffer中,因此需要遠端QP的記憶體地址和訪問許可權。單邊操作多用於批量資料傳輸。

    可以看出,在單邊操作過程中,客戶端需要知道遠端QP的remote_addr(要讀取或寫入的地址)和remote_key,而這兩個資訊是通過SEND/REVEIVE操作來交換的,RDMA通訊過程的大致流程如下:

1)初始化context,註冊記憶體域

2)建立RDMA連線

3)通過SEND/RECEIVE操作,C/S交換包含RDMA memory region key的MSG_MR訊息(一般是客戶端先發送)

4)通過WRITE/READ操作,進行資料傳輸(單邊操作)

5)傳送MSG_DONE訊息,關閉連線

相關函式

1、RDMA_CM API(<rdma/rdma_verbs.h>)中的有關WRITE/READ和SEND/RECEIVE涉及到的函式如下所示

傳送RDMA WRITE請求,單邊操作

int rdma_post_write (struct rdma_cm_id *id, void *context, void *addr, size_t length, struct ibv_mr *mr, int flags, uint64_t remote_addr, uint32_t rkey);
把寫wr傳送到和rdma_cm_id關聯的qp的sq中,本地資料緩衝區中的資料就會被寫到遠端記憶體中。本地緩衝區和遠端記憶體都是已經註冊過的,才能和RDMA裝置直接互動。
id就是呼叫者對應的rdma_cm_id,context就是使用者自定義的context,addr是傳送者的本地地址,就是傳送資料緩衝區陣列,這個陣列應該不同於send/recv操作對應的緩衝區陣列,length就是緩衝區陣列長度,mr就是這個傳送緩衝區陣列對應的mr,也就是說這個傳送緩衝區陣列是要被RDMA讀取的,所以需要註冊,flags是傳送標誌,remote_addr就是遠端的地址,即伺服器地址,rkey就是訪問遠端地址的許可權。相當於說,我是client,對應id,我要傳送的資料在addr放著,長度length,這個傳送的資料所在的記憶體已經在RDMA裝置上註冊過了,是mr,傳送標誌是flags,控制寫操作,由於我是直接寫到伺服器,伺服器什麼都不做,所以我得知道存到哪,而這個地址是通過send/recv交換過的,所以我就把資料放到remote_addr地址處,當然這塊地址也是已經註冊過的,我有這個地址的鑰匙相當於操作許可權rkey,這個也是伺服器給我的。write就相當於說客戶端一個人通過網路把資料直接放到伺服器的指定位置,傳送之前需要獲取對方地址和key。

傳送RDMA READ請求,單邊操作

int rdma_post_read (struct rdma_cm_id *id, void *context, void *addr, size_t length, struct ibv_mr *mr, int flags, uint64_t remote_addr, uint32_t rkey);
把讀wr傳送到和rdma_cm_id關聯的qp的sq中,遠端的資料將會被讀到本地緩衝區中。本地和遠端的資料緩衝區都是和RDMA裝置直接打交道的,所以需要註冊,有對應的mr。
id就是客戶端的id,context是使用者自定義,NULL應該可以,addr是本地目的地址,相當於說客戶端去伺服器取資料,存到這個addr中,已經註冊過了,是mr,length是讀操作的長度,flags是可選的標誌位,用來控制讀操作,remote_addr是要讀取的伺服器地址,已經註冊過,rkey是和remote_addr相關聯的key。
RDMA READ操作是客戶端來進行,伺服器無需操作,所以客戶端需要知道伺服器把資料放哪了,就是remote_addr,這個地址已經註冊過了,並且返回了key,客戶端還得知道這個遠端地址的key,就是rkey,拿到資料之後還得存到客戶端,存的目的地址就是addr,長度是length,這個目的地址已經註冊了,是mr。

傳送RDMA SEND請求,雙邊操作

int rdma_post_send (struct rdma_cm_id *id, void *context, void *addr, size_t length, struct ibv_mr *mr, int flags);
id是本地id,context使用者自定義,addr是傳送緩衝區地址,length是緩衝區長度,緩衝區註冊過了,是mr,flags標誌位,控制傳送請求。將addr的長度length的資料發到所連線的對方。

傳送RDMA RECEIVE請求,雙邊操作

int rdma_post_recv (struct rdma_cm_id *id, void *context, void *addr, size_t length, struct ibv_mr *mr);

id是本地id,context使用者自定義,addr是本地接收緩衝區地址,length是緩衝區長度,緩衝區註冊過了,是mr。

2、Infiniband VERBS API中(<infiniband/verbs.h>)的有關WRITE/READ和SEND/RECEIVE涉及到的函式如下所示

把WR傳送到QP的SQ中,根據send_wr中的opcode區分是SEND/WRITE/READ操作

int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr);

qp是從ibv_create_qp()返回的QP,wr是要傳送到QP的SQ中的wr連結串列,bad_wr:指向它的指標將填充第一個處理失敗的工作請求。返回0表示成功。struct ibv_send_wr結構體中有個成員sg_list,指定用於讀取或寫入的本地記憶體buffer,至於是讀取還是寫入,取決於操作碼opcode,對於SEND/WRITE操作,sg_list指定要讀取的記憶體buffer,對於READ操作,sg_list指定要寫入的記憶體buffer。

RECEIVE操作,recv_wr中並沒有操作碼opcode,因為只有一個接收操作

int ibv_post_recv(struct ibv_qp *qp, struct ibv_recv_wr *wr, struct ibv_recv_wr **bad_wr);

wr是傳送到QP的RQ中的wr,bad_wr指向它的指標將會用來填充第一個失敗的處理請求。返回0表示成功。

可見,RDMA_CM API中,把四種常見的操作分割開來,而在Infiniband VERBS API中,SEND/READ/WRITE都是通過ibv_post_send()實現,而RECEIVE是通過ibv_post_recv()實現。

注意,無論是send/recv還是read/write,直觀上看是資料直接傳送,但其實是這些操作會對應一個註冊過的記憶體,和RDMA直接互動,由RDMA直接讀取和寫入資料,避免應用記憶體和核心記憶體進行資料拷貝帶來的開銷。
比如send,傳送的資料所在記憶體是已經註冊過的,RNIC非同步排程輪到相應的WQE的時候,發現是SEND操作,就會直接和註冊過的記憶體互動,把資料傳送出去。
比如recv,接收的緩衝區是註冊過的,當RNIC非同步排程輪到對應的WQE的時候,發現是RECV操作,RDMA就會直接把資料存到相應的地址中。
比如write,傳送的資料所在的記憶體是已經註冊過的,RNIC直接和這塊記憶體互動,把資料寫到伺服器端的目的地址。
比如read,用於儲存資料的接收記憶體是已經註冊過的,RNIC直接通過網路互動,把伺服器的資料直接拿過來,存到接收記憶體。
相當於說,通訊過程需要操作的緩衝區都是註冊過的,意味著可以和RDMA裝置即RNIC進行直接資料互動,這樣就提高了通訊效率,降低延遲。send/recv和read/write操作中,都是RNIC直接和註冊過的記憶體進行資料互動,低延遲、高吞吐量、低CPU佔用率。