searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

rdma建连、断连代码流程分析

2023-08-29 06:49:10
1198
0
RDMA CM 是一个通信管理器 (CM),用于设置可靠、连接和不可靠的数据报数据传输。它提供了用于建立连接的 RDMA 传输中性接口。 API 概念基于套接字,但适用于基于队列对 (QP) 的语义。 QP 的通信必须通过特定的 RDMA 设备进行,并且数据传输是基于消息的。
RDMA CM 可以控制 RDMA API 的 QP 和通信管理(即连接建立或拆除)功能,或者仅控制通信管理。它与 libibverbs 库定义的动词 API 结合使用。 libibverbs 库提供了发送和接收数据所需的底层接口。
RDMA CM 可以异步或同步操作。操作模式通过在特定调用中使用 rdma_cm 事件通道参数来控制。如果提供了事件通道,则 rdma_cm 标识符会在该通道上报告其事件数据(例如,建立连接的结果)。如果未提供通道,则所选 rdma_cm 标识符的所有 rdma_cm 操作都将被阻止,直到通道完成。
 
有两种方式在两个节点间建链:
1. 通过socket的方式。在应用程序中通过调用ibv_modify_qp()显式更改QP状态。
2. 使用librdmacm(iWARP只能使用这种方式)
 
socket建链
 
TCP/IP
展开34号报文,可以看到它是Client端(192.168.217.130)发给Server端(192.168.217.128)的IPv4报文:
 
Client发给Server的建链信息
其TCP层Payload的内容是:
0000:000011:46d9ab:fe800000000000004b92470ab6f183
其被冒号分成了四个部分,正好对应上面执行结果截图中的LID、QPN、PSN和GID。所以这个报文的目的是Client端把自己建链的信息发给了Server端。
而Server端在回复了ACK之后,于36号报文发出了自己的信息:
 
Server发给Client的建链信息
 
Client端在38号回复了”done”。
 
Client端回复的完成信息
 
之后的39-42号报文,双方就断开了TCP连接,说明这个程序在交换完上述信息后没有再用Socket API交换信息,执行的都是RDMA业务了。
 
使用socket的建链过程如下图所示:
 
 
cm建链
Communication Manager
 
此大多数RoCE网卡都可以支持Socket和CM两种建链方式。
所有的GSI QP都必须使用相同的QPN,即QP1;以及相同的Queue Key——特殊的值0x8001_0000。而最高位为1的Queue Key都是特权Key,普通的用户无法使用,这也就保证了CM只能由内核态协议栈来管理。
一个基于RoCE v2的CM报文为例,它的结构如下图所示:
 
RC QP建链
RC QP的CM建链和断链流程和TCP非常相似,如下图所示,建链是一个“三次握手”的流程:
 
RC QP间的CM建链流程
 
RC QP的CM建链报文交互(参考自IB Specification Release 1.4 Figure 132)
1. 首先Client端要发起一个REQ(Request)消息,表示一个连接请求,消息的报文中携带有连接参数,比如本端要连接的QP的QPN、起始PSN、重传次数上限等等。另外也可以跟iWARP的MPA建链一样携带一些自定义的Private Data,用于满足用户的定制化需求。
2. Server端在Client端发起连接请求前就一直处于监听状态,当它监听到连接请求后,CM层会对连接参数进行校验和记录,而Private Data会被上送给用户进行校验。校验通过后,Server端会发送REP(Reply)消息,表示接受之前的连接请求,并且在消息报文中携带自己的连接参数——包括QPN、起始PSN等等。REP报文也可以携带自定义的Private Data。
3. Client端收到REP消息后,将对其中的内容进行校验和记录,然后发送一个RTU(Ready To Use)消息,表示同意你的参数,可以利用双方约定好的QP进行数据交互了。
如上图所示,Client端在发出RTU消息之后就可以发送数据了,而Server端在接收到RTU消息后才可以发送数据。
上述过程中,如果任意一个校验过程中检出了错误,则会回复给对端一个REJ(Reject)消息,终止本次连接流程,消息报文中会携带拒绝连接的原因。
RC QP断链
RC QP的断链一般是一个“四次挥手“的过程。关于断链的过程,IB规范并没有规定两端谁先发起、后发的一方是否要先回复Reply后再发送Request等细节,下图中的例子只是一种可能的情况:
 
