Featured image of post RDMA 之 Shared Receive Queue

RDMA 之 Shared Receive Queue

本文转载于知乎专栏:11. RDMA 之 Shared Receive Queue,作者:Savir。IB 协议通过 SRQ 的机制大大减小了接收端对内存容量的需求,本文主要介绍 SRQ 的原理以及跟 RQ 的异同。

# RDMA 之 Shared Receive Queue

本文欢迎非商业转载,转载请注明出处。

声明:仅用于收藏,便于阅读

Savir, 知乎专栏:11. RDMA 之 Shared Receive Queue

我们曾在 【3. RDMA 基本元素】 中简单介绍了 SRQ 的概念,本文将带大家了解更多关于 SRQ 的细节。

# 基本概念

# 什么是 SRQ

全称为 Shared Receive Queue,直译为共享接收队列。我们知道,RDMA 通信的基本单位是 QP,每个 QP 都由一个发送队列 SQ 和接收队列 RQ 组成。

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

2024-06-28_11_1

# 为什么要用 SRQ

通常情况下,我们向 SQ 中下任务的数量要远远超过向 RQ 中下发任务的数量。为什么呢?请先回忆一下哪些操作类型会用到 SQ,哪些又会用到 RQ。

SEND/WRITE/READ 都需要通信发起方向 SQ 中下发一个 WR,而只有和 SEND 配合的 RECV 操作才需要通信响应方下发 WR 到 RQ 中(带立即数的 Write 操作也会消耗 Receive WR,我们还没讲到)。而我们又知道,SEND-RECV 这一对操作通常都是用于传递控制信息,WRITE 和 READ 才是进行大量远端内存读写操作时的主角,所以自然 SQ 的使用率是远远高于 RQ 的。

每个队列都是有实体的,占用着内存以及网卡的片上存储空间。在商用场景下,QP 的数量是可能达到十万级甚至更高的,对内存容量提出了很高的要求,内存都是白花花的银子买的,SRQ 就是 IB 协议为了节省用户的内存而设计的一种机制。

来看一下协议中对为什么要使用 SRQ 的官方解释(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.

• 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.

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

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

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

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

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

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

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

我们一共节省了 (N - K) * M 个 RQ WQE,RQ WQE 本身其实不是很大,大约在几个 KB 的样子,看起来好像占不了多少内存。但是如前文所说,实际上节省的还有用于存放数据的内存空间,这可是很大一块内存了,我们用图来说明:

2024-06-28_11_2

上图中的 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 一样,SRQC 是用来告知硬件跟 SRQ 有关的属性的,包括深度、WQE 大小等信息,本文不再赘述了。

# SRQN

即 SRQ Number。同 QP 一样,每个节点中可能存在多个 SRQ,为了标识和区分这些 SRQ,每个 SRQ 都有一个序号,称为 SRQN。

# SRQ 的 PD

我们在 【7. RDMA 之 Protection Domain】 中介绍过 Protection Domain 的概念,它用来隔离不同的 RDMA 资源。每个 SRQ 都必须指定一个自己的 PD,可以跟自己关联的 QP 的 PD 相同,也可以不同;SRQ 之间也可以使用相同的 PD。

如果在使用 SRQ 的时候,收到了数据包,那么只有在要访问的 MR 和 SRQ 处于同一个 PD 下,才会正常接收这个数据包,否则会产生立即错误。

# 异步事件

我们在 【10. RDMA 之 Completion Queue】 一文中介绍过,IB 协议根据错误的上报方式将错误类型分为立即错误,完成错误和异步错误。其中的异步错误类似于中断/事件,所以我们有时候也称其为异步事件。每个 HCA 都会注册一个事件处理函数专门用来处理异步事件,收到异步事件后,驱动程序会对其进行必要的处理和进一步上报给用户。

关于 SRQ 有一个特殊的异步事件,用来及时通知上层用户 SRQ 的状态,即 SRQ Limit Reached 事件。

# SRQ Limit

SRQ 可以设置一个水线/阈值,当队列中剩余的 WQE 数量小于水线时,这个 SRQ 会就上报一个异步事件。提醒用户“队列中的 WQE 快用完了,请下发更多 WQE 以防没有地方接收新的数据”。这个水线/阈值就被称为 SRQ Limit,这个上报的事件就被称为 SRQ Limit Reached。

2024-06-28_11_3

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

上报异步事件之后,SRQ Limit 的值会被硬件重新设置为 0(应该是为了防止一直上报异步事件给上层)。当然用户可以不使用这个机制,只需要将 SRQ Limit 的值设为 0 即可。

# 用户接口

# 控制面

还是老四样——“增、删、改、查”:

  • 创建——Create SRQ

创建 SRQ 的时候,跟 QP 一样会申请所有 SRQ 相关的软硬件资源,比如驱动程序会申请 SRQN,申请 SRQC 的空间并向其中填写配置。创建 SRQ 时还必须指定每个 SRQ 的深度(能存放多少 WQE)以及每个 WQE 的最大 sge 数量。

  • 销毁——Destroy SRQ

销毁 SRQ 的所有相关软硬件资源。

  • 修改——Modify SRQ

除了 SRQ 深度等属性外,SRQ Limit 的值也是通过这个接口设置的。因为每次产生 SRQ Limit Reached 事件之后,水线的值都会被清零,所以每次都需要用户调用 Modify SRQ 重新设置水线。

  • 查询——Query SRQ

通常是用来查询水线的配置的。

# 数据面

# Post SRQ Receive

跟 Post Receive 一样,就是向 SRQ 中下发接收 WQE,里面包含了作为接收缓冲区的内存块的信息。需要注意的是,主语是 SRQ,与 QP 没有任何关系,现在用户是不关心这个 SRQ 被哪些 QP 关联的。

# SRQ 和 RQ 的区别

从功能上来说,SRQ 和 RQ 一样都是用来储存接收任务书的,但是由于 SRQ 的共享性,所以其和 RQ 有一些差异。

# 状态机

我们在 【9. RDMA 之 Queue Pair】 中介绍过,QP 有着复杂的状态机,不同的状态下 QP 的收发能力存在差异。而 SRQ 只有非错误和错误两种状态:

无论是哪种状态下,用户都可以向 SRQ 中下发 WQE,但是在错误状态下,相关联的 QP 不能从这个 SRQ 中获得收到的数据。另外在错误状态下,用户也无法查询和修改 SRQ 的属性。

QP 处于错误状态时,可以通过 Modify QP 来使其回到 RESET 状态,但是对 SRQ 来说,只能通过销毁它来退出错误状态。

# 接收流程

对于一个 QP 来说,RQ 和 SRQ 不能同时使用,两者需选其一,如果对一个已经关联 SRQ 的 QP 的 RQ 下发 WQE,那么会返回一个立即错误。

下面我们来对比看一下 SRQ 和 RQ 的接收流程。本小结的内容是本文的重点,相信读者看过之后,就对 SRQ 的机制有比较完整的了解了。

# RQ 的接收流程

首先,我们重温一下普通 RQ 的接收流程(结合发送端的完整流程请阅读 【4. RDMA 操作类型】 )一文):

  1. 创建 QP。

  2. 通过 Post Recv 接口,用户分别向 QP2 和 QP3 的 RQ 下发接收 WQE,WQE 中包含接收到数据后放到哪块内存区域的信息。

  3. 硬件收到数据。

  4. 硬件发现是发给 QP3 的,那么从 QP3 的 RQ 中取出 WQE1,将接收到的数据放到 WQE1 指定的内存区域。

  5. 硬件完成数据存放后,向 QP3 的 RQ 关联的 CQ3 产生一个 CQE,上报任务完成信息。

  6. 用户从 CQ3 中取出 WC(CQE),然后从指定内存区域取走数据。

  7. 硬件收到数据。

  8. 硬件发现是发送给 QP2 的,那么从 QP2 的 RQ 中取出 WQE1,将接收到的数据放到 WQE1 指定的内存区域。

  9. 硬件完成数据存放后,向 QP2 的 RQ 关联的 CQ2 产生一个 CQE,上报任务完成信息。

  10. 用户从 CQ2 中取出 WC(CQE),然后从指定内存区域取走数据。

