RDMA send/recv 和 read/write操作

RDMA one-side 和 two-side原语

在RDMA传输中,SEND/RECEIVE是双边操作,即需要通信双方的参与,并且RECEIVE要先于SEND执行,这样对方才能发送数据,当然如果对方不需要发送数据,可以不执行RECEIVE操作,因此该过程和传统通信相似,区别在于RDMA的零拷贝网络技术和内核旁路,延迟低,多用于传输短的控制消息。WRITE/READ是单边操作,顾名思义,读/写操作是一方在执行,在实际的通信过程中,WRITE/READ操作是由active即客户端来执行的,而passive即服务器不需要执行任何操作。RDMA WRITE操作中,由客户端把数据从本地buffer中直接push到远程QP的虚拟空间的连续内存块中(物理内存不一定连续),因此需要知道目的地址(remote_addr)和访问权限(remote_key)。RDMA READ操作中,是客户端直接到远程的QP的虚拟空间的连续内存块中获取数据poll到本地目的buffer中,因此需要远程QP的内存地址和访问权限。单边操作多用于批量数据传输。

可以看出,在单边操作过程中,客户端需要知道远程QP的remote_addr(要读取或写入的地址)和remote_key,而这两个信息是通过SEND/REVEIVE操作来交换的,RDMA通信过程的大致流程如下:

1)初始化context,注册内存域

2)建立RDMA连接

3)通过SEND/RECEIVE操作,C/S交换包含RDMA memory region key的MSG_MR消息(一般是客户端先发送)

4)通过WRITE/READ操作,进行数据传输(单边操作)

5)发送MSG_DONE消息,关闭连接

相关函数

1、RDMA_CM API(<rdma/rdma_verbs.h>)中的有关WRITE/READ和SEND/RECEIVE涉及到的函数如下所示

发送RDMA WRITE请求,单边操作

int rdma_post_write(struct rdma_cm_id *id, void *context, void *addr, size_t length, 
struct ibv_mr *mr, int flags, uint64_t remote_addr, uint32_t rkey);

把写WR发送到和rdma_cm_id关联的QP的SQ中,本地数据缓冲区中的数据就会被写到远程内存中。本地缓冲区和远程内存都是已经注册过的,才能和RDMA设备直接交互。
id就是调用者对应的rdma_cm_id,context就是用户自定义的context,addr是发送者的本地地址,就是发送数据缓冲区数组,这个数组应该不同于send/recv操作对应的缓冲区数组,length就是缓冲区数组长度,mr就是这个发送缓冲区数组对应的mr,也就是说这个发送缓冲区数组是要被RDMA读取的,所以需要注册,flags是发送标志,remote_addr就是远程的地址,即服务器地址,rkey就是访问远程地址的权限。相当于说,我是client,对应id,我要发送的数据在addr放着,长度length,这个发送的数据所在的内存已经在RDMA设备上注册过了,是mr,发送标志是flags,控制写操作,由于我是直接写到服务器,服务器什么都不做,所以我得知道存到哪,而这个地址是通过send/recv交换过的,所以我就把数据放到remote_addr地址处,当然这块地址也是已经注册过的,我有这个地址的钥匙相当于操作权限rkey,这个也是服务器给我的。write就相当于说客户端一个人通过网络把数据直接放到服务器的指定位置,发送之前需要获取对方地址和key。

发送RDMA READ请求,单边操作

int rdma_post_read (struct rdma_cm_id *id, void *context, void *addr, size_t length, 
struct ibv_mr *mr, int flags, uint64_t remote_addr, uint32_t rkey);

把读WR发送到和rdma_cm_id关联的QP的SQ中,远程的数据将会被读到本地缓冲区中。本地和远程的数据缓冲区都是和RDMA设备直接打交道的,所以需要注册,有对应的mr。
id就是客户端的id,context是用户自定义,NULL应该可以,addr是本地目的地址,相当于说客户端去服务器取数据,存到这个addr中,已经注册过了,是mr,length是读操作的长度,flags是可选的标志位,用来控制读操作,remote_addr是要读取的服务器地址,已经注册过,rkey是和remote_addr相关联的key。
RDMA READ操作是客户端来进行,服务器无需操作,所以客户端需要知道服务器把数据放哪了,就是remote_addr,这个地址已经注册过了,并且返回了key,客户端还得知道这个远程地址的key,就是rkey,拿到数据之后还得存到客户端,存的目的地址就是addr,长度是length,这个目的地址已经注册了,是mr。

发送RDMA SEND请求,双边操作

int rdma_post_send (struct rdma_cm_id *id, void *context, void *addr, size_t length, 
struct ibv_mr *mr, int flags);

id是本地id,context用户自定义,addr是发送缓冲区地址,length是缓冲区长度,缓冲区注册过了,是mr,flags标志位,控制发送请求。将addr的长度length的数据发到所连接的对方。

发送RDMA RECEIVE请求,双边操作

int rdma_post_recv (struct rdma_cm_id *id, void *context, void *addr, size_t length, struct ibv_mr *mr);

