RDMA Shared Receive Queue(四)

参考知乎文章《RDMA之Shared Receive Queue》:https://zhuanlan.zhihu.com/p/279904125

SRQ

SRQ全称为Shared Receive Queue,即共享接受队列。在QP中,SQ用于下发SEND/WRITE/READ等操作,而RQ只用于下发RECV操作,对于本端来说,只有QP接收到对端发过来的Send报文和带立即数的报文(少量)才会消耗RWQE(RQ中的Post Receive WR),在实际使用中,SEND/RECV操作通常都是用于传递控制信息,WRITE和READ才是进行大量远端内存读写操作时的主角,所以自然SQ的使用率是远远高于RQ的。

所以,协议为了给接收端节省资源而设计了SRQ,不同的QP使用一个特殊的RQ,这个特殊的RQ就是SRQ。当与其关联的QP想要下发接收WQE时,都填写到这个SRQ中,然后每当硬件接收到数据后,就根据SRQ中的下一个RWQE的内容把数据存放到指定位置。

例如下图所示,左侧未非共享RQ形态,右侧为共享SRQ形态。

  • 对于非共享RQ形态,当QP2接收到send报文,将从QO2的RQ中拿下一个RWQE处理,当QP3接收到send报文,将从QO3的RQ中拿下一个RWQE处理。
  • 对于共享SRQ形态,当QP2接收到send报文,将从SRQ中拿下一个RWQE处理,当QP3接收到send报文,也将从SRQ中拿下一个RWQE处理。
    在这里插入图片描述