RC QP间的CM断链流程
1. 首先Client端发送DREQ(Disconnect Request)消息,表示自己想要断开某个连接,消息中携带着指定连接的ID以及QPN等信息。这个过程也可以携带Private Data,用于定制化功能。
2. Server端收到DREQ消息并校验通过后,回复一个DREP(Disconnect Reply)消息,表示同意断链请求。
3. Server端发送DREQ消息,同1。
4. Client端收到DREQ消息,校验通过后回复DREP,同2。
上图中Client端和Server端收到对端回复的DREP消息后,就可以销毁跟对端有关的CM软硬件资源了。
 
UD QP建链
UD QP的CM建链流程相对于RC要简单,主要原因自然是UD服务类型不用建立连接,也不需要保证可靠性,对端收不收的到无所谓。由上层应用的逻辑保证建链的完成,比如Client端可以在一定时间内没收到Reply就重发Request。这个过程下图所示:
 
UD QP间的CM建链流程
首先需要解释一个名词——SIDR,全称为Service ID Resolution Protocol,它的过程非常简单:
1.Client端发送请求消息SIDR_REQ(SIDR Request),其中会携带一个Service ID字段,表明它需要Server端提供的服务。这个过程中也可以携带Private Data用于定制化实现。
2.Server端收到SIDR_REQ消息后,会对请求进行校验。校验通过后,在回复消息SIDR_REP(SIDR Reply)中会携带一个和Service ID对应的可用QP的QPN以及Queue Key,此后Client就可以以它为目的QP发送数据给Server。这个过程中也可以携带Private Data。
Client端收到Server发出的回复后,就可以从报文中获取对端可以用来接收消息的QPN和Q_Key了,至此SIDR的标准交互流程就结束了。至于Service ID和QPN的对应关系,IB规范中并没有做出规定,这个可以由用户应用程序自行决定。这个流程中Server端好像不知道Client端的QPN。这是因为作为被动一方的Server端并不一定要知道Client端的QPN。如果Server端也有向Client端发送消息的需求的话,才需要通过其他方式获得Client端的信息。具体的实现由用户决定,比如Client端可以在首个Send报文的Payload部分就携带本端的QPN、Q_Key等信息;也可以通过在Send with immediate消息中携带立即数,这个立即数即为Client端的QPN或者Q_Key。
 
整个CM建链过程中出现了两个QP:一个是用于处理CM和MAD报文的GSI(UD)类型的QP,一个是建链之后两端具体的一对相互之间可以发送Send消息的QP。用户无法使用GSI QP来传递业务消息,只能使用后者。另外对于RC QP来说,此时两端并未掌握对端的Virtual Address和Remote Key。所以如果要进行RDMA Write/Read操作,还需要通过这一对QP间Send消息来传递VA和R_Key。这个过程也是由用户自己决定如何实现的。
CM建链流程如下图所示:
 
 
建链过程
断开链接过程:
 