2024-06-28_11_4

# SRQ 的接收流程

而 SRQ 的接收流程有一些区别:

  1. 创建 SRQ1,并创建 QP2 和 QP3,都关联到 SRQ1 上。

  2. 通过 Post SRQ Recv 接口,用户向 SRQ1 中下发两个接收 WQE,WQE 中包含接收到数据后放到哪块内存区域的信息。

  3. 硬件收到数据。

  4. 硬件发现是发给 QP3 的,从 SRQ1 中取出第一个 WQE(现在是 WQE1),根据 WQE 内容存放收到的数据。

SRQ 中的每个 WQE 是“无主的“,不关联到任何一个 QP,硬件按队列顺序依次取出 WQE 就把数据放到里面了。

  1. 硬件发现 QP3 的 RQ 关联的 CQ 是 CQ3,所以向其中产生一个 CQE。

  2. 用户从 CQ3 中取出 CQE,从指定内存区域取走数据。

细心地读者可能会问,用户下发 WR 时,每个 WR 都指定了一些未来用来存放数据的内存区域。但是 SRQ 是一个池子,里面每个 WQE 都指向了不同的若干段内存区域。用户收到某个 QP 对应的 CQ 中的 WC 后如何知道接收到的数据存放到哪里了呢?

WC 中其实有 wr_id 信息,告知用户数据放到哪个 WR(WQE)指定的内存区域了,既然 WR 是用户下发的,用户自然知道其指向的具体位置。

  1. 硬件收到数据

  2. 硬件发现是发给 QP2 的,从 SRQ1 中取出第一个 WQE(现在是 WQE2),根据 WQE 内容存放收到的数据。

  3. 硬件发现 QP2 的 RQ 关联的 CQ 是 CQ2,所以向其中产生一个 CQE。

  4. 用户从 CQ2 中取出 CQE,从指定内存区域取走数据。

2024-06-28_11_5

# 总结

本文首先介绍了 SRQ 的基本概念,然后是其设计初衷、相关机制和用户接口,最后对比 RQ 描述了 SRQ 的接收流程。在实际业务中,SRQ 的使用率还是蛮高的,希望读者能够深入理解。

就写到这里吧,感谢阅读。下一篇我将给大家介绍下 Memeory Window。

# 协议相关章节

  • 10.2.9 SRQ 的设计思想以及相关操作

  • 10.2.3 SRQ 和 QP 的 PD

  • 10.8.2 关联 SRQ 的 QP 和不使用 SRQ 的 QP 的关系

  • 10.8.5 SRQ 相关的返回 WC

  • 11.5.2.4 异步事件

# 其他参考资料


  1. Linux Kernel Networking - Implement and Theory. Chapter 13. Shared Receive Queue ↩︎

本博客已稳定运行
总访客数: Loading
总访问量: Loading
发表了 73 篇文章 · 总计 323.73k

使用 Hugo 构建
主题 StackJimmy 设计
基于 v3.27.0 分支版本修改