id是本地id,context用户自定义,addr是本地接收缓冲区地址,length是缓冲区长度,缓冲区注册过了,是mr。

2、Infiniband VERBS API中(<infiniband/verbs.h>)的有关WRITE/READ和SEND/RECEIVE涉及到的函数如下所示

把WR发送到QP的SQ中,根据send_wr中的opcode区分是SEND/WRITE/READ操作

int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr);

QP是从ibv_create_qp()返回的QP,wr是要发送到QP的SQ中的WR链表,bad_wr:指向它的指针将填充第一个处理失败的工作请求。返回0表示成功。struct ibv_send_wr结构体中有个成员sg_list,指定用于读取或写入的本地内存buffer,至于是读取还是写入,取决于操作码opcode,对于SEND/WRITE操作,sg_list指定要读取的内存buffer,对于READ操作,sg_list指定要写入的内存buffer。

RECEIVE操作,recv_wr中并没有操作码opcode,因为只有一个接收操作

int ibv_post_recv(struct ibv_qp *qp, struct ibv_recv_wr *wr, struct ibv_recv_wr **bad_wr);

WR是发送到QP的RQ中的WR,bad_wr指向它的指针将会用来填充第一个失败的处理请求。返回0表示成功。

可见,RDMA_CM API中,把四种常见的操作分割开来,而在Infiniband VERBS API中,SEND/READ/WRITE都是通过ibv_post_send()实现,而RECEIVE是通过ibv_post_recv()实现。

注意,无论是send/recv还是read/write,直观上看是数据直接发送,但其实是这些操作会对应一个注册过的内存,和RDMA直接交互,由RDMA直接读取和写入数据,避免应用内存和内核内存进行数据拷贝带来的开销。
比如send,发送的数据所在内存是已经注册过的,RNIC异步调度轮到相应的WQE的时候,发现是SEND操作,就会直接和注册过的内存交互,把数据发送出去。
比如recv,接收的缓冲区是注册过的,当RNIC异步调度轮到对应的WQE的时候,发现是RECV操作,RDMA就会直接把数据存到相应的地址中。
比如write,发送的数据所在的内存是已经注册过的,RNIC直接和这块内存交互,把数据写到服务器端的目的地址。
比如read,用于存储数据的接收内存是已经注册过的,RNIC直接通过网络交互,把服务器的数据直接拿过来,存到接收内存。
相当于说,通信过程需要操作的缓冲区都是注册过的,意味着可以和RDMA设备即RNIC进行直接数据交互,这样就提高了通信效率,降低延迟。send/recv和read/write操作中,都是RNIC直接和注册过的内存进行数据交互,低延迟、高吞吐量、低CPU占用率。