地址数据结构
rdma地址  rdma_cm_get_rdma_address  rdma_resolve_addr
struct rdma_route route;
struct rdma_addrinfo **rai
struct rdma_addrinfo {
intai_flags;
intai_family;
intai_qp_type;
intai_port_space;
socklen_tai_src_len;
socklen_tai_dst_len;
struct sockaddr*ai_src_addr;
struct sockaddr*ai_dst_addr;
char*ai_src_canonname;
char*ai_dst_canonname;
size_tai_route_len;
void*ai_route;
size_tai_connect_len;
void*ai_connect;
struct rdma_addrinfo*ai_next;
}
struct rdma_addr {
union {
struct sockaddrsrc_addr;
struct sockaddr_insrc_sin;
struct sockaddr_in6src_sin6;
struct sockaddr_storage src_storage;
};
union {
struct sockaddrdst_addr;
struct sockaddr_indst_sin;
struct sockaddr_in6dst_sin6;
struct sockaddr_storage dst_storage;
};
union {
struct rdma_ib_addribaddr;
} addr;
};
struct rdma_ib_addr {
union ibv_gidsgid;
union ibv_giddgid;
__be16pkey;
};
union ibv_gid {
uint8_traw[16];
struct {
__be64subnet_prefix;
__be64interface_id;
} global;
};
struct rdma_route {
struct rdma_addr addr;
struct ibv_sa_path_rec*path_rec;
int num_paths;
};
struct ibv_sa_path_rec {
/* reserved */
/* reserved */
union ibv_gid dgid;
union ibv_gid sgid;
__be16        dlid;
__be16        slid;
int           raw_traffic;
/* reserved */
__be32        flow_label;
uint8_t       hop_limit;
uint8_t       traffic_class;
int           reversible;
uint8_t       numb_path;
__be16        pkey;
/* reserved */
uint8_t       sl;
uint8_t       mtu_selector;
uint8_t      mtu;
uint8_t       rate_selector;
uint8_t       rate;
uint8_t       packet_life_time_selector;
uint8_t       packet_life_time;
uint8_t       preference;
}
 
基本通用地址:struct sockaddr *addr
struct sockaddr
 
 {
unsigned short sa_family;     /* address family, AF_xxx */
char sa_data[14];                 /* 14 bytes of protocol address */
};
sa_family是地址家族,一般都是 “AF_xxx”的形式。好像通常大多用的是都是 AF_INET。
sa_data是14字节协议地址。 
此数据结构用做 bind、connect 、recvfrom、 sendto等函数的参数,指明地址信息。
 
但一般编程中并不直接针对此数据结构操作,而是使用另一个与   sockaddr 等价的数据结构
sockaddr_in (在   netinet/in.h 中定义):
struct sockaddr_in {
short int sin_family;                      /* Address family */
unsigned short int sin_port;       /* Port number */
struct in_addr sin_addr;              /* Internet address */
unsigned char sin_zero[8];         /* Same size as struct sockaddr */
};
struct in_addr {
unsigned long s_addr;
};
typedef struct in_addr {
union {
            struct{
                        unsigned char s_b1,
                        s_b2,
                        s_b3,
                        s_b4;
                        } S_un_b;
           struct {
                        unsigned short s_w1,
                        s_w2;
                        } S_un_w;
            unsigned long S_addr;
          } S_un;
} IN_ADDR;
sin_family 指代协议族,在   socket 编程中只能是 AF_INET
sin_port 存储端口号(使用网络字节顺序)  
sin_addr 存储   IP 地址,使用 in_addr   这个数据结构  
sin_zero 是为了让   sockaddr 与 sockaddr_in   两个数据结构保持大小相同而保留的空字节。  
s_addr 按照网络字节顺序存储   IP 地址
sockaddr_in和 sockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向 
sockadd的结构体,并代替它。也就是说,你可以使用 sockaddr_in建立你所需要的信息,
在最后用进行类型转换就可以了 bzero((char*)&mysock,sizeof(mysock));//初始化
mysock结构体名 
mysock.sa_family=AF_INET;
mysock.sin_addr.s_addr=inet_addr("192.168.0.1");
客户端建链
 
参考
zhuanlan.zhihu.com/p/476407641
zhuanlan.zhihu.com/p/494826608
linux内核源代码
rdma_core源代码
perftest源代码
0条评论
0 / 1000
吴****云
3文章数
0粉丝数
吴****云
3 文章 | 0 粉丝
吴****云
3文章数
0粉丝数
吴****云
3 文章 | 0 粉丝
原创

