【RDMA】RDMA编程入门--编辑中

目录

一、前言

二、基本概念

1、队列和队列成员

2、传输模式

简介

单边双边传输流程简述

3、编程接口(verbs API)

三、编程示例

工作大致流程说明

编程代码实例

四、RDMA通信和ceph AsyncMessenger的关系

【RDMA】RDMA 学习资料总目录_bandaoyu的博客-CSDN博客SavirRDMA 分享1. RDMA概述https://blog.csdn.net/bandaoyu/article/details/112859853https://zhuanlan.zhihu.com/p/1388747382. 比较基于Socket与RDMA的通信https://blog.csdn.net/bandaoyu/article/details/1128613993. RDMA基本元素和编程基础https://blog.csdn.net/bandaoyu/article/de.https://blog.csdn.net/bandaoyu/article/details/120485737

作者:bandaoyu 本文随时更新,地址:https://blog.csdn.net/bandaoyu/article/details/125681856

一、前言

首先应该先了解RDMA:https://blog.csdn.net/bandaoyu/article/details/112859853
RDMA 学习资料总目录:https://blog.csdn.net/bandaoyu/article/details/120485737

本文所讲述的主要是IBoE的RDMA编程,主要是ROCE、iWrap,与IB可能略有不同。


二、基本概念

1、队列和队列成员

关键词:点对点通信、QP(SQ+RQ)、CQ、*QE


RDMA提供了基于消息队列的点对点通信,每个应用都可以直接获取自己的消息,无需OS和协议栈的介入。

队列:QP (Queue Pairs) 和CQ

RDMA提供了基于消息队列的点对点通信,当应用需要通信时,就会创建一条Channel连接,每条Channel的两端各有一对队列(Send Queue(SQ)和Receive Queue(RQ)),合称Queue Pairs(QP),除此之外,两端还需要一个 完成队列-Complete Queue(CQ),CQ用来存放 发送和接收请求处理完成的通知。

队列成员:SQE、RQE、CQE等*QE

队列里面的成员叫xxE,SQ里面的成员叫SQE,RQ里面的叫RQE,CQ里面的就是CQE……

而发送队列SQ和接收队列RQ,统称工作队列WQ (work Queue),所以里面单元又叫WQE;

SR(发送请求)和RR(接收请求)都是WR=工作请求;

每当网卡发送or接收完成一个请求,会在CQ产生一个CQE,知会用户消息已经被处理完。

总结

SQ:发送队列,SQ中的单元叫SQE;

RQ:接收队列,RQ中的单元叫RQE;

SQ和RQ都是WQ,就像男人和女人都叫人,WQ中的单元叫WQE;

SR(发送请求)和RR(接收请求)都是WR=工作请求;

QP=SQ+RQ

CQ:完成队列,CQ中的单元叫CQE;

2、传输模式

简介

RDMA 单边和双边两种传输模式。

SEND/RECEIVE:

是双边操作,每一次数据传输都需要双边参与。

READ和WRITE :

是单边操作,除了第一次握手(获得对端的内存地址),后续的数据传输,单端直接DMA读写对端的内存。

第一次握手获得对端接收数据的内存地址后,本端明确信息的源和目的地址,数据的读或存都通过远端的DMA在RNIC与应用buffer之间完成,再由远端RNIC封装成消息返回到本端。对端的CPU毫无知觉。(所以WRITE完成后,一般要再发一个消息去通知对端:我已经完成一次操作了,去查看CQ队列吧。然后对端就会去读CQ队列,拿出CQE解析或者本地把数据写到哪里了,它再去处理数据)

在实际中,SEND/RECEIVE多用于连接控制类报文,而数据报文多是通过READ/WRITE来完成的。

单边双边传输流程简述

双边


对于双边操作为例,A向B发送数据的流程如下:

首先,A和B都要创建并初始化好各自的QP,CQ

A 向SQ中放入一个发送请求(SQE),SQE描述指向一个等待被发送的数据的buffer地址 S_addr;

对于B,要提前向RQ中放入N个接收请求(RQE),RQE描述指向一块用于存储数据的buffer地址R_addr。

(SQ和RQ都是WQ,就像男人和女人都叫人,WQ中的单元叫WQE;所以SQE也称WQE)
(SQ和RQ都是WQ,就像男人和女人都叫人,WQ中的单元叫WQE;所以RQE、SQE也称WQE)

