【RDMA】RDMA read和write编程实例(verbs API)

 WRITE|READ编程(RDMA read and write with IB verbs)

(本文讲解的示例代码在:RDMA read and write with IB verbs | The Geek in the Corner)


将 RDMA 与verbs一起使用非常简单:首先注册内存块,然后交换内存描述符,然后进行读/写操作。注册是通过调用 ibv_reg_mr() 来完成的,它将内存块固定(从而防止它被交换出)并返回一个包含 uint32_t key的结构 ibv_mr * ,允许远程访问注册的内存。然后必须通过某种带外机制与对等方交换此key以及块的地址。然后,对方可以在调用 ibv_post_send() 时使用key和地址来post  RDMA 读和写请求。

一些代码可能有指导意义:

/* PEER 1 */

/* PEER 1 */const size_t SIZE = 1024;char *buffer = malloc(SIZE);
struct ibv_mr *mr;
uint32_t my_key;
uint64_t my_addr;mr = ibv_reg_mr(pd, buffer, SIZE, IBV_ACCESS_REMOTE_WRITE);my_key = mr->rkey;
my_addr = (uint64_t)mr->addr;/* exchange my_key and my_addr with peer 2 */

/* PEER 2 */ 

/* PEER 2 */const size_t SIZE = 1024;char *buffer = malloc(SIZE);
struct ibv_mr *mr;
struct ibv_sge sge;
struct ibv_send_wr wr, *bad_wr;
uint32_t peer_key;
uint64_t peer_addr;mr = ibv_reg_mr(pd, buffer, SIZE, IBV_ACCESS_LOCAL_WRITE);/* get peer_key and peer_addr from peer 1 */strcpy(buffer, "Hello!");memset(&wr, 0, sizeof(wr));sge.addr = (uint64_t)buffer;
sge.length = SIZE;
sge.lkey = mr->lkey;wr.sg_list = &sge;
wr.num_sge = 1;
wr.opcode = IBV_WR_RDMA_WRITE;wr.wr.rdma.remote_addr = peer_addr;
wr.wr.rdma.rkey = peer_key;ibv_post_send(qp, &wr, &bad_wr);

PEER 1 的 ibv_reg_mr() 的最后一个参数 IBV_ACCESS_REMOTE_WRITE 指定我们希望对PEER  2 具有对位于缓冲区的内存块的写访问权限。

在实践中使用它更复杂。本文附带的示例代码连接两台主机,交换内存区域密钥keys、读取或写入远程内存,然后断开连接。顺序如下: 

  1. 初始化上下文并注册内存区域。
  2. 建立连接。
  3. 使用之前帖子中描述的发送/接收模型在对等点之间交换内存区域密钥keys。
  4. 后读/写操作。
  5. 断开。

连接的每一端都有两个线程:处理连接事件的主线程和轮询完成队列(CQ)的线程。为了避免死锁和竞争条件,我们安排了我们的操作,以便一次只有一个线程发布工作请求。为了详细说明上面的顺序,在建立连接后,

客户端将:

  1. 在 MSG_MR 消息中发送其 RDMA 内存区域密钥keys。
  2. 等待服务器的 MSG_MR 消息(包含其 RDMA 密钥keys)。
  3. 发布 RDMA 操作。
  4. 通过发送 MSG_DONE 消息通知服务器它已准备好断开连接。
  5. 等待来自服务器的 MSG_DONE 消息。
  6. 断开。

第一步发生在 RDMA 连接事件处理程序线程的上下文中,但第二步到第六步发生在verbs  CQ 轮询线程的上下文中。(verbs =verbs api =verbs 库)

服务端的操作顺序类似:

  1. 等待客户端的 MSG_MR 消息及其 RDMA 密钥。
  2. 在 MSG_MR 消息中发送其 RDMA 密钥。
  3. 发布 RDMA 操作。
  4. 通过发送 MSG_DONE 消息通知客户端它已准备好断开连接。
  5. 等待来自客户端的 MSG_DONE 消息。
  6. 断开。

这里所有六个步骤都发生在verbs  CQ 轮询线程的上下文中。等待 MSG_DONE 是必要的,否则我们可能会在对等方(peer)的 RDMA 操作完成之前关闭连接。在(服务端)发送 MSG_DONE 之前,我们不必等待 RDMA 操作 完成——InfiniBand 规范要求 requests  将按照它们发布的顺序启动 处理。这意味着在 RDMA 操作完成之前,对等方(peer)不会收到 MSG_DONE。

