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

news2025/7/14 15:06:45

 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");
    else
      printf("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");
  else
    printf("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


更多,见:https://blog.csdn.net/bandaoyu/article/details/113125244

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

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

相关文章

HTML5教程-表格宽度设置,最大宽度,自动宽度

HTML表格宽度 参考:html table width HTML表格是网页设计中常用的元素之一,可以用来展示数据、创建布局等。表格的宽度是一个重要的参数,可以通过不同的方式来设置表格的宽度,本文将详细介绍HTML表格宽度的不同设置方式和示例代…

2024年12月9日Github流行趋势

项目名称:ollama / ollama 项目维护者:mxyng, jmorganca, dhiltgen, BruceMacD, technovangelist等项目介绍:快速上手使用Llama 3.2、Mistral、Gemma 2及其他大型语言模型。项目star数:101,591项目fork数:8,117 项目名…

IntelliJ+SpringBoot项目实战(29)--如何将Beetl的模板文件放在独立的文件目录

在实际的项目开发中,为了方便前端人员调试页面,所以有必要将Beetl的模板文件放在独立的目录下,方便前端人员维护,而不是打包到项目的jar包中,如果打包到项目的jar包中还有另外的问题,就是一改动页面就要重新…

在Ubuntu上使用IntelliJ IDEA:开启你的Java开发之旅!

你好,年轻的学徒!🧑‍💻 是时候踏上进入Java开发世界的史诗之旅了,我们的得力助手将是强大的IntelliJ IDEA。准备好了吗?出发吧! 在我们开始之前,我们需要下载这个工具。但是&#…

GWAS分析先做后学

大家好,我是邓飞。 GWAS分析是生物信息和统计学的交叉学科,上可以学习编程,下可以学习统计。对于Linux系统,R语言,作图,统计学,机器学习等方向,都是一个极好的入门项目。生物信息如…

LeetCode—189. 轮转数组(中等)

题目描述: 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例1: 输入: nums [1,2,3,4,5,6,7], k 3输出:[5,6,7,1,2,3,4] 解释: 向右轮转 1 步:[7,1,2,3,4,5,6] 向右轮转 2 步:[6,7,1,2,3,4,5] 向…

ModelScope-Agent(1): 基于开源大语言模型的可定制Agent系统

目录 简介快速入门 简介 github地址 快速入门 看前两篇,调用千问API和天气API # 选用RolePlay 配置agent from modelscope_agent.agents.role_play import RolePlay # NOQArole_template 你扮演一个天气预报助手,你需要查询相应地区的天气&#x…

go语言的成神之路-标准库篇-fmt标准库

目录 一、三种类型的输出 print: println: printf: 总结: 代码展示: 二、格式化占位符 %s:用于格式化字符串。 %d:用于格式化整数。 %f:用于格式化浮点数。 %v&#xff1…

pyenv 安装脚本解读

pyenv 安装脚本 curl https://pyenv.run | bash执行上面这一行脚本就可以安装pyenv来满足你对 Python 多版本共存以及切换的支持。 pyenv搭配virtualenv可以满足你对Python虚拟环境版本的支持。个人感觉pyenv比conda更轻量,更推荐使用。 那么上面的脚本到底干了什…

OpenAI 12Days 第二天 强化微调(RFT):推动语言模型在科学研究中的应用

OpenAI 12Days 第二天 强化微调(RFT):推动语言模型在科学研究中的应用 文章目录 OpenAI 12Days 第二天 强化微调(RFT):推动语言模型在科学研究中的应用RFT的工作原理与应用领域案例研究:基因突变…

柯桥职场商务英语生活英语口语培训外贸纺织口语学习

"等一下"该怎么说? 大家应该都知道,wait a moment是一个祈使句,祈使句就难免带有命令的口吻,还有点不耐烦。 如果你把“等一下”说成wait a moment,外国人多半认为你是个傲慢无礼的人。毕竟在他们看来wait a…

嵌入式蓝桥杯学习7 产生PWM

Cubemx配置 打开cubemx,前面的配置看上文,这里主要配置定时器产生PWM波。 以PA1的TIM2-CH2通道为例进行演示。 1.在Timers中打开TIM2,将Channel2配置为PWM Generation CH2。 2.将Clock Source 选择为Internal Clock。 3.配置Paramater Settings中的参…

SQL Server中SELECT (Transact-SQL)语法定义和解释

语法定义&#xff1a; <SELECT statement> ::[ WITH { [ XMLNAMESPACES , ] [ <common_table_expression> [ , ...n ] ] } ]<query_expression>[ ORDER BY <order_by_expression> ][ <FOR Clause> ][ OPTION ( <query_hint> [ , ...n ] )…

证书监控续签工具

详细实现方式以及代码下载请前往https://www.passerma.com/article/91 一个基于 go 的 ssl 证书监控续签工具&#xff0c;用于监控阿里云免费 ssl 证书的有效期&#xff0c;并在证书即将过期时自动续签证书 仅支持续签阿里云的免费证书 页面 使用方法 1.申请阿里云账号 2.…

【开源】A063—基于Spring Boot的农产品直卖平台的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

HDR视频技术之六:色调映射

图像显示技术的最终目的就是使得显示的图像效果尽量接近人们在自然界中观察到的对应的场景。 HDR 图像与视频有着更高的亮度、更深的位深、更广的色域&#xff0c;因此它无法在常见的普通显示器上显示。 入门级的显示器与播放设备&#xff08;例如普通人家使用的电视&#xff0…

【C++笔记】map和set的使用

前言 各位读者朋友们大家好&#xff01;上期我们讲完了二叉搜索树这一数据结构&#xff0c;这一期我们来讲STL中的map和set这两大容器。这两个容器的底层是红黑树&#xff0c;红黑树的底层是平衡二叉搜索树。 目录 前言一. 序列式容器和关联式容器二. set系列的使用2.1 set类…

DevOps持续集成

DevOps流程 第一步安装git 关闭防火墙 systemctl stop firewalld cd /usr/loacl vim docker-compose.yml docker search gitlab 拉取gitlab镜像 2.33GB docker pull gitlab/gitlab-ce:latestvim docker-compose.yml修改docker-compose.yml version: 3.1 services:gitlab:i…

TCP的“可靠性”(上)

目录 TCP的“可靠性”&#xff08;上&#xff09;确认应答&#xff08;可靠性传输的基础&#xff09;超时重传连接管理&#xff08;三次握手&#xff0c;四次挥手&#xff09; TCP的“可靠性”&#xff08;上&#xff09; 想必大家都或多或少的听说过TCP的特性&#xff1a;有连…

【C++】求第二大的数详细解析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;输入描述&#x1f4af;解题思路分析1. 题目核心要求2. 代码实现与解析3. 核心逻辑逐步解析定义并初始化变量遍历并处理输入数据更新最大值与次大值输…