rdma建连、断连代码流程分析

2023-08-29 06:49:10
1198
0
RDMA CM 是一个通信管理器 (CM),用于设置可靠、连接和不可靠的数据报数据传输。它提供了用于建立连接的 RDMA 传输中性接口。 API 概念基于套接字,但适用于基于队列对 (QP) 的语义。 QP 的通信必须通过特定的 RDMA 设备进行,并且数据传输是基于消息的。
RDMA CM 可以控制 RDMA API 的 QP 和通信管理(即连接建立或拆除)功能,或者仅控制通信管理。它与 libibverbs 库定义的动词 API 结合使用。 libibverbs 库提供了发送和接收数据所需的底层接口。
RDMA CM 可以异步或同步操作。操作模式通过在特定调用中使用 rdma_cm 事件通道参数来控制。如果提供了事件通道,则 rdma_cm 标识符会在该通道上报告其事件数据(例如,建立连接的结果)。如果未提供通道,则所选 rdma_cm 标识符的所有 rdma_cm 操作都将被阻止,直到通道完成。
 
有两种方式在两个节点间建链:
1. 通过socket的方式。在应用程序中通过调用ibv_modify_qp()显式更改QP状态。
2. 使用librdmacm(iWARP只能使用这种方式)
 
socket建链
 
TCP/IP
展开34号报文,可以看到它是Client端(192.168.217.130)发给Server端(192.168.217.128)的IPv4报文:
 
Client发给Server的建链信息
其TCP层Payload的内容是:
0000:000011:46d9ab:fe800000000000004b92470ab6f183
其被冒号分成了四个部分,正好对应上面执行结果截图中的LID、QPN、PSN和GID。所以这个报文的目的是Client端把自己建链的信息发给了Server端。
而Server端在回复了ACK之后,于36号报文发出了自己的信息:
 
Server发给Client的建链信息
 
Client端在38号回复了”done”。
 
Client端回复的完成信息
 
之后的39-42号报文,双方就断开了TCP连接,说明这个程序在交换完上述信息后没有再用Socket API交换信息,执行的都是RDMA业务了。
 
使用socket的建链过程如下图所示:
 
 
cm建链
Communication Manager
 
此大多数RoCE网卡都可以支持Socket和CM两种建链方式。
所有的GSI QP都必须使用相同的QPN,即QP1;以及相同的Queue Key——特殊的值0x8001_0000。而最高位为1的Queue Key都是特权Key,普通的用户无法使用,这也就保证了CM只能由内核态协议栈来管理。
一个基于RoCE v2的CM报文为例,它的结构如下图所示:
 
RC QP建链
RC QP的CM建链和断链流程和TCP非常相似,如下图所示,建链是一个“三次握手”的流程:
 
RC QP间的CM建链流程
 
RC QP的CM建链报文交互(参考自IB Specification Release 1.4 Figure 132)
1. 首先Client端要发起一个REQ(Request)消息,表示一个连接请求,消息的报文中携带有连接参数,比如本端要连接的QP的QPN、起始PSN、重传次数上限等等。另外也可以跟iWARP的MPA建链一样携带一些自定义的Private Data,用于满足用户的定制化需求。
2. Server端在Client端发起连接请求前就一直处于监听状态,当它监听到连接请求后,CM层会对连接参数进行校验和记录,而Private Data会被上送给用户进行校验。校验通过后,Server端会发送REP(Reply)消息,表示接受之前的连接请求,并且在消息报文中携带自己的连接参数——包括QPN、起始PSN等等。REP报文也可以携带自定义的Private Data。
3. Client端收到REP消息后,将对其中的内容进行校验和记录,然后发送一个RTU(Ready To Use)消息,表示同意你的参数,可以利用双方约定好的QP进行数据交互了。
如上图所示,Client端在发出RTU消息之后就可以发送数据了,而Server端在接收到RTU消息后才可以发送数据。
上述过程中,如果任意一个校验过程中检出了错误,则会回复给对端一个REJ(Reject)消息,终止本次连接流程,消息报文中会携带拒绝连接的原因。
RC QP断链
RC QP的断链一般是一个“四次挥手“的过程。关于断链的过程,IB规范并没有规定两端谁先发起、后发的一方是否要先回复Reply后再发送Request等细节,下图中的例子只是一种可能的情况:
 
