【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.rhkb.cn/news/483923.html

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

相关文章

UE5 C++ 不规则按钮识别,复选框不规则识别 UPIrregularWidgets

插件名称:UPIrregularWidgets 插件包含以下功能 你可以点击任何图片,而不仅限于矩形图片。 UPButton、UPCheckbox 基于原始的 Button、Checkbox 扩展。 复选框增加了不规则图像识别功能,复选框增加了悬停事件。 欢迎来到我的博客 记录学习过…

洛谷P2670扫雷游戏(Java)

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

如何加强游戏安全,防止定制外挂影响游戏公平性

在现如今的游戏环境中,外挂始终是一个困扰玩家和开发者的问题。尤其是定制挂(Customized Cheats),它不仅复杂且隐蔽,更能针对性地绕过传统的反作弊系统,对游戏安全带来极大威胁。定制挂通常是根据玩家的需求…

概率论相关知识随记

作为基础知识的补充,随学随记,方便以后查阅。 概率论相关知识随记 期望(Expectation)期望的定义离散型随机变量的期望示例:掷骰子的期望 连续型随机变量的期望示例:均匀分布的期望 期望的性质线性性质期望的…

DICOM MPPS详细介绍

文章目录 前言一、常规检查业务流程二、MPPS的作用三、MPPS的原理1、MPPS与MWL2、MPPS服务过程 四、MPPS的实现步骤1、创建实例2、传递状态 五、总结 前言 医院中现有的DICOM MWL(Modality Worklist)已开始逐渐得到应用,借助它可以实现病人信息的自动录入&#xff0…

Secured Finance 推出 TVL 激励计划以及基于 FIL 的稳定币

Secured Finance 是新一代 DeFi 2.0 协议,其正在推出基于 FIL 的稳定币、固定收益市场以及具有吸引力的 TVL 激励计划,以助力 Filecoin 构建更强大的去中心化金融生态体系,并为 2025 年初 Secured Finance 协议代币的推出铺平道路。Secure Fi…

FPGA Xilinx维特比译码器实现卷积码译码

FPGA Xilinx维特比译码器实现卷积码译码 文章目录 FPGA Xilinx维特比译码器实现卷积码译码1 Xilinx维特比译码器实现2 完整代码3 仿真结果 MATLAB (n,k,m)卷积码原理及仿真代码(你值得拥有)_matlab仿真后代码-CSDN博客 MATLAB 仿真…

Linux 权限管理:用户分类、权限解读与常见问题剖析

🌟 快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。🌟 🚩用通俗易懂且不失专业性的文字,讲解计算机领域那些看似枯燥的知识点🚩 目录 💯L…

rabbitmq 安装延时队列插件rabbitmq_delayer_message_exchange(linux centOS 7)

1.插件版本 插件地址:Community Plugins | RabbitMQ rabbitmq插件需要对应的版本,根据插件地址找到插件 rabbitmq_delayer_message_exchange 点击Releases 因为我rabbitmq客户端显示的版本是: 所以我选择插件版本是: 下载 .ez文…

遗传算法与深度学习实战(26)——编码卷积神经网络架构

遗传算法与深度学习实战(26)——编码卷积神经网络架构 0. 前言1. EvoCNN 原理1.1 工作原理1.2 基因编码 2. 编码卷积神经网络架构小结系列链接 0. 前言 我们已经学习了如何构建卷积神经网络 (Convolutional Neural Network, CNN),在本节中&a…

数学建模之熵权法

熵权法 概述 **熵权法(Entropy Weight Method,EWM)**是一种客观赋权的方法,原理:指标的变异程度越小,所包含的信息量也越小,其对应的权值应该越低(例如,如果对于所有样本而言,某项指标的值都相…

同道猎聘Q3营收降利润增,AI或成估值重塑关键词

2024年,经济向好的趋势没有改变,挑战却仍然存在。企业纷纷进行结构性变革优化或业务方向调整。这一点反映到人才市场,绝大多数企业对招聘扩张持保守态度,降本增效的主题仍在延续。 作为人才市场水温变化的“温度计”,…

46 基于单片机的烧水壶系统设计

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52RC单片机,采用四个按键,通过DS18B20检测温度,开机显示实时温度 第一个按键为切换功能按键,按下后,可以设置烧水温度的大小&…

推荐学习笔记:矩阵补充和矩阵分解

参考: 召回 fun-rec/docs/ch02/ch2.1/ch2.1.1/mf.md at master datawhalechina/fun-rec GitHub 业务 隐语义模型与矩阵分解 协同过滤算法的特点: 协同过滤算法的特点就是完全没有利用到物品本身或者是用户自身的属性, 仅仅利用了用户与…

【机器学习】—Transformers的扩展应用:从NLP到多领域突破

好久不见!喜欢就关注吧~ 云边有个稻草人-CSDN博客 目录 引言 一、Transformer架构解析 (一)、核心组件 (二)、架构图 二、领域扩展:从NLP到更多场景 1. 自然语言处理(NLP) 2…

【SpringMVC】用户登录器项目,加法计算器项目的实现

阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 一:用户登录项目实现 1:需求 2:准备工作 (1&#xf…

数据结构(2)——顺序表的模拟实现

一:顺序表的认识 通过数据结构(1)对于算法复杂度的理解,现在我们正式进入数据结构的核心内容,今天,先来使用C语言实现一下数据结构中最简单的顺序表。 首先介绍一下顺序表的概念,先从线性表说…

docker更换容器存储位置

一:原因 今天之前在某个服务器上使用docker搭建的服务突然无法访问了,进入服务器查看发现服务运行正常,但是就是无法使用,然后我这边准备将docker服务重新启动下看看,发现docker服务无法重启,提示内存已满…

Day5:生信新手笔记 — R语言基本语法

一、数据类型 &#xff08;重点只有两个&#xff0c;剩下的不看&#xff09; 1.1 向量&#xff08;vector&#xff09; 矩阵&#xff08;Matrix&#xff09; 数组&#xff08;Array&#xff09; 1.2 数据框&#xff08;Data frame&#xff09; x<- c(1,2,3) #常用的向…

【机器学习】窥数据之序,悟算法之道:机器学习的初心与远方

文章目录 机器学习入门&#xff1a;从零开始学习基础与应用前言第一部分&#xff1a;什么是机器学习&#xff1f;1.1 机器学习的定义1.1.1 举个例子&#xff1a;垃圾邮件分类器 1.2 机器学习的核心思想1.2.1 数据驱动的模式提取1.2.2 为什么机器学习比传统方法更灵活&#xff1…