为简洁起见(并说明它们几乎相同),此示例的代码合并了上一组帖子中的许多客户端和服务器代码(common.c中),客户端 (rdma-client) 和服务器 (rdma-server) 继续运行不同的 RDMA 连接管理器循环处理事件(RDMA connection manager event loops),但它们相同的verbs 代码部分——轮询 CQ、发送消息、发布 RDMA 操作等共用一份代码。

我们也使用相同代码进行RDMA 读取和写入操作,因为它们非常相似。 rdma-server 和 rdma-client 将““read” or “write” 作为它们的第一个命令行参数。


让我们从 rdma-common.c 的顶部开始,它包含客户端和服务器通用的verbs 代码。我们首先定义我们的消息结构体。我们将使用它来在节点之间传递 RDMA 内存区域 (MR) 密钥并发出我们已完成的信号。

struct message 
{enum {MSG_MR,MSG_DONE} type;union {struct ibv_mr mr;} data;
};

我们的连接结构体已扩展,包括用于 RDMA 操作的内存区域以及对等方(peer)的 MR 结构和两个状态变量:

struct connection 
{struct rdma_cm_id *id;struct ibv_qp *qp;int connected;struct ibv_mr *recv_mr;struct ibv_mr *send_mr;struct ibv_mr *rdma_local_mr;struct ibv_mr *rdma_remote_mr;struct ibv_mr peer_mr;struct message *recv_msg;struct message *send_msg;char *rdma_local_region;char *rdma_remote_region;enum {SS_INIT,SS_MR_SENT,SS_RDMA_SENT,SS_DONE_SENT} send_state;enum {RS_INIT,RS_MR_RECV,RS_DONE_RECV} recv_state;
};

完成处理程序(completion handler)使用 send_state 和 recv_state 这两个状态枚举变量来确保对等点(peer)之间的消息和 RDMA 操作的正确顺序。

该结构体由 build_connection() 初始化:

void build_connection(struct rdma_cm_id *id)
{struct connection *conn;struct ibv_qp_init_attr qp_attr;build_context(id->verbs);build_qp_attr(&qp_attr);TEST_NZ(rdma_create_qp(id, s_ctx->pd, &qp_attr));id->context = conn = (struct connection *)malloc(sizeof(struct connection));conn->id = id;conn->qp = id->qp;conn->send_state = SS_INIT;conn->recv_state = RS_INIT;conn->connected = 0;register_memory(conn);post_receives(conn);
}

由于我们使用 RDMA read操作,我们必须在 struct rdma_conn_param 中设置initiator_depth 和responder_resources。这些控制并行的 RDMA read请求的数量(These control the number of simultaneous outstanding RDMA read requests):

void build_params(struct rdma_conn_param *params)
{memset(params, 0, sizeof(*params));params->initiator_depth = params->responder_resources = 1;params->rnr_retry_count = 7; /* infinite retry */
}

将 rnr_retry_count 设置为 7 表示我们希望网卡在对端回复  receiver-not-ready (RNR) 错误时无限期地重新发送。当在对端发布相应的接收请求( receive request)之前发布发送请求(send request )时,会发生 RNR。

使用 send_message() 函数post 发送:

void send_message(struct connection *conn)
{struct ibv_send_wr wr, *bad_wr = NULL;struct ibv_sge sge;memset(&wr, 0, sizeof(wr));wr.wr_id = (uintptr_t)conn;wr.opcode = IBV_WR_SEND;wr.sg_list = &sge;wr.num_sge = 1;wr.send_flags = IBV_SEND_SIGNALED;sge.addr = (uintptr_t)conn->send_msg;sge.length = sizeof(struct message);sge.lkey = conn->send_mr->lkey;while (!conn->connected);TEST_NZ(ibv_post_send(conn->qp, &wr, &bad_wr));
}

send_mr() 封装了这个函数,并被 rdma-client 用来将它的 MR 发送到服务器,提示服务器发送它的 MR 作为响应,从而启动 RDMA 操作:

void send_mr(void *context)
{struct connection *conn = (struct connection *)context;conn->send_msg->type = MSG_MR;memcpy(&conn->send_msg->data.mr, conn->rdma_remote_mr, sizeof(struct ibv_mr));send_message(conn);
}

完成处理程序( completion handler)完成大部分工作。它维护 send_state 和 recv_state,根据需要回复消息和发布 RDMA 操作:

