【RDMA】RDMA编程入门--编辑中
目录
四、RDMA通信和ceph AsyncMessenger的关系
作者: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。
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发送数据的流程如下:
-
首先,A和B都要创建并初始化好各自的QP,CQ
-
A和B分别向自己的WQ中注册WQE,对于A,WQ=SQ,WQE描述指向一个等到被发送的数据;对于B,WQ=RQ,WQE描述指向一块用于存储数据的buffer。
-
A的RNIC异步调度轮到A的WQE,解析到这是一个SEND消息,从buffer中直接向B发出数据。数据流到达B的RNIC后,B的WQE被消耗,并把数据直接存储到WQE指向的存储位置。
-
AB通信完成后,A的CQ中会产生一个完成消息CQE表示发送完成。与此同时,B的CQ中也会产生一个完成消息表示接收完成。每个WQ中WQE的处理完成都会产生一个CQE。
双边操作与传统网络的底层buffer pool类似,收发双方的参与过程并无差别,区别在零拷贝、kernel bypass,实际上传统网络中一些高级的网络SOC已经实现类似功能。
对于RDMA,这是一种复杂的消息传输模式,多用于传输短的控制消息。
单边
对于单边操作,以存储网络环境下的存储为例(A作为文件系统,B作为存储介质):
-
首先A、B建立连接,QP已经创建并且初始化。
-
数据被存档在A的buffer地址VA,注意VA应该提前注册到A的RNIC,并拿到返回的local key,相当于RDMA操作这块buffer的权限。
-
A把数据地址VA,key封装到专用的报文传送到B,这相当于A把数据buffer的操作权交给了B。同时A在它的WQ中注册进一个WR,以用于接收数据传输的B返回的状态。
-
B在收到A的送过来的数据VA和R_key后,RNIC会把它们连同存储地址VB到封装RDMA READ,这个过程A、B两端不需要任何软件参与,就可以将A的数据存储到B的VB虚拟地址。
-
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通信代码中可能不太关注这几个参数。