【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");
    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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JAVAWeb之CSS学习

前引 CSS,层叠样式表(Cascading Style Sheets),能够对网页中元素位置的排版进行像素级精确控制,支持几乎所有的字体字号样式,拥有网页对象和模型样式编辑的能力,简单来说,美化页面。…

Linux CentOS

​阿里云开源镜像下载链接 https://mirrors.aliyun.com/centos/7/isos/x86_64/ VMware 安装 CentOS7 自定义 下一步 选择稍后安装操作系统 选择 输入 查看物理机CPU内核数量 CtrlShiftEsc 总数不超过物理机内核数量 推荐内存 自选 推荐 推荐 默认 拆分成多个 默认 自定义硬件…

gazebo 仿真阶段性问题汇总三

目录 报错:关节设置为 revolute 缺少 limit 属性解决方法 轮子转向左右相反解决方法 rviz2只显示一次雷达数据,刷新后消失解决方法 修改2D雷达为3D雷达2d 雷达3D 雷达 报错:关节设置为 revolute 缺少 limit 属性 revolute 表示是有限制的旋转…

python学习笔记14 python中的库,常见的内置库(random、hashlib、json、时间、os)

接着上一篇python学习记录的内容,今天我们来看库 内置库:time、random、os、json…第三方库:requests,pandas,numpy…自定义库:xxx.py 导入的方式 import 直接将一个库导入进来 import base.day04.login_demo as t # 导入login_…

微信小程序版小米商城的搭建流程详解!

很多初学微信小程序语法的同学,可能不知道如何布局和搭建一个项目,下面我将讲解初学者如何搭建项目和注意事项。 一、 app.json的配置 {"pages": ["pages/index/index","pages/classification/classification","pag…

PETR:Position Embedding Transformation forMulti-View 3D Object Detection

全文摘要 本文介绍了一种名为“位置嵌入变换(PETR)”的新方法,用于多视角三维物体检测。该方法将三维坐标的位置信息编码为图像特征,并产生具有三维位置感知能力的特征。通过对象查询可以感知这些特征并进行端到端的目标检测。在…

2024前端框架年度总结报告(二):新生qwik+solid和次新生svelte+Astro对比 -各自盯着前端的哪些个痛点 - 前端的区域发展差异

引言 2024年,前端开发依然是技术领域的热点之一。随着 Web 应用的日益复杂,前端框架的更新换代也加速了。尽管 React、Vue 和 Angular 老牌框架年度总结 等“老牌”框架仍然占据着主流市场,但一些新兴的框架在不断挑战这些“巨头”的地位&am…

多模态大语言模型的对比

简介 文章主要对比了包括 VideoLLaMA 2 、CogVLM2-video 、MiniCPM-V等模型 目前主流的多模态视觉问答大模型,大部分采用视觉编码器、大语言模型、图像到文本特征的投影模块 目录 简介1. VideoLLaMA 21.1 网络结构1.2 STC connector具体的架构 2. MiniCPM-V 2.62.…

美畅物联丨智能监控,高效运维:视频汇聚平台在储能领域的实践探索

在当今全球能源格局不断变化的大背景下,对清洁能源的需求正以惊人的速度增长。储能项目作为平衡能源供需、提升能源利用效率的关键环节,其规模和复杂度也在不断攀升。在储能项目的运营管理过程中,安全监控、设备运维以及数据管理等方面面临着…

node.js实现分页,jwt鉴权机制,token,cookie和session的区别

文章目录 1. 分⻚功能2. jwt鉴权机制1.jwt是什么2.jwt的应用3.优缺点 3. cookie,token,session的对比 1. 分⻚功能 为什么要分页 如果数据量很⼤,⽐如⼏万条数据,放在⼀个⻚⾯显⽰的话显然不友好,这时候就需要采⽤分⻚…

消息中间件-Kafka2-3.9.0源码构建

消息中间件-Kafka2-3.9.0源码构建 1、软件环境 JDK Version 1.8Scala Version 2.12.0Kafka-3.9.0 源码包 下载地址:https://downloads.apache.org/kafka/3.9.0/kafka-3.9.0-src.tgzGradle Version > 8.8Apache Zookeeper 3.7.0 2、源码编译 打开源码根目录修改…

修复docker启动失败:Failed to start Docker Application Container Engine

配置了镜像源之后,运行sudo systemctl restart docker.service失败,提示让运行systemctl status docker.service或journalctl -xeu docker.service查看详细信息。 运行后者发现有如下日志: 红色区域是我设置的一个镜像源这个日志的意思就是…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-43

文件下载与邀请翻译者 学习英特尔开发手册,最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册,会是一件耗时费力的工作。如果有愿意和我一起来做这件事的,那么&#xff…

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

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

react 路由鉴权

权限路由一般两种 1中接口中返回菜单 2 接口中返回权限,前端做匹配 一般都是那种结合,react中没有vue那种钩子函数如何做? 在项目中写一个高阶函数,在高阶函数中判断权限、是否登录等操作app.tsx或者man.tsx中使用 《AuthRouter》…

Ansys Maxwell:电机中的磁芯和磁体损耗

了解磁体中的涡流损耗和电动机叠片中的磁芯损耗 磁体中的涡流损耗会影响电动机的效率和热性能。当交变磁场在导电材料(例如电动机中使用的磁铁)内感应出电流回路时,就会发生这种现象。这些电流会产生热量,导致效率降低和磁热点。…

EtherCAT转ProfiNet网关实现西门子1200PLC与伺服电机连接的通讯案例

一. 案例背景 西门子1200PLC通过捷米特JM-ECTM-PN(EtherCAT转ProfiNet)网关将松下伺服电机(包括不限于型号MHMFO22D1U2M)或EtherCAT协议的其它设备或连接到ProfiNetPLC上,并在正常运行中支持EtherCAT协议。本产品可作为EtherCAT主站,做为西门子S7-1200系…

在 MacOS 上为 LM Studio 更换镜像源

在 MacOS 之中使用 LM Studio 部署本地 LLM时,用户可能会遇到无法下载模型的问题。 一般的解决方法是在 huggingface.co 或者国内的镜像站 hf-mirror.com 的项目介绍卡页面下载模型后拖入 LM Studio 的模型文件夹。这样无法利用 LM Studio 本身的搜索功能。 本文将…

AI开发:用模型来识别手写数字的完整教程含源码 - Python 机器学习

今天一起来学习scikit-learn 。 scikit-learn 是一个强大的 Python 机器学习库,提供多种分类、回归、聚类算法,适用于从数据预处理到模型评估的全流程。它支持简单一致的 API,适合快速构建和测试模型。 官方地址在这里,记得Mark…

快速上手 RabbitMQ:使用 Docker 轻松搭建消息队列系统

在现代的分布式系统中,消息队列(Message Queue)是实现异步通信、解耦系统组件、提高系统可扩展性和可靠性的重要工具。RabbitMQ 是一个广泛使用的开源消息代理软件,它实现了高级消息队列协议(AMQP)&#xf…