为什用SRQ?(本小节参考知乎文章

使用SRQ最大好处就是节省资源,先引用一下协议中关于使用SRQ的说明(协议1.4版本 10.2.9.1章节):

Without SRQ, an RC, UC or UD Consumer must post the number of receive WRs necessary to handle incoming receives on a given QP. If the Consumer cannot predict the incoming rate on a given QP, because, for example, the connection has a bursty nature, the Consumer must either: post a sufficient number of RQ WRs to handle the highest incoming rate for each connection, or, for RC, let message flow control cause the remote sender to back off until local Consumer posts more WRs.

Either approach is inefficient:

​ • Posting sufficient WRs on each QP to hold the possible incoming rate, wastes WQEs, and the associated Data Segments, when the Receive Queue is inactive. Furthermore, the HCA doesn’t provide a way of reclaiming these WQEs for use on other connections.

​ • Letting the RC message flow control cause the remote sender to back off can add unnecessary latencies, specially if the local Consumer is unaware that the RQ is starving.

A Shared Receive Queue solves this problem by allowing multiple Queue Pairs to share Receive Work Requests and, more importantly, the Data Segments associated with Receive Work Requests. When an incoming Receive Message arrives on any QP that is associated with an SRQ, the HCA uses the next available SRQ WQE to receive the incoming data. The HCA returns Work Completions through the Completion Queue that is associated with the QP that received the incoming Send operation.

简单来说,就是没有SRQ的情况下,因为RC/UC/UD的接收方不知道对端什么时候会发送过来多少数据,所以必须做好最坏的打算,做好突发性收到大量数据的准备,也就是向RQ中下发足量的接收RWQE;另外RC服务类型可以利用流控机制来反压发送方,也就是告诉对端”我这边RQ WQE不够了“,这样发送端就会暂时放缓或停止发送数据。

但是正如前文所说,第一种方法由于是为最坏情况准备的,大部分时候有大量的RQ WQE处于空闲状态未被使用,这对内存是一种极大地浪费;第二种方法虽然不用下发那么多RQ WQE了,但是流控是有代价的,即会增加通信时延。

而SRQ通过允许很多QP共享接收WQE(以及用于存放数据的内存空间)来解决了上面的问题。当任何一个QP收到Send报文后(或带立即数的操作),硬件会从SRQ中取出一个WQE,根据其内容存放接收到的数据,然后硬件通过Completion Queue来返回接收任务的完成信息给对应的上层用户,这样上层用户可以知道哪个QP接收到了数据。

来看一下使用SRQ比使用普通的RQ可以节省多少内存:

假设接受数据的节点上有N对QP,并且每个QP都可能在随机的时间收到连续的M个消息(每个消息都需要消耗一个RQ中的RWQE)。

  • 如果不使用SRQ的话,用户一共需要下发N * M个RQ WQE。
  • 如果使用SRQ的话,用户只需要下发K * M个RQ WQE,而K远小于N。

这个K是可以由用户根据业务来配置的,如果存在大量的并发接收的情况,那么就把K设置大一点,否则K设置成个位数就足够应付一般的情况了。

这样一共节省了(N - K) * M个RQ WQE,RQ WQE本身其实不是很大,大约在几个KB的样子,看起来好像占不了多少内存,但是,真正节省的是用于存放数据的内存空间,用下图来说明:
在这里插入图片描述

上图中的SRQ中有两个RQ WQE,可以看一下RQ WQE的内容,它们是由数个sge(Scatter/Gather Element)组成的,每个sge由一个内存地址,长度和秘钥组成。有了起始地址和长度,sge就可以指向一块连续的内存区域,那么多个sge就可以表示多个彼此离散的连续内存块,我们称多个sge为sgl(Scatter/Gather List)。sge在IB软件协议栈中随处可见(其实在整个Linux都很常见),可以用非常少的空间表示非常大的内存区域,IB的用户都使用sge来指定发送和接收区域的。

可以简单估算下每个sge可以指向多大的内存区域,length是一个32bit的无符号整型,可以表示4GB的空间。假设一个RQ WQE最大可以存放256个sge,那么一个RQ WQE一共就是1TB。当然实际上不可能这么大,这里只是想直观的告诉读者RQ WQE背后可能占用着多大的内存空间。

SRQC

即SRQ Context。同QPC(或RQC)一样,SRQC是用来告知硬件跟SRQ有关的属性的,包括SRQ深度、WQE大小、SRQ buf addr等信息,此外,还必须拥有另外一个字段——limit水线字段。

SRQN

即SRQ Number。同QP一样,每个节点中可能存在多个SRQ,为了标识和区分这些SRQ,每个SRQ都有一个序号,称为SRQN。需要注意,SRQN与QPN是分别管理的,实际使用时,一般通过QPN找到SRQN,再找到SRQC。

SRQ事件相关

以下是与SRQ相关的可能发生的事件的描述。对于这些事件,字段event->element.srq包含收到此异步事件的SRQ的句柄。这些事件仅会在此SRQ所属的代码上下文中生成。

IBV_EVENT_SRQ_LIMIT_REACHED

当一个SRQ被启用并且其中的RWQE数量下降到该SRQ的限制值(limit水线)以下时,将生成此事件。在生成此事件时,硬件会将该SRQ的限制值将被设置为零,以防止重复上报事件。用户收到该事件后,需要向SRQ中下发新的的RWQE,如果需要的话,再通过Modify_srq接口重新设置限制值(limit水线)。
在这里插入图片描述

因为SRQ是多个QP共享的,所以如果深度比较小的情况下,很有可能突然里面的WQE就用完了。所以协议设计了这种机制,来保证用户能够及时干预WQE不够的情况。当然用户可以不使用这个机制,只需要将SRQ Limit的值设为0即可。

IBV_EVENT_SRQ_ERR

当共享接收队列(SRQ)上发生错误时,会生成此事件,该错误会阻止 RDMA 设备从 SRQ 中出列 RWQE 并报告接收完成。 当 SRQ 遇到此错误时,与此 SRQ 关联的所有 QP 都将转换为 IBV_QPS_ERR 状态,并且将为它们生成IBV_EVENT_QP_FATAL 异步事件。任何已转换到错误状态的 QP 必须将其状态修改为 Reset 以进行恢复。

IBV_EVENT_QP_LAST_WQE_REACHED

当与 SRQ 相关联的 QP 由 RDMA 设备自动或由用户明确转换为 IBV_QPS_ERR 状态时,生成该事件。这可能是因为最后一个 WQE 生成了错误的完成,或者 QP 转换为 IBV_QPS_ERR 状态,并且 QP 的接收队列(SRQ)上没有更多的 WQE。 此事件实际上意味着此 QP 将不再从 SRQ 消耗 WQE。 如果 QP 发生错误且未生成此事件,则用户必须销毁与此 SRQ 关联的所有 QP 以及 SRQ 本身,以便回收与违规 QP 关联的所有 WQE。至少,处于错误状态的 QP 必须将其状态更改为 Reset 以进行恢复。

RQ与SRQ差异

PD

RQ是属于QP的,所以RQ的PD就是QP的PD,这就要求:RQ下发RWQE所使用的MR必须与QP同属一个PD,如下图。
在这里插入图片描述

但是SRQ只需要保证其RWQE使用的MR与SRQ同属一个PD即可,也就是说,SRQ与QP可以是不同PD,如下图所示。
在这里插入图片描述

CQ

虽然SRQ关联的很多QP,但每个QP消耗RWQE所产生的CQE,还是放在RQ所关联的CQ中,如下图所示。
在这里插入图片描述

差异汇总

汇总一下差异

RQSRQ
stateRQ state
0x0: Reset
0x1: Initiate
0x2: Ready
0x3: Error
SRQ State
0000: good
0001: Error
RWQE不足无通知SRQ Limit event
通过设定一个水线告诉用户RWQE不足,一旦通知过,则硬件需要将水线设置为0,防止一直上报事件,当下发足够RWQE后需要使用Modify SRQVerb接口重新设置水线,此外,通过Modify SRQVerb接口也可以调整水线,但不能低于当前已下发的RWQE个数

硬件通过async event上报这个事件,软件通过 ibv_get_async_event 接口捕获事件, 事件类型为 IBV_EVENT_SRQ_LIMIT_REACHED

注意,这要求硬件给SRQ CTX中增加一个limit水线字段,用户可以通过接口查询或修改limit水线,此外,应保证修改水线后必须下发足够的RWQE才能使水线生效
CQ每个RQ关联一个CQ,产生CQE有序SRQ不关联CQ(XRC会关联),当QP收到send就从SRQ中取RWQE,并将CQE送到RQ关联的CQ中。不同QP之间无序,同一QP有序
进入错误状态冲刷RWEQ,可通过RESET重置QP,进入错误状态的RQ将无法下发RWQE任何要使用SRQ的QP被强制进入错误状态,只能删除所有关联QP后摧毁SRQ,需要注意,SRQ进入错误状态还可以下发RWQE,但无法使用,此外,任何试图查询或修SRQ的操作都应该失败
QP进入错误状态冲刷RWEQ,可通过RESET重置QP只冲刷已经从SRQ出队的RWQE,也就是QP正在使用的RWEQ,其他还在RWEQ的保持不动
摧毁限制RQ中RWQE冲刷完即可摧毁所有关联的QP必须先进入错误状态,之后等QP排空即可摧毁QP,等所有QP摧毁后,才可以摧毁SRQ
与SRQ关联的QP如果需要通过modify接口重置QP,必须先让QP进入错误状态之后,才可以重置QP摧毁限制
PD校验RQ访问的MR必须与QP处于同一个PDSRQ访问的MR必须与SRQ处于同一PD,而SRQ与QP可以处于不同PD
numberRQ与QP共用一个qpn单独拥有srqn
doorbellUAR中存在和RQ一起使用(通过某个bit区分)
流控有流控(credit)无流控

接口

ibv_create_srq

struct ibv_srq *ibv_create_srq(struct ibv_pd *pd, struct ibv_srq_init_attr *srq_init_attr)

输入参数:
pd 				与共享接收(SRQ)关联的保护域
srq_init_attr 	创建 SRQ 所需的初始属性列表

输出参数:
ibv_srq__attr 	设置结构的实际值

返回值:
指向创建的 SRQ 的指针或失败时的 NULL

ibv_create_srq创建一个共享接收队列(SRQ)。 读取srq_attr-> max_wr和srq_attr-> max_sge以确定所需的SRQ大小,并将其设置为返回时分配的实际值。 如果ibv_create_srq成功,那么max_wr和max_sge将至少与请求的值一样大。

struct ibv_srq定义如下:
struct ibv_srq {
    struct ibv_context      *context;       struct ibv_context from ibv_open_device
    void                    *srq_context;
    struct ibv_pd           *pd;            Protection domain
    uint32_t                handle;
    pthread_mutex_t         mutex;
    pthread_cond_t          cond;
    uint32_t                events_completed;
}

struct ibv_srq_init_attr
{
    void *srq_context;
    struct ibv_srq_attr attr;
};

struct ibv_srq_attr
{
    uint32_t    max_wr;
    uint32_t    max_sge;  //重点!!!!!!!!!sge个数可能大于1,特别是在spdk中
    uint32_t    srq_limit; //常常设为0,由上层软件自己管理,比如spdk
};

ibv_modify_srq

int ibv_modify_srq (struct ibv_srq *srq, struct ibv_srq_attr *srq_attr, int srq_attr_mask)

输入参数:
srq				要修改的 SRQ
srq_attr		指定要修改的 SRQ(输入) /返回所选 SRQ 属性的当前值(输出)
srq_attr_mask 	用于指定要修改哪些 SRQ 属性的位掩码

输出参数:
srq_attr 		返回带有更新值的 struct ibv_srq_attr

返回值:
成功时为 0
错误时为-1,如果调用失败,则将 errno 设置为指示失败的原因。
    
struct ibv_srq_attr {
	uint32_t		max_wr;
	uint32_t		max_sge;
	uint32_t		srq_limit;
};

ibv_modify_srq使用srq_attr中的属性值根据掩码srq_attr_mask修改SRQ srq的属性。 srq_attr是ibv_create_srq下面定义的ibv_srq_attr结构。 参数srq_attr_mask指定要修改的SRQ属性。 它是一个或多个标志的0或按位OR:

IBV_SRQ_MAX_WR //调整 SRQ 的大小
IBV_SRQ_LIMIT //设置 SRQ 限制

如果要修改的任何属性无效,则不会修改任何属性。 此外,并非所有设备都支持调整SRQ的大小。 要检查设备是否支持调整大小,请检查设备能力标志中是否设置了IBV_DEVICE_SRQ_RESIZE位。

一旦SRQ中的WR数降到SRQ限制以下,修改SRQ限制就会使SRQ产生一个 IBV_EVENT_SRQ_LIMIT_REACHED 异步事件。

ibv_destroy_srq

int ibv_destroy_srq(struct ibv_srq * srq)

输入参数:
srq 		要摧毁的SRQ

输出参数:
没有

返回值:
成功时为 0
错误时为 -1,如果调用失败,则将 errno 设置为指示失败的原因。

摧毁SRQ时,必需保证所有关联QP已经解绑。此外,摧毁QP前,需要将QP状态更改的ERROR状态,之后才能摧毁。

ibv_query_srq

int ibv_query_srq(struct ibv_srq * srq, struct ibv_srq_attr *srq_attr)

输入参数:
srq 		指定的 SRQ
srq_attr 	指定 SRQ 的属性

输出参数:
srq_attr 要查询的 SRQ 的属性, 返回 struct ibv_srq_attr

返回值:
成功时为 0
错误时为-1,如果调用失败,则将 errno 设置为指示失败的原因。
    
struct ibv_srq_attr {
	uint32_t		max_wr;
	uint32_t		max_sge;
	uint32_t		srq_limit;
};

ibv_query_srq 返回指定 SRQ 的属性列表和当前值。它通过指针 srq_attr 返回属性,该指针是 ibv_create_srq 中描述的 ibv_srq_attr 结构。如果srq_attr 中 srq_limit 的值为 0,表示当前无水线,不会生 IBV_EVENT_SRQ_LIMIT_REACHED 事件。

ibv_post_srq_recv

int ibv_post_srq_recv(struct ibv_srq * srq, struct ibv_recv_wr *recv_wr, struct ibv_recv_wr ** bad_recv_wr)

输入参数:
srq 			SRQ的句柄,包含来自 ibv_create_qp 的结构体 ibv_qp
wr 				包含接受缓冲区的第一个工作请求(wr)

输出参数:
bad_recv_wr		执行出错时,指向的错误 wr

返回值:
成功时为 0
错误时为 -1,如果调用失败,则将 errno 设置为指示失败的原因。

ibv_post_srq_recv 将工作请求列表发布到指定的 SRQ。它会在第一次失败时停止处理此列表中的 WR(可以在发布请求时立即检测到),并通过 bad_recv_wr参数返回此失败的 WR。 只有在 WR 完全执行请求并从相应的完成队列(CQ)检 索到工作完成后,才能安全地重用 WR 使用的缓冲区。 如果将 WR 发布到 UDQP,则传入消息的全局路由头(GRH)将放置在分散列表中的缓冲区的前 40 个字节中。如果传入消息中不存在 GRH,则前 40 个字节将是未定义的。这意味着在 UD QP 的所有情况下,传入消息的实际数据将以 40 字节的偏移量开始进入分散列表中的缓冲区。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值