RC QP间的CM断链流程
1. 首先Client端发送DREQ(Disconnect Request)消息,表示自己想要断开某个连接,消息中携带着指定连接的ID以及QPN等信息。这个过程也可以携带Private Data,用于定制化功能。
2. Server端收到DREQ消息并校验通过后,回复一个DREP(Disconnect Reply)消息,表示同意断链请求。
3. Server端发送DREQ消息,同1。
4. Client端收到DREQ消息,校验通过后回复DREP,同2。
上图中Client端和Server端收到对端回复的DREP消息后,就可以销毁跟对端有关的CM软硬件资源了。
 
UD QP建链
UD QP的CM建链流程相对于RC要简单,主要原因自然是UD服务类型不用建立连接,也不需要保证可靠性,对端收不收的到无所谓。由上层应用的逻辑保证建链的完成,比如Client端可以在一定时间内没收到Reply就重发Request。这个过程下图所示:
 
UD QP间的CM建链流程
首先需要解释一个名词——SIDR,全称为Service ID Resolution Protocol,它的过程非常简单:
1.Client端发送请求消息SIDR_REQ(SIDR Request),其中会携带一个Service ID字段,表明它需要Server端提供的服务。这个过程中也可以携带Private Data用于定制化实现。
2.Server端收到SIDR_REQ消息后,会对请求进行校验。校验通过后,在回复消息SIDR_REP(SIDR Reply)中会携带一个和Service ID对应的可用QP的QPN以及Queue Key,此后Client就可以以它为目的QP发送数据给Server。这个过程中也可以携带Private Data。
Client端收到Server发出的回复后,就可以从报文中获取对端可以用来接收消息的QPN和Q_Key了,至此SIDR的标准交互流程就结束了。至于Service ID和QPN的对应关系,IB规范中并没有做出规定,这个可以由用户应用程序自行决定。这个流程中Server端好像不知道Client端的QPN。这是因为作为被动一方的Server端并不一定要知道Client端的QPN。如果Server端也有向Client端发送消息的需求的话,才需要通过其他方式获得Client端的信息。具体的实现由用户决定,比如Client端可以在首个Send报文的Payload部分就携带本端的QPN、Q_Key等信息;也可以通过在Send with immediate消息中携带立即数,这个立即数即为Client端的QPN或者Q_Key。
 
整个CM建链过程中出现了两个QP:一个是用于处理CM和MAD报文的GSI(UD)类型的QP,一个是建链之后两端具体的一对相互之间可以发送Send消息的QP。用户无法使用GSI QP来传递业务消息,只能使用后者。另外对于RC QP来说,此时两端并未掌握对端的Virtual Address和Remote Key。所以如果要进行RDMA Write/Read操作,还需要通过这一对QP间Send消息来传递VA和R_Key。这个过程也是由用户自己决定如何实现的。
CM建链流程如下图所示:
 
 
建链过程
断开链接过程:
 