A的RNIC异步从A的SQ中读取SQE(WQE),读到刚才放入的SQ,解析到这是一个SEND消息,从S_addr中将数据直接DMA到B。数据流到达B的RNIC后,B从RQ中消耗掉一个RQE(WQE),从RQE中解析出存数据的地址R_addr,并把数据直接存储R_addr。

AB通信完成后,A的CQ中会产生一个完成消息CQE表示发送完成。与此同时,B的CQ中也会产生一个完成CQE消息表示接收完成。即每个WQ中WQE的处理完成都会产生一个CQE。

双边操作与传统网络的底层buffer pool类似,收发双方的参与过程并无差别,区别在零拷贝、kernel bypass。

对于RDMA,这是一种复杂的消息传输模式,多用于传输短的控制消息。

单边

以WRITE为例:

首先,A和B都要创建并初始化好各自的QP,CQ。

双方握手交换信息。(A的buffer地址VA告知了B,B的buffer地址VB告知了A)

A把内存地址VA,key封装到专用的报文传送到B,这相当于A把数据buffer的操作权交给了B。

B把内存地址VB,key封装到专用的报文传送到A,这相当于B把数据buffer的操作权交给了A。

A要向B“发送”数据:

A 向SQ中放入一个发送请求(SQE),SQE描述指向一个等待被发送的数据的buffer地址 S_addr;

A的RNIC异步从A的SQ中读取SQE(WQE),读到刚才放入的SQ,解析到这是一个WRITE消息,从S_addr中将数据直接DMA到B的内存地址VB(范围内的某个地址VB+xx)。数据流到达B的RNIC后,B从A的SQE中解析出存数据的地址VB+xx,并把数据直接存储VB+xx。

AB通信完成后,A的CQ中会产生一个完成消息CQE表示发送完成。

与此同时,B的CQ中是否也要产生CQE,取决于A发过来的WR中设定的条件(又是为了减少中断。A可能想发多个消息合并为一个CQ)。

这个过程A、B两端不需要任何软件(CPU\内核)参与,只有两个网卡“悄咪咪”的完成了将A的数据存储到B的VB虚拟地址。

3、编程接口(verbs API)

Verbs API

由OpenFabrics推动实现的一组RDMA应用编程接口(API)。地位相当于以太网编程的socket接口。传统以太网的用户,基于Socket API来编写应用程序;而RDMA的用户,基于Verbs API来编写应用程序。

Verbs API是RDMA最基本的软件接口,业界的RDMA应用,要么直接基于这组API编写,要么基于在Verbs API上又封装了一层接口的各种中间件编写。(如rdma_cm)

Verbs api 分为用户态Verbs接口和内核态Verbs接口,分别用于用户态和内核态的RDMA应用。 对于Linux系统来说,由rdma-core和内核中的RDMA子系统(如intel的irdma)提供,所以我们可以看到服务器安装intel 的RDMA网卡后,要使用RDMA网卡,需要安装rdma-core和irdma。

原文链接:https://blog.csdn.net/bandaoyu/article/details/113125244

rdma_cm

对于rdma编程,目前主流实现是利用rdma_cm来建立连接,然后利用verbs来传输数据。

rdma_cm和ibverbs分别会创建一个fd,这两个fd的分工不同。rdma_cm fd主要用于通知建连相关的事件,verbs fd则主要通知有新的cqe发生。当直接对rdma_cm fd进行poll/epoll监听时,此时只能监听到POLLIN事件,这意味着有rdma_cm事件发生。当直接对verbs fd进行poll/epoll监听时,同样只能监听到POLLIN事件,这意味着有新的cqe。

《rdma_cm和verbs的区别》原文链接:https://blog.csdn.net/bandaoyu/article/details/115668933

三、编程示例

工作大致流程说明

RDMA有两种通信模式 Read/Write和Send/Receive,此处以Write为例:

1、编程步骤1准备工作和资源初始化:

  • 获取设备列表

 ibv_get_device_list -->rocep216s0f0、rocep216s0f1 (RDMA网卡有多个网口)

  • 打开要请求的设备

 ibv_open_device(rocep216s0f0)

  • 查询端口

ibv_query_port(res.ib_ctx, 1, &res.port_attr)

  • 分配保护域以及您的资源   

res.pd = ibv_alloc_pd(res.ib_ctx)

  • 创建 CQ