原文链接:https://blog.csdn.net/upupday19/article/details/79377984

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RDMA HCA/TCA是一种高速网络适配器,它使用RDMA技术来提高数据传输的效率和性能。HCA代表Host Channel Adapter,而TCA代表Target Channel Adapter。HCA通常安装在主机上,而TCA通常安装在存储设备上。这两种适配器都支持RDMA技术,可以通过RDMA协议进行高速数据传输。 RDMA技术是一种零拷贝技术,它可以直接在内存中传输数据,而不需要将数据从内存复制到网络适配器的缓冲区中。这种技术可以显著提高数据传输的效率和性能,减少CPU的负载,降低网络延迟和网络拥塞。 RDMA HCA/TCA通常使用InfiniBand或者RoCE(RDMA over Converged Ethernet)网络来进行高速数据传输。这些网络可以提供非常低的延迟和高的带宽,适用于高性能计算、云计算、大数据分析等领域。 以下是一个使用RDMA Write with Immediate Data的例子: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <infiniband/verbs.h> #define MSG_SIZE 1024 #define RDMA_BUF_SIZE 1024 struct rdma_context { struct ibv_context *ctx; struct ibv_pd *pd; struct ibv_mr *mr; struct ibv_cq *cq; struct ibv_qp *qp; struct ibv_comp_channel *comp_channel; struct ibv_port_attr port_attr; char *rdma_buf; uint32_t rkey; uint64_t remote_addr; }; int main(int argc, char *argv[]) { struct rdma_context ctx; struct ibv_device **dev_list; struct ibv_device *ib_dev; struct ibv_qp_init_attr qp_init_attr; struct ibv_qp_attr qp_attr; struct ibv_wc wc; int num_devices; int ret; int i; /* 获取IB设备列表 */ dev_list = ibv_get_device_list(&num_devices); if (!dev_list) { perror("ibv_get_device_list"); return -1; } /* 选择第一个IB设备 */ ib_dev = dev_list[0]; if (!ib_dev) { fprintf(stderr, "No IB devices found\n"); return -1; } /* 打开IB设备 */ ctx.ctx = ibv_open_device(ib_dev); if (!ctx.ctx) { perror("ibv_open_device"); return -1; } /* 创建PD */ ctx.pd = ibv_alloc_pd(ctx.ctx); if (!ctx.pd) { perror("ibv_alloc_pd"); return -1; } /* 分配内存 */ ctx.rdma_buf = malloc(RDMA_BUF_SIZE); if (!ctx.rdma_buf) { perror("malloc"); return -1; } /* 注册内存 */ ctx.mr = ibv_reg_mr(ctx.pd, ctx.rdma_buf, RDMA_BUF_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE); if (!ctx.mr) { perror("ibv_reg_mr"); return -1; } /* 创建CQ */ ctx.cq = ibv_create_cq(ctx.ctx, 1, NULL, NULL, 0); if (!ctx.cq) { perror("ibv_create_cq"); return -1; } /* 创建QP */ memset(&qp_init_attr, 0, sizeof(qp_init_attr)); qp_init_attr.send_cq = ctx.cq; qp_init_attr.recv_cq = ctx.cq; qp_init_attr.qp_type = IBV_QPT_RC; qp_init_attr.cap.max_send_wr = 1; qp_init_attr.cap.max_recv_wr = 1; qp_init_attr.cap.max_send_sge = 1; qp_init_attr.cap.max_recv_sge = 1; ctx.qp = ibv_create_qp(ctx.pd, &qp_init_attr); if (!ctx.qp) { perror("ibv_create_qp"); return -1; } /* 修改QP状态 */ memset(&qp_attr, 0, sizeof(qp_attr)); qp_attr.qp_state = IBV_QPS_INIT; qp_attr.pkey_index = 0; qp_attr.port_num = 1; qp_attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE; ret = ibv_modify_qp(ctx.qp, &qp_attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS); if (ret) { perror("ibv_modify_qp"); return -1; } /* 获取端口属性 */ ret = ibv_query_port(ctx.ctx, 1, &ctx.port_attr); if (ret) { perror("ibv_query_port"); return -1; } /* 创建Completion Channel */ ctx.comp_channel = ibv_create_comp_channel(ctx.ctx); if (!ctx.comp_channel) { perror("ibv_create_comp_channel"); return -1; } /* 将CQ绑定到Completion Channel */ ret = ibv_req_notify_cq(ctx.cq, 0); if (ret) { perror("ibv_req_notify_cq"); return -1; } /* 等待CQ事件 */ ret = ibv_get_cq_event(ctx.comp_channel, &ctx.cq, &ctx.ctx); if (ret) { perror("ibv_get_cq_event"); return -1; } /* 请求下一个CQ事件 */ ret = ibv_req_notify_cq(ctx.cq, 0); if (ret) { perror("ibv_req_notify_cq"); return -1; } /* 获取远程节点的rkey和地址 */ ctx.rkey = 0x12345678; ctx.remote_addr = 0xdeadbeef; /* 向远程节点发送数据 */ memset(ctx.rdma_buf, 0, RDMA_BUF_SIZE); strcpy(ctx.rdma_buf, "Hello RDMA!"); struct ibv_send_wr wr, *bad_wr; struct ibv_sge sge; memset(&wr, 0, sizeof(wr)); wr.wr_id = 0; wr.opcode = IBV_WR_RDMA_WRITE_WITH_IMM; wr.send_flags = IBV_SEND_SIGNALED; wr.imm_data = 0x1234; wr.wr.rdma.remote_addr = ctx.remote_addr; wr.wr.rdma.rkey = ctx.rkey; wr.sg_list = &sge; wr.num_sge = 1; sge.addr = (uintptr_t)ctx.rdma_buf; sge.length = strlen(ctx.rdma_buf) + 1; sge.lkey = ctx.mr->lkey; ret = ibv_post_send(ctx.qp, &wr, &bad_wr); if (ret) { perror("ibv_post_send"); return -1; } /* 等待发送完成 */ do { ret = ibv_poll_cq(ctx.cq, 1, &wc); if (ret < 0) { perror("ibv_poll_cq"); return -1; } } while (ret == 0); /* 检查发送状态 */ if (wc.status != IBV_WC_SUCCESS) { fprintf(stderr, "Send failed with status %d\n", wc.status); return -1; } /* 关闭QP */ ret = ibv_destroy_qp(ctx.qp); if (ret) { perror("ibv_destroy_qp"); return -1; } /* 关闭Completion Channel */ ret = ibv_destroy_comp_channel(ctx.comp_channel); if (ret) { perror("ibv_destroy_comp_channel"); return -1; } /* 关闭CQ */ ret = ibv_destroy_cq(ctx.cq); if (ret) { perror("ibv_destroy_cq"); return -1; } /* 注销内存 */ ret = ibv_dereg_mr(ctx.mr); if (ret) { perror("ibv_dereg_mr"); return -1; } /* 释放内存 */ free(ctx.rdma_buf); /* 释放PD */ ret = ibv_dealloc_pd(ctx.pd); if (ret) { perror("ibv_dealloc_pd"); return -1; } /* 关闭IB设备 */ ret = ibv_close_device(ctx.ctx); if (ret) { perror("ibv_close_device"); return -1; } /* 释放IB设备列表 */ ibv_free_device_list(dev_list); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值