void on_completion(struct ibv_wc *wc)
{struct connection *conn = (struct connection *)(uintptr_t)wc->wr_id;if (wc->status != IBV_WC_SUCCESS)die("on_completion: status is not IBV_WC_SUCCESS.");if (wc->opcode & IBV_WC_RECV) {conn->recv_state++;if (conn->recv_msg->type == MSG_MR) {memcpy(&conn->peer_mr, &conn->recv_msg->data.mr, sizeof(conn->peer_mr));post_receives(conn); /* only rearm for MSG_MR */if (conn->send_state == SS_INIT) /* received peer's MR before sending ours, so send ours back */send_mr(conn);}} else {conn->send_state++;printf("send completed successfully.\n");}if (conn->send_state == SS_MR_SENT && conn->recv_state == RS_MR_RECV) {struct ibv_send_wr wr, *bad_wr = NULL;struct ibv_sge sge;if (s_mode == M_WRITE)printf("received MSG_MR. writing message to remote memory...\n");elseprintf("received MSG_MR. reading message from remote memory...\n");memset(&wr, 0, sizeof(wr));wr.wr_id = (uintptr_t)conn;wr.opcode = (s_mode == M_WRITE) ? IBV_WR_RDMA_WRITE : IBV_WR_RDMA_READ;wr.sg_list = &sge;wr.num_sge = 1;wr.send_flags = IBV_SEND_SIGNALED;wr.wr.rdma.remote_addr = (uintptr_t)conn->peer_mr.addr;wr.wr.rdma.rkey = conn->peer_mr.rkey;sge.addr = (uintptr_t)conn->rdma_local_region;sge.length = RDMA_BUFFER_SIZE;sge.lkey = conn->rdma_local_mr->lkey;TEST_NZ(ibv_post_send(conn->qp, &wr, &bad_wr));conn->send_msg->type = MSG_DONE;send_message(conn);} else if (conn->send_state == SS_DONE_SENT && conn->recv_state == RS_DONE_RECV) {printf("remote buffer: %s\n", get_peer_message_region(conn));rdma_disconnect(conn->id);}
}
Let’s examine on_completion() in parts. First, the state update:if (wc->opcode & IBV_WC_RECV) {conn->recv_state++;if (conn->recv_msg->type == MSG_MR) {memcpy(&conn->peer_mr, &conn->recv_msg->data.mr, sizeof(conn->peer_mr));post_receives(conn); /* only rearm for MSG_MR */if (conn->send_state == SS_INIT) /* received peer's MR before sending ours, so send ours back */send_mr(conn);}} else {conn->send_state++;printf("send completed successfully.\n");

如果完成的操作是接收操作(即,如果 wc->opcode 设置了 IBV_WC_RECV),则 recv_state 递增。

如果收到的消息是 MSG_MR,我们将收到的 MR 复制到我们的连接结构的 peer_mr 成员中,并重新准备接收槽。这对于确保我们在对端的 RDMA 操作完成后收到 MSG_DONE 消息是必要的。如果我们收到了对方的 MR 但还没有发送我们的(服务器就是这种情况),我们就调用 send_mr() 将我们的 MR 发给对方。更新 send_state 并不复杂。


接下来我们检查 send_state 和 recv_state 的两个特定组合:

if (conn->send_state == SS_MR_SENT && conn->recv_state == RS_MR_RECV){struct ibv_send_wr wr, *bad_wr = NULL;struct ibv_sge sge;if (s_mode == M_WRITE)printf("received MSG_MR. writing message to remote memory...\n");elseprintf("received MSG_MR. reading message from remote memory...\n");memset(&wr, 0, sizeof(wr));wr.wr_id = (uintptr_t)conn;wr.opcode = (s_mode == M_WRITE) ? IBV_WR_RDMA_WRITE : IBV_WR_RDMA_READ;wr.sg_list = &sge;wr.num_sge = 1;wr.send_flags = IBV_SEND_SIGNALED;wr.wr.rdma.remote_addr = (uintptr_t)conn->peer_mr.addr;wr.wr.rdma.rkey = conn->peer_mr.rkey;sge.addr = (uintptr_t)conn->rdma_local_region;sge.length = RDMA_BUFFER_SIZE;sge.lkey = conn->rdma_local_mr->lkey;TEST_NZ(ibv_post_send(conn->qp, &wr, &bad_wr));conn->send_msg->type = MSG_DONE;send_message(conn);} 
else if (conn->send_state == SS_DONE_SENT && conn->recv_state == RS_DONE_RECV) 
{printf("remote buffer: %s\n", get_peer_message_region(conn));rdma_disconnect(conn->id);
}

这些组合中的第一个是当我们既发送了我们的 MR 又收到了对方的 MR 时。这表明我们已准备好发布 RDMA 操作并发布 MSG_DONE。发布 RDMA 操作意味着构建 RDMA 工作请求(RDMA  work request)。这类似于发送工作请求( work request),除了我们指定 RDMA 操作码并传递对等方的 RDMA 地址/密钥:

wr.opcode = (s_mode == M_WRITE) ? IBV_WR_RDMA_WRITE : IBV_WR_RDMA_READ;wr.wr.rdma.remote_addr = (uintptr_t)conn->peer_mr.addr;
wr.wr.rdma.rkey = conn->peer_mr.rkey;

请注意,我们不需要为 remote_addr 使用 conn->peer_mr.addr(即remote_addr不一定非得等于 conn->peer_mr.addr?)— 如果我们愿意,我们可以使用落入 ibv_reg_mr() 注册的内存区域范围内的任何地址(如conn->peer_mr.addr+x,conn->peer_mr.addr+x在注册的内存区域范围内?)
第二个状态组合是 SS_DONE_SENT 和 RS_DONE_RECV,表明我们已经发送 MSG_DONE 并从对等方接收 MSG_DONE。这意味着打印消息缓冲区并断开连接是安全的:

printf("remote buffer: %s\n", get_peer_message_region(conn));
rdma_disconnect(conn->id);

 就是这样。如果一切正常,您应该在使用 RDMA 写入时看到以下内容:

$ ./rdma-server write

listening on port 47881.

received connection request.

send completed successfully.

received MSG_MR. writing message to remote memory...

send completed successfully.

send completed successfully.

remote buffer: message from active/client side with pid 20692

peer disconnected.

$ ./rdma-client write 192.168.0.1 47881

address resolved.

route resolved.

send completed successfully.

received MSG_MR. writing message to remote memory...

send completed successfully.

send completed successfully.

remote buffer: message from passive/server side with pid 26515

disconnected.

 当使用 RDMA read时:

$ ./rdma-server read

listening on port 47882.

received connection request.

send completed successfully.

received MSG_MR. reading message from remote memory...

send completed successfully.

send completed successfully.

remote buffer: message from active/client side with pid 20916

peer disconnected.

$ ./rdma-client read 192.168.0.1 47882

address resolved.

route resolved.

send completed successfully.

received MSG_MR. reading message from remote memory...

send completed successfully.

send completed successfully.

remote buffer: message from passive/server side with pid 26725

disconnected.

再次,示例代码可在此处获得。

Updated, Oct. 4: Sample code is now at the-geek-in-the-corner/02_read-write at master · tarickb/the-geek-in-the-corner · GitHub.

 infiniband概念空间分析 - 知乎

https://www.researchgate.net/figure/RDMA-Write_fig2_4245345

operation diagram: rdma read

operation diagram: rdma write

Introduction to Programming Infiniband RDMA | Better Tomorrow with Computer Science

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/62962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

洛谷P2670扫雷游戏(Java)

三.P2670 [NOIP2015 普及组] 扫雷游戏 题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n 行 m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩…

泷羽sec专题课笔记-- Linux作业--开机自启动方法以及破解

本笔记为 泷羽sec 《红队全栈课程》学习笔记,课程请可自行前往B站学习,课程/笔记主要涉及网络安全相关知识、系统以及工具的介绍等,请使用该课程、本笔记以及课程和笔记中提及工具的读者,遵守网络安全相关法律法规,切勿…

Linux内核早期打印机制与RS485通信技术

往期内容 本专栏往期内容:Uart子系统 UART串口硬件介绍深入理解TTY体系:设备节点与驱动程序框架详解Linux串口应用编程:从UART到GPS模块及字符设备驱动 解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解IMX 平台UART驱…

汽车网络安全 -- IDPS如何帮助OEM保证车辆全生命周期的信息安全

目录 1.强标的另一层解读 2.什么是IDPS 2.1 IDPS技术要点 2.2 车辆IDPS系统示例 3.车辆纵深防御架构 4.小结 1.强标的另一层解读 在最近发布的国家汽车安全强标《GB 44495》,在7.2节明确提出了12条关于通信安全的要求,分别涉及到车辆与车辆制造商云平台通信、车辆与车辆…

如何利用内链策略提升网站的整体权重?

内链是谷歌SEO中常常被低估的部分,实际上,合理的内链策略不仅能帮助提升页面间的关联性,还可以增强网站的整体权重。通过正确的内链布局,用户可以更流畅地浏览你的网站,谷歌爬虫也能更快地抓取到更多页面,有…

工业—使用Flink处理Kafka中的数据_ChangeRecord2

使用 Flink 消费 Kafka 中 ChangeRecord 主题的数据,每隔 1 分钟输出最近 3 分钟的预警次数最多的 设备,将结果存入Redis 中, key 值为 “warning_last3min_everymin_out” , value 值为 “ 窗口结束时间,设备id” &am…

汇编语言学习-二

好吧,已经隔了两天,下完班看了两天,在电脑上装了虚拟机版的MS_DOS,主要是怕折腾坏我的电脑系统; 这个第二天应该是称为第二章更为合适,目前第二章已经看完,基本的命令也是敲了敲; 下面就进行一…

等差数列末项计算

等差数列末项计算 C语言代码C 代码Java代码Python代码 💐The Begin💐点点关注,收藏不迷路💐 给出一个等差数列的前两项a1,a2,求第n项是多少。 输入 一行,包含三个整数a1,a2&#x…

【笔记2-1】ESP32:基于vscode的espidf插件的开发环境搭建

主要参考b站宸芯IOT老师的视频,记录自己的笔记,老师讲的主要是linux环境,但配置过程实在太多问题,就直接用windows环境了,老师也有讲一些windows的操作,只要代码会写,操作都还好,开发…

基于Java Springboot蛋糕订购小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 微信…

使用Postman搞定各种接口token实战

现在许多项目都使用jwt来实现用户登录和数据权限,校验过用户的用户名和密码后,会向用户响应一段经过加密的token,在这段token中可能储存了数据权限等,在后期的访问中,需要携带这段token,后台解析这段token才…

H3C OSPF实验

实验拓扑 实验需求 按照图示配置 IP 地址按照图示分区域配置 OSPF ,实现全网互通为了路由结构稳定,要求路由器使用环回口作为 Router-id,ABR 的环回口宣告进骨干区域 实验解法 一、配置IP地址 [R1]int l0 [R1-LoopBack0]ip add 1.1.1.1 32 […

LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型 Matlab代码注释清晰。 程序设计 完整程序和数据获取方式:私信博主回复LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型(Matlab&#…

Ai编程cursor + sealos + devBox实现登录以及用户管理增删改查(十三)

一、什么是 Sealos? Sealos 是一款以 Kubernetes 为内核的云操作系统发行版。它以云原生的方式,抛弃了传统的云计算架构,转向以 Kubernetes 为云内核的新架构,使企业能够像使用个人电脑一样简单地使用云。 二、适用场景 业务运…

CSS学习记录02

CSS颜色 指定颜色是通过使用预定义的颜色名称&#xff0c;或RGB&#xff0c;HEX&#xff0c;HSL&#xff0c;RGBA&#xff0c;HSLA值。 CSS颜色名 在CSS中&#xff0c;可以使用颜色名称来指定颜色&#xff1a; CSS背景色 您可以为HTML元素设置背景色&#xff1a; <h1 s…

【VUE3】npm : 无法加载文件 D:\Program\nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本。

npm : 无法加载文件 D:\Program\nodejs\npm.ps1。未对文件 D:\Program\nodejs\npm.ps1 进行数字签名。无法在当前系统上运行该脚本。有关运行脚本和设置执行策略的详细信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_ Execution_Policies。…

级联树结构TreeSelect和上级反查

接口返回结构 前端展示格式 前端组件 <template><div ><el-scrollbar height"70vh"><el-tree :data"deptOptions" :props"{ label: label, children: children }" :expand-on-click-node"false":filter-node-me…

Ansible自动化一键部署单节点集群架构

自动化部署利器&#xff1a;Ansible 一键部署脚本 在现代IT基础设施管理中&#xff0c;Ansible以其简洁、强大的自动化能力脱颖而出。以下是精心打造的Ansible自动化一键部署脚本&#xff0c;旨在简化部署流程&#xff0c;提升效率&#xff0c;确保一致性和可靠性。 通过这个…

基于智能语音交互的智能呼叫中心工作机制

在智能化和信息化不断进步的现代&#xff0c;智能呼叫中心为客户提供高质量、高效率的服务体验&#xff0c;提升众多品牌用户的满意度和忠诚度。作为实现智能呼叫中心的关键技术之一的智能语音交互技术&#xff0c;它通过集成自然语言处理&#xff08;NLP&#xff09;、语音识别…

CLIP模型也能处理点云信息

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…