地址数据结构
rdma地址  rdma_cm_get_rdma_address  rdma_resolve_addr
struct rdma_route route;
struct rdma_addrinfo **rai
struct rdma_addrinfo {
intai_flags;
intai_family;
intai_qp_type;
intai_port_space;
socklen_tai_src_len;
socklen_tai_dst_len;
struct sockaddr*ai_src_addr;
struct sockaddr*ai_dst_addr;
char*ai_src_canonname;
char*ai_dst_canonname;
size_tai_route_len;
void*ai_route;
size_tai_connect_len;
void*ai_connect;
struct rdma_addrinfo*ai_next;
}
struct rdma_addr {
union {
struct sockaddrsrc_addr;
struct sockaddr_insrc_sin;
struct sockaddr_in6src_sin6;
struct sockaddr_storage src_storage;
};
union {
struct sockaddrdst_addr;
struct sockaddr_indst_sin;
struct sockaddr_in6dst_sin6;
struct sockaddr_storage dst_storage;
};
union {
struct rdma_ib_addribaddr;
} addr;
};
struct rdma_ib_addr {
union ibv_gidsgid;
union ibv_giddgid;
__be16pkey;
};
union ibv_gid {
uint8_traw[16];
struct {
__be64subnet_prefix;
__be64interface_id;
} global;
};
struct rdma_route {
struct rdma_addr addr;
struct ibv_sa_path_rec*path_rec;
int num_paths;
};
struct ibv_sa_path_rec {
/* reserved */
/* reserved */
union ibv_gid dgid;
union ibv_gid sgid;
__be16        dlid;
__be16        slid;
int           raw_traffic;
/* reserved */
__be32        flow_label;
uint8_t       hop_limit;
uint8_t       traffic_class;
int           reversible;
uint8_t       numb_path;
__be16        pkey;
/* reserved */
uint8_t       sl;
uint8_t       mtu_selector;
uint8_t      mtu;
uint8_t       rate_selector;
uint8_t       rate;
uint8_t       packet_life_time_selector;
uint8_t       packet_life_time;
uint8_t       preference;
}
 
基本通用地址:struct sockaddr *addr
struct sockaddr
 
 {
unsigned short sa_family;     /* address family, AF_xxx */
char sa_data[14];                 /* 14 bytes of protocol address */
};
sa_family是地址家族,一般都是 “AF_xxx”的形式。好像通常大多用的是都是 AF_INET。
sa_data是14字节协议地址。 
此数据结构用做 bind、connect 、recvfrom、 sendto等函数的参数,指明地址信息。
 
但一般编程中并不直接针对此数据结构操作,而是使用另一个与   sockaddr 等价的数据结构
sockaddr_in (在   netinet/in.h 中定义):
struct sockaddr_in {
short int sin_family;                      /* Address family */
unsigned short int sin_port;       /* Port number */
struct in_addr sin_addr;              /* Internet address */
unsigned char sin_zero[8];         /* Same size as struct sockaddr */
};
struct in_addr {
unsigned long s_addr;
};
typedef struct in_addr {
union {
            struct{
                        unsigned char s_b1,
                        s_b2,
                        s_b3,
                        s_b4;
                        } S_un_b;
           struct {
                        unsigned short s_w1,
                        s_w2;
                        } S_un_w;
            unsigned long S_addr;
          } S_un;
} IN_ADDR;
sin_family 指代协议族,在   socket 编程中只能是 AF_INET
sin_port 存储端口号(使用网络字节顺序)  
sin_addr 存储   IP 地址,使用 in_addr   这个数据结构  
sin_zero 是为了让   sockaddr 与 sockaddr_in   两个数据结构保持大小相同而保留的空字节。  
s_addr 按照网络字节顺序存储   IP 地址
sockaddr_in和 sockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向 
sockadd的结构体,并代替它。也就是说,你可以使用 sockaddr_in建立你所需要的信息,
在最后用进行类型转换就可以了 bzero((char*)&mysock,sizeof(mysock));//初始化
mysock结构体名 
mysock.sa_family=AF_INET;
mysock.sin_addr.s_addr=inet_addr("192.168.0.1");
客户端建链
 
参考
zhuanlan.zhihu.com/p/476407641
zhuanlan.zhihu.com/p/494826608
linux内核源代码
rdma_core源代码
perftest源代码
文章来自个人专栏
DPU
2 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0