res.cq = ibv_create_cq(res.ib_ctx, cq_size, NULL, NULL, 0);
  • 注册一个内存区域  (注册过之后产生key,把key交给对端,对端才能DMA该区域)
res.mr = ibv_reg_mr(res.pd, res.buf, size, mr_flags);
  • 创建QP
res.qp = ibv_create_qp(res.pd, &qp_init_attr);
  • 交换控制信息(交换QP的ID,各自的内存地址等)

可以通过 Socket 或者 RDMA_CM API 来交换控制信息,这里演示的是使用 Socket 交换信息。因为RDMA 是根据点到点的通信,RDMA没有IP的概念,只有QP的ID的概念,所以要通过 Socket 或者 RDMA_CM API 来交换控制信息告诉对方,本地的QP的ID---->LID 。

(LID的概念来自于IB,可以阅读本地了解LID的概念:http://t.csdn.cn/HBuXG

  • 转换 QP 状态

QP 创建后处于RESET重置状态,要经过切换到RTS:Ready To Send 状态后,才能正常工作,可以把这一步理解为QP的初始化。

  • RESET:重置状态,QP 刚创建时即处于 RESET 状态,此时不能在 QP 中添加发送请求或接收请求,所有入站消息都被默默丢弃
  • INIT:已初始化状态,此时不能添加发送请求,可以添加接收请求,但是请求不会被处理,所有入站消息都被默默丢弃。最好在QP处于这种状态时将接收请求加入到其中,再切换到 RTR 状态。这样可以避免发送消息的远程 QP 在需要使用接收请求时没有接收请求可用的情况发生。
  • RTR:Ready To Receive 状态,此时不能添加发送请求,但是可以添加并且处理接收请求,所有入站信息都将得到处理。在这种状态下收到的第一条消息,将触发异步事件「通信已建立」
  • RTS:Ready To Send 状态,此时可以添加和处理发送和接收请求,所有入站信息都将得到处理

2、编程步骤1 创建请求和执行发送和处理

发送端:

应用程序创建WR请求,既填充ibv_send_wr结构体。
调用ibv_post_send将WR放入QP的SQ中(成为WQE)

网卡做的事情:
RDMA网卡从QP的SQ中取出WQE,把WQE指定的数据DMA到WQE指定的对端的内存地址中。
RDMA网卡DMA完成后产生一个WC(work complete),放入CQ(成为CQE)

应用程序polling CQ(或者event通知),看到有CQE,就知道数据传输(出去)完成。

poll_result = ibv_poll_cq(res->cq, 1, &wc);

应用程序对poll_result  进行处理(解析处理,看发送成功or失败)


接收端:

 网卡做的事情:

RDMA网卡接收到对端RDMA传来的数据和WQE,根据WQE中指定的地址,把数据放到本地的内存上,接收完成后。生成一个WC,放到CQ中。

应用程序polling CQ(或者event通知),看到有CQE,就知道数据传输(到来)完成。

poll_result = ibv_poll_cq(res->cq, 1, &wc);

应用程序对poll_result  进行处理(读出来,解析,知道对端DMA过来的数据放在哪个内存地址,然后去读取)

编程代码实例

链接:《RDMA SEND/WRITE编程实例(IBV Verbs )》http://t.csdn.cn/4UwMw

          《RDMA 编程实例(rdma_cm API) 》http://t.csdn.cn/WPff3

四、RDMA通信和ceph AsyncMessenger的关系

Messenger::send_message(Message *m, dest)

AsyncMessenger::send_message(Message *m, dest)

--|AsyncMessenger::_send_message(Message *m, dest)

----|AsyncMessenger::submit_message(Message *m,conn,dest,...)

------|AsyncConnection::send_message(Message *m)

--------|out_q[priority].emplace_back(std::move(bl),m) #放入队列

//回调操作(write_handler= new C_handle_write(this))放入event中心

--------|EventCenter::dispatch_event_external(write_handler)

----------|external_event.push_back(write_handler)

//wakeup process_events 线程执行

----------|wakeup()

|

| w->center.process_events //执行process_events,取出前面放入event中心的event并处理

        | cb = event->read_cb; cb->do_request()

        |--|C_handle_write

//process_events处理(do_request)我们的让入的event,实际就是执行C_handle_write

| --|write_handler = new C_handle_write(this)

//所以就是执行:AsyncConnection::handle_write

C_handle_write::do_request(int fd)

--| AsyncConnection::handle_write()

----|bufferlist data;m=_get_next_outgoing(&data); #out_q

----|AsyncConnection::write_message(m,data,more)

------|AsyncConnection::outcoming_bl <--bl ------|AsyncConnection::_try_send(bool more)

--------|AsyncConnection::connectedSocket cs->send(outcoming_bl,more)

----------|connectedSocket::_csi->send(outcoming_bl,more)

/*_csi是std::unique_ptr<ConnectedSocketImpl> _csi;

_csi->send是_csi->sendConnectedSocketImpl:: virtual ssizet_t send(bl,more)是虚函数,RDMA模式的时候,其实现就是RDMAConnectedSocketImpl::send *(posix就调用socket的send,RDMA就调用RDMA的send)*/

//所以下面就是ceph AsyncMessenger网络模块的RDMA入口

----------|RDMAConnectedSocketImpl::send(outcoming_bl,more)#<---------RDMA send 入口

=====================================草稿================================

 基本概念

原理

RDMA提供了基于消息队列的点对点通信,每个应用都可以直接获取自己的消息,无需OS和协议栈的介入。

消息服务建立在通信双方本端和远端应用之间创建的channel-IO连接之上。

当应用需要通信时,就会创建一条Channel连接,每条Channel的首尾端点是两对Queue Pairs(QP),每对QP由Send Queue(SQ)和Receive Queue(RQ)构成,这些队列中管理着各种类型的消息。

QP会被映射到应用的虚拟地址空间,使得应用直接通过它访问RNIC。除了QP描述的两种基本队列之外,RDMA还提供一种队列-Complete Queue(CQ),CQ用来知会用户WQ上的消息已经被处理完。

RDMA提供了一套software transport interface,方便用户创建传输请求-Work Request(WR),WR中描述了应用希望传输到Channel对端的消息内容。

WR通知给QP中的某个队列-Work Queue(WQ)。

在WQ中,用户的WR被转化为Work Queue Ellement(WQE)的格式,等待RNIC的异步调度解析,并从WQE指向的buffer中拿到真正的消息发送到Channel对端。

传输

RDMA 的send/receive和read/write传输

传输

RDMA共有三种底层数据传输模式。

SEND/RECEIVE是双边操作,即必须要远端的应用感知参与才能完成收发。

READ和WRITE是单边操作,只需要本端明确信息的源和目的地址,远端应用不必感知此次通信,数据的读或存都通过远端的DMA在RNIC与应用buffer之间完成,再由远端RNIC封装成消息返回到本端。

在实际中,SEND/RECEIVE多用于连接控制类报文,而数据报文多是通过READ/WRITE来完成的。

双边

对于双边操作为例,A向B发送数据的流程如下:

  1. 首先,A和B都要创建并初始化好各自的QP,CQ

  2. A和B分别向自己的WQ中注册WQE,对于A,WQ=SQ,WQE描述指向一个等到被发送的数据;对于B,WQ=RQ,WQE描述指向一块用于存储数据的buffer。

  3. A的RNIC异步调度轮到A的WQE,解析到这是一个SEND消息,从buffer中直接向B发出数据。数据流到达B的RNIC后,B的WQE被消耗,并把数据直接存储到WQE指向的存储位置。

  4. AB通信完成后,A的CQ中会产生一个完成消息CQE表示发送完成。与此同时,B的CQ中也会产生一个完成消息表示接收完成。每个WQ中WQE的处理完成都会产生一个CQE。

双边操作与传统网络的底层buffer pool类似,收发双方的参与过程并无差别,区别在零拷贝、kernel bypass,实际上传统网络中一些高级的网络SOC已经实现类似功能。

对于RDMA,这是一种复杂的消息传输模式,多用于传输短的控制消息。

单边

对于单边操作,以存储网络环境下的存储为例(A作为文件系统,B作为存储介质):

  1. 首先A、B建立连接,QP已经创建并且初始化。

  2. 数据被存档在A的buffer地址VA,注意VA应该提前注册到A的RNIC,并拿到返回的local key,相当于RDMA操作这块buffer的权限。

  3. A把数据地址VA,key封装到专用的报文传送到B,这相当于A把数据buffer的操作权交给了B。同时A在它的WQ中注册进一个WR,以用于接收数据传输的B返回的状态。

  4. B在收到A的送过来的数据VA和R_key后,RNIC会把它们连同存储地址VB到封装RDMA READ,这个过程A、B两端不需要任何软件参与,就可以将A的数据存储到B的VB虚拟地址。

  5. B在存储完成后,会向A返回整个数据传输的状态信息。

单边操作传输方式是RDMA与传统网络传输的最大不同,提供直接访问远程的虚拟地址,无须远程应用的参与,这种方式适用于批量数据传输。

Verbs 的身世

RDMAC(RDMA Consortium)和IBTA(InfiniBand Trade Association)主导了RDMA,RDMAC是IETF的一个补充,它主要定义的是iWRAP和iSER,IBTA是infiniband的全部标准制定者,并补充了RoCE v1 v2的标准化。

应用和RNIC之间的传输接口层(software transport interface)被称为Verbs。

IBTA解释了RDMA传输过程中应具备的特性行为,而并没有规定Verbs的具体接口和数据结构原型。

这部分工作由另一个组织OFA(Open Fabric Alliance)来完成,OFA提供了RDMA传输的一系列Verbs API。

OFA开发出了OFED(Open Fabric Enterprise Distribution)协议栈,支持多种RDMA传输层协议。

OFED中除了提供向下与RNIC基本的队列消息服务,向上还提供了ULP(Upper Layer Protocols),通过ULPs,上层应用不需要直接到Verbs API对接,而是借助于ULP与应用对接,常见的应用不需要做修改,就可以跑在RDMA传输层上。

队列和队列成员

QP (Queue Pairs)

RDMA提供了基于消息队列的点对点通信,当应用需要通信时,就会创建一条Channel连接,每条Channel的两端是 各有一对Queue Pairs(QP)。每对QP由Send Queue(SQ)和Receive Queue(RQ)构成,除了QP之外,通信还需一种队列:完成队列--Complete Queue(CQ)。

队列里面的成员叫xxE,SQ里面的成员叫SQE,RQ里面的叫RQE,CQ里面的就是CQE……

而发送队列SQ和接收队列RQ,统称工作队列WQ (work Queue),所以里面单元又叫WQE;

SR(发送请求)和RR(接收请求)都是WR=工作请求;

每当网卡发送or接收完成一个请求,会在CQ产生一个CQE,知会用户消息已经被处理完。

总结:

SQ:发送队列,SQ中的单元叫SQE;

RQ:接收队列,RQ中的单元叫RQE;

SQ和RQ都是WQ,就像男人和女人都叫人,WQ中的单元叫WQE;

SR(发送请求)和RR(接收请求)都是WR=工作请求;

QP=SQ+RQ

CQ:完成队列,CQ中的单元叫CQE;

通信过程

(RDMA有两种通信模式 Read/Write和Send/Receive,此处以Write为例)

获取设备列表  ibv_get_device_list -->rocep216s0f0、rocep216s0f1

打开要请求的设备  ibv_open_device(rocep216s0f0)

……

创建QP、创建 CQ

交换控制信息(交换QP的ID,各自的内存地址等)

连接QP

发送端:

  • 应用程序创建WR请求,调用ibv_send_wr(send work request)
  • ibv_send_wr将WR放入QP的SQ中(成为WQE)
  • RDMA网卡从QP的SQ中取出WQE,把WQE指定的数据DMA到WQE指定的对端的内存地址中。
  • RDMA网卡DMA完成后产生一个WC(work complete),放入CQ(成为CQE)
  • 应用程序polling CQ(或者event通知),看到有CQE,就知道数据传输完成。

接收端:

     RDMA网卡接收到对端RDMA传来的数据和WQE,根据WQE中指定的地址,把数据放到本地的内存上,接收完成后。生成一个WC,放到CQ中。

因为不经过内核(CPU),所以内核完全不知道有数据到。

InfiniBand和IBoE(RoCE、iWARP)编程的差异

InfiniBand是真正的RDMA,有自己的链路层、网络层、传输层的协议栈,所以编程代码中的LRH 、 LID、L3 中的 GRH 都是为L1-L3服务。而RoCEv2、iWARP基于以太网实现RDMA ,L1-L3依赖于TCP,所以RoCEv2、iWARP通信代码中可能不太关注这几个参数。

posted on 2022-10-04 01:21  bdy  阅读(361)  评论(0编辑  收藏  举报

导航