# RDMA 技术及其编程方法(二):编程指导
# 一、libibverbs 简介
- libibverbs 由 Roland Dreier 自 2006 年开始开发和维护,实际上是*nix 中的 Verbs API 标准
- 开源
- Verbs 的核心部分自 2005 年起集成到 Linux 内核中–内核 2.6.11
- Inbox in several *nix distributions
- 目前有多个硬件供应商提供的级别较低的库
- 对所有启用 RDMA 的传输协议使用相同的 API
- InfiniBand: 支持 RDMA 的网络体系结构
- 需要支持它的网卡和 InfiniBand 交换机。
- RoCE:基于以太网/IP 帧的 RDMA 数据包封装
- 需要支持它的网卡和标准以太网交换机
- iWARP:提供基于流控制传输协议(SCTP)和传输控制协议(TCP)的 RDMA
- 需要支持它的网卡和标准以太网交换机
- InfiniBand: 支持 RDMA 的网络体系结构
- libibverbs 是完全线程安全的
- libibverbs 本身是线程安全的
- 用户态空间低级驱动程序库也是线程安全的
- 应用程序可以在多线程中使用 RDMA 资源
危险
销毁一个线程中的资源并在另一个线程中使用它将导致 segmentation fault,这个问题在非多线程代码中也会发生
- 使用 libibverbs 的基本须知
tips 说明 头文件引入 #include<infiniband/verbs.h> 编译时链接 -libverbs 所有的 input structures 需要为 zeroed 使用 memset()或结构初始化、如果该 structure 有扩充的需求,则零值将保留遗留行为 大多数资源句柄都是指针,因此使用错误的句柄可能会导致分段错误 使用 NULL 检查句柄 返回指针的 Verbs 成功时返回有效值,失败时返回 NULL 检查返回值 返回整形变量的 Verbs 如果成功则返回零,如果成功则返回-1 或 errno 检查返回值
# 二、Verbs API 详解
# 1. 简介
- 在内核和用户态空间均可使用
- Verbs 中的类
- 资源管理:Qps、CQs、SRQs 等等
- WR 处理:post send, 轮询 CQ 等等
- 内存注册
- 地址句柄
- Verbs 中的操作
- Device 操作
- 上下文操作
- PD 操作
- QP bringup
- 活跃 QP 操作
# 2. Verbs 对象创建层次
- 获取 devide 列表
- 打开请求的 device
- 查询 device 功能
- 分配 PD 内存空间
- 注册内存域 MR
- 关联并创建完成队列 CQ
- 创建 QP
- Bring up a QP
- Post WR 并且轮询 CQ
- 清理资源
# 3. 两个动态库
- libibverbs.so
- 用于直接通过用户态空间访问 InfiniBand 硬件的库
- Infiniband(根据 Infiniband 规范)和 iWarp(iWARP 动词规范)的 RDMA Verbs 的实现
- 它处理创建、修改、查询和销毁资源的控制路径,如保护域(PD)、完成队列(CQ)、队列对(QP)、共享接收队列(SRQ)、地址句柄(AH)、内存区域(MR)
- 它还处理发送和接收发布到 QPS 和 SRQ 的数据,使用轮询和完成事件从 CQs 获取完成
- librdmacm.so
- 用户态空间的 RDMA 连接管理器
- 使用 Socket 语义的 RDMA(InfiniBand、ROCE 和 iWARP)通信管理库
# 三、Connection Manager
# 1. 连接的建立
基于 Infiniband 通信管理(CM)协议(在通用服务接口(GSI)上定义的协议 QP:QP1)
提供以下服务
- 在对等 RC 和 QP 之间交换必要的参数,使它们为通信做好准备
- 初始化器请求连接到远程上的服务 ID(服务 ID 映射)
- 类 TCP 握手:请求/响应/即用消息
- 查找给定服务 ID 的远程 UD 和 QP 序号
- 服务 ID 请求/响应消息
- 加载备用路径
- 在对等 RC 和 QP 之间交换必要的参数,使它们为通信做好准备
连接管理器(Connection Manager,CM)是一个用户态空间的库,它提供了一个通用的接口,用于在 RDMA 网络中建立连接。它可以用于建立连接,也可以用于查找远程 QP 的地址,以便在不建立连接的情况下发送数据。
- 需要在对等 QP 之间交换信息
- 负责 RC、UC、RD 连接的建立
- 应用程序使用 SA 来获取其他信息(例如路径记录)
- SIDR 用于 UD
# 2. CM 的抽象类型(RDMACM)
- 类似于 Socket 连接模式的语义
- 对 IB 和 ROCE 都使用基于 IP 的寻址模式
类 | 说明 |
---|---|
rdma_create/destroy_id | creates/destroys a connection identifier (equivalent to a socket) |
rdma_create/destroy_qp | allocate/destroy a qp for communication |
rdma_bind_addr | set local port to listen on |
rdma_resolve_addr | obtain local RDMA device to reach remote address |
rdma_resolve_route | determine route to remote address |
rdma_get_src_port | query local port |
rdma_get_local_addr | query local ip |
rdma_get_peer_addr | query remote ip |
rdma_connect/disconnect | connect/disconnect rc qps, or resolve service id to qp for ud qps |
rdma_listen | listen for incoming connections |
rdma_accept/reject | accept/reject incoming connection requests |
rdma_create/destroy_event_channel | allocate/destroy an event channel |
rdma_get_cm_event | get next event |
rdma_ack_cm_event | acknowledge event(s) to rdmacm |
rdma_join/leave_multicast | join/leave multicast addresses |
- 使用 rdmacm 的基本须知
tips 说明 头文件引入 #include<rdma/rdma_cma.h> 编译时链接 -lrdmacm
# 四、 RDMACM 程序解析——被动方
流程如下:
- 创建事件 channel,以便我们可以接收 rdmacm 事件,如连接请求和连接建立通知。
- 创建连接 ID 并绑定到地址。
- 创建 Listener 并返回端口/地址。
- 等待连接请求
- 创建 PD、CQ 和 Send-Receive QP
- 接受连接请求
- 等待建立连接
- 视情况发布操作
# 1. 创建事件 channel
- 打开用于报告通信事件的 channel。异步事件将通过事件 channel 报告给用户,对应方法为
struct rdma_event_channel * rdma_create_event_channel(void)
。 - 事件 channel 用于定向 rdma_cm_id 上的所有事件。对于许多客户端来说,单个事件 channel 可能就足够了,然而,当管理大量的连接或 cm_id 时,用户可能会发现将不同 cm_id 的事件定向到不同的 channel 进行处理是有用的。
- 必须通过调用
rdma_destroy_event_channel
销毁所有创建的事件 channel。用户应该调用rdma_get_cm_event
来检索事件 channel 上的事件。
struct rdma_event_channel *channel = rdma_create_event_channel();
if (!channel) {
perror("rdma_create_event_channel");
return -1;
}
struct rdma_cm_event* event;
// 此处会阻塞,直到有事件发生
int err = rdma_get_cm_event(channel, &event);
if (err) {
perror("rdma_get_cm_event");
return err;
}
// 中间处理代码...
rdma_destroy_event_channel(channel);
- 每个事件 channel 都映射到一个文件描述符。可以像使用和操作任何其他 FD 一样使用和操作关联的文件描述符,以更改其行为。
# 2. 创建连接 ID
- 创建用于跟踪通信信息的标识符,对应方法为
int rdma_create_id(struct rdma_event_channel *channel, struct rdma_cm_id **id, void *context, enum rdma_port_space ps)
。 - 输入参数:
- channel:事件 channel
- id:指向 rdma_cm_id 指针的指针,用于返回新创建的 rdma_cm_id
- context:用户上下文,将在事件中返回给用户
- ps:RDMA 端口空间,指定要使用的端口空间
- rdma_cm_id 在概念上等同于用于 RDMA 通信的套接字。不同之处在于,RDMA 通信需要显式绑定到指定的 RDMA 设备,然后才能进行通信,并且大多数操作本质上是异步的。
- 端口空间
- RDMA_PS_TCP:提供可靠、面向连接的 QP 通信。与 TCP 不同,RDMA 端口空间提供基于消息的通信,而不是基于流的通信。
- RDMA_PS_UDP:提供不可靠、无连接的 QP 通信。支持数据报和组播通信。
- 销毁:在调用此函数并确认相关事件之前,用户必须释放任何与 rdma_cm_id 相关的 QP。
struct rdma_cm_id *listen_id;
int err = rdma_create_id(channel, &listen_id, NULL, RDMA_PS_TCP);
if (err) {
perror("rdma_create_id");
return err;
}
// 中间处理代码...
rdma_destroy_id(listen_id);
# 3. 绑定地址
- 将源地址与 rdma_cm_id 相关联。对应方法为
int rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr)
。- 地址中可以包含通配符。
- 如果绑定到特定本地地址,则 rdma_cm_id 也将绑定到本地 RDMA 设备。
- 通常,在调用
rdma_listen
以绑定到特定端口号之前调用此函数,但也可以在调用rdma_resolve_addr
以绑定到特定地址之前在主动方调用该函数。 - 如果用于绑定到端口 0,rdma_cm 将选择一个可用端口,可以使用
rdma_get_src_port
检索该端口。
/* sockaddr_in 是 IPV4 的地址结构体
* AF_INET:IPV4
* htons:将主机字节序转换为网络字节序(小端存储), 20079 是端口号
* INADDR_ANY:表示任意地址
*/
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(20079),
.sin_addr = { .s_addr = INADDR_ANY },
};
err = rdma_bind_addr(listen_id, (struct sockaddr *)&addr);
if (err) {
perror("rdma_bind_addr");
return err;
}
# 4. 创建 Listener,返回端口/地址
- 初始化传入连接请求或数据报服务查找的 Listener。对应方法为
int rdma_listen(struct rdma_cm_id *id, int backlog)
。- 侦听将被限制为本地绑定源地址
- 在调用此函数之前,用户必须已通过调用
rdma_bind_addr
将rdma_cm_id
绑定到本地地址。 - 如果
rdma_cm_id
绑定到特定的 IP 地址,则侦听将仅限于该地址和关联的 RDMA 设备。 - 如果
rdma_cm_id
仅绑定到 RDMA 端口号,则将在所有 RDMA 设备上进行侦听。
- 返回已绑定到本地地址的
rdma_cm_id
的本地端口号。对应方法为uint16_t rdma_get_src_port(struct rdma_cm_id *id)
。 - 返回已绑定到本地设备的
rdma_cm_id
的本地 IP 地址。对应方法为struct sockaddr * rdma_get_local_addr(struct rdma_cm_id *id)
。 - 解析目的节点和服务地址,并返回建立通信所需的信息。提供与 getaddrinfo 等效的 RDMA 功能(配合
rdma_create_ep
使用)。对应方法为int rdma_getaddrinfo (char *node, char *service, struct rdma_addrinfo *hints, struct rdma_addrinfo **res)
。- node: 可选,目的节点的主机名,或者点分十进制的 IPv4/IPv6 十六进制地址
- service:地址的服务名称或端口号。
- hints:一个包含有关调用方支持的服务类型的提示的 rdma_addrinfo 结构的引用。
- res:指向包含响应信息的 rdma_addrinfo 结构的 LinkedList 的指针。
// 等待连接请求的最大数量
int backlog = 10;
err = rdma_listen(listen_id, backlog);
if (err) {
perror("rdma_listen");
return err;
}
uint16_t port = rdma_get_src_port(listen_id);
port = ntohs(port);
printf("listening on port %u.\n", port);
struct sockaddr *local_addr = rdma_get_local_addr(listen_id);
if (local_addr->sa_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)local_addr;
char ip[INET_ADDRSTRLEN];
// 需要加上头文件 #include <arpa/inet.h>
inet_ntop(AF_INET, &(sin->sin_addr), ip, INET_ADDRSTRLEN);
printf("Local IP address is: %s\n", ip);
printf("Local port is: %d\n", ntohs(sin->sin_port));
}
struct rdma_addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = RAI_PASSIVE;
hints.ai_port_space = RDMA_PS_TCP;
struct rdma_addrinfo *res;
err = rdma_getaddrinfo(NULL, "20079", &hints, &res);
if (err) {
perror("rdma_getaddrinfo");
return err;
}
// do something with struct rdma_addrinfo *cur = res...
# 5. 等待连接请求
- 检索通信事件。如果没有挂起的事件,默认情况下,调用将阻塞,直到接收到事件。对应方法为
int rdma_get_cm_event(struct rdma_event_channel *channel, struct rdma_cm_event **event)
。 - 通过修改与给定通道相关联的文件描述符,可以更改此函数的默认同步行为。
- 所有报告的事件都必须通过调用
rdma_ack_cm_event
进行确认。 rdma_cm_id
的销毁将被阻塞,直到相关事件被确认。
struct rdma_cm_event* event;
// 此处会阻塞,直到有事件发生
err = rdma_get_cm_event(channel, &event);
# 6. 创建 PD、CQ 和 Send-Receive QP
- 分配与指定的
rdma_cm_id
相关联的 QP,并将其转换为用于发送和接收。对应方法为int rdma_create_qp(struct rdma_cm_id *id, struct ibv_pd *pd, struct ibv_qp_init_attr *qp_init_attr)
。 - 在调用此函数之前,
rdma_cm_id
必须绑定到本地 RDMA 设备,且保护域 PD 必须用于同一设备。 - 被分配给
rdma_cm_id
的 QP 会由 librdmacm 自动转换状态. - 分配完毕后,QP 将准备就绪,处理接收信息的发布。如果 QP 未连接,它将准备好发布发送。
pd = ibv_alloc_pd(cm_client_id->verbs);
if (!pd) {
perror("Failed to allocate a protection domain");
return -1;
}
io_completion_channel = ibv_create_comp_channel(cm_client_id->verbs);
if (!io_completion_channel) {
perror("Failed to create an I/O completion event channel");
return -1;
}
cq = ibv_create_cq(cm_client_id->verbs, 10, NULL, io_completion_channel, 0);
if (!cq) {
perror("Failed to create a completion queue");
return -1;
}
ret = ibv_req_notify_cq(cq, 0);
if (ret) {
perror("Failed to request notifications");
return -1;
}
bzero(&qp_init_attr, sizeof(qp_init_attr));
qp_init_attr.qp_type = IBV_QPT_RC;
qp_init_attr.cap.max_send_wr = 10;
qp_init_attr.cap.max_recv_wr = 10;
qp_init_attr.cap.max_send_sge = 1;
qp_init_attr.cap.max_recv_sge = 1;
qp_init_attr.send_cq = cq;
qp_init_attr.recv_cq = cq;
ret = rdma_create_qp(cm_client_id, pd, &qp_init_attr);
if (ret) {
perror("Failed to create QP");
return -1;
}
client_qp = cm_client_id->qp;
# 7. 最后的操作
- Accept 请求连接
- 等待连接建立
- 发布操作
# 五、 RDMACM 程序解析——主动方
# 1. 创建事件 channel
- 与被动方相同
# 2. 创建连接 ID
- 与被动方相同
# 3. 绑定地址
- 将目的地址和可选源地址从 IP 地址解析为 RDMA 地址。如果成功,则指定的 rdma_cm_id 将绑定到本地设备。对应方法为
int rdma_resolve_addr (struct rdma_cm_id *id, struct sockaddr *src_addr, struct sockaddr *dst_addr, int timeout_ms)
。 - 此方法用于将给定的目标 IP 地址映射到可用的 RDMA 地址。
- IP 到 RDMA 地址的映射使用本地路由表或通过 ARP 完成。
- 如果给定源地址,则将
rdma_cm_id
绑定到该地址,就像调用rdma_ind_addr
一样。 - 如果没有给出源地址,并且
rdma_cm_id
尚未绑定到设备,则rdma_cm_id
将根据本地路由表绑定到源地址。 - 在此方法调用之后,
rdma_cm_id
将绑定到 RDMA 设备。 - 该方法调用通常在调用
rdma_resolve_route
和rdma_connect
之前在主动方上进行。
注释
InfiniBand 特定
- 此方法还会将目标 IP 地址和源 IP 地址(如果给定)映射到 GID。
- 为了执行映射,IPoIB 必须同时在本地和远程节点上运行。
# 4. 创建 QP
- 与被动方相同
# 5. 解析路由
- 解析指向目标地址的 RDMA 路由,以建立连接。目标地址必须已通过调用 rdma_resolve_addr 解析。对应方法为
int rdma_resolve_route (struct rdma_cm_id *id, int timeout_ms);
# 6. 建立连接
对应方法为
int rdma_connect (struct rdma_cm_id *id, struct rdma_conn_param *conn_param);
- id:指向 rdma_cm_id 的指针
- conn_param:指向 rdma_conn_param 结构的指针,包含连接参数
对于
RDMA_PS_TCP
类型的rdma_cm_id
,该调用会向远程目的地发起连接请求对于
RDMA_PS_UDP
类型的rdma_cm_id
,它会启动对提供数据报服务的远程 QP 的查询
# 7. 最后的操作
- 等待连接建立
- 发布操作
# 六、实战:基于 RDMA 的 client-server 程序
# 1. server 端
- 工作流程:
- 初始化 RDMA 资源
- 等待 client 连接
- 分配并固定服务器缓冲区 buffer
- 接受客户端连接
- 将有关本地服务器缓冲区的信息发送到客户端
- 等待断开连接
# 2. client 端
- 工作流程:
- 初始化 RDMA 资源
- 连接 server
- 通过发送/接收 exchange 接收服务器端缓冲区信息
- 从(第一个)本地缓冲区向服务器缓冲区进行 RDMA 写入。
- 进行 RDMA 读取,将服务器缓冲区的内容读入第二个本地缓冲区。
- 比较第一缓冲区和第二缓冲区的内容,并进行匹配
- 断开连接
# 3. 项目实现
Waiting for api.github.com...
# 4. 补充:RDMA 应用程序标准流程
# 参考文献
- Mellanox Technologies, Inc. (2017). RDMA Aware Networks Programming User Manual. Mellanox Technologies, Inc.