9. RDMA之Queue Pair

  • Post author:
  • Post category:其他

转自:https://zhuanlan.zhihu.com/p/195757767

Queue Pair

        我们曾经在3. RDMA基本元素一文中简单的介绍了QP的概念,本文将更深入的讲解一些关于QP的细节。

基本概念回顾

        首先我们来简单回顾下关于QP的基础知识:

        根据IB协议中的描述,QP是硬件和软件之间的一个虚拟接口。QP是队列结构,按顺序存储着软件给硬件下发的任务(WQE),WQE中包含从哪里取出多长的数据,并且发送给哪个目的地等等信息。

QP的概念

        每个QP间都是独立的,彼此通过PD隔离,因此一个QP可以被视为某个用户独占的一种资源,一个用户也可以同时使用多个QP。

        QP有很多种服务类型,包括RC、UD、RD和UC等,所有的源QP和目的QP必须为同一种类型才能进行数据交互。

        虽然IB协议将QP称为“虚拟接口”,但是它是有实体的:

  • 硬件上,QP是一段包含着若干个WQE的存储空间,IB网卡会从这段空间中读取WQE的内容,并按照用户的期望去内存中存取数据。至于这个存储空间是内存空间还是IB网卡的片内存储空间,IB协议并未做出限制,每个厂商有各自的实现
  • 软件上,QP是一个由IB网卡的驱动程序所维护的数据结构,其中包含QP的地址指针以及一些相关的软件属性。

QPC

5. RDMA基本服务类型一文中,我们曾经提到过QPC全称是Queue Pair Context,用于存储QP相关属性。驱动程序里面是有储存QP的软件属性的,既然我们可以在软件里储存QP的属性,为什么还要用使用QPC呢?

        这是因为QPC主要是给硬件看的,也会用来在软硬件之间同步QP的信息

        我们说过QP在硬件上的实体只是一段存储空间而已,硬件除了知道这段空间的起始地址和大小之外一无所知,甚至连这个QP服务类型都不知道。还有很多其他的重要信息,比如某个QP中包含了若干个WQE,硬件怎么知道有多少个,当前应该处理第几个呢?

        所有上述的这些信息,软件是可以设计一定的数据结构并为其申请内存空间的,但是软件看到的都是虚拟地址,这些内存空间在物理上是离散的,硬件并不知道这些数据存放到了哪里。所以就需要软件通过操作系统提前申请好一大片连续的空间,即QPC来承载这些信息给硬件看。网卡及其配套的驱动程序提前约定好了QPC中都有哪些内容,这些内容分别占据多少空间,按照什么顺序存放。这样驱动和硬件就可以通过通过QPC这段空间来读写QP的状态等等信息。

QPC的概念

        如上图所示,硬件其实只需要知道QPC的地址0x12350000就可以了,因为它可以解析QPC的内容,从而得知QP的位置,QP序号,QP大小等等信息。进而就能找到QP,知道应该取第几个WQE去处理。不同的厂商可能实现有些差异,但是大致的原理就是这样。

        IB软件栈中还有很多Context的概念,除了QPC之外,还有Device Context,SRQC,CQC,EQC(Event Queue Context,事件队列上下文)等,它们的作用与QPC类似,都是用来在记录和同步某种资源的相关属性。

QP Number

        简称为QPN,就是每个QP的编号。IB协议中规定用24个bit来表示QPN,即每个节点最大可以同时使用 224 个QP,这已经是一个很大的数量了,几乎不可能用完。每个节点都各自维护着QPN的集合,相互之间是独立的,即不同的节点上可以存在编号相同的QP。

        QPN的概念本身非常简单,但是有两个特殊的保留编号需要额外注意一下:

QP0

        编号为0的QP用于子网管理接口SMI(Subnet Management Interface),用于管理子网中的全部节点,说实话我也还没搞清楚这个接口的作用,暂且按下不表。

QP1

        编号为1的QP用于通用服务接口GSI(General Service Interface),GSI是一组管理服务,其中最出名的就是CM(Communication Management),是一种在通信双方节点正式建立连接之前用来交换必须信息的一种方式。其细节将在后面的文章中专门展开介绍。

        这也就是我们之前的文章画的关于QP的图中,没有出现过QP0和QP1的原因了。这两个QP之外的其他QP就都是普通QP了。用户在创建QP的时候,驱动或者硬件会给这个新QP分配一个QPN,一般的QPN都是2、3、4这样按顺序分配的。当QP被销毁之后,它的QPN也会被重新回收,并在合适的时候分配给其他新创建的QP。

用户接口

        我们从控制层面和数据层面来分类介绍用户接口,控制面即用户对某种资源进行某种设置,一般都是在正式收发数据之前进行;而数据面自然就是真正的数据收发过程中进行的操作。

控制面

        接触过算法的读者应该都了解,链表的节点涉及到“增、删、改、查”四个操作,链表的节点是一片内存区域,是一种软件资源。

“增”即向操作系统申请一片内存用来存放数据,系统将在内存中划分一块空间,并将其标记为“已被进程XX使用”,其他没有权限的进程将无法覆盖甚至读取这片内存空间。

“删”即通知操作系统,这片空间我不使用了,可以标记成“未使用”并给其它进程使用了。

“改”就是写,即修改这片内存区域的内容。

“查”就是读,即获取这片内存区域的内容。

QP作为RDMA技术中最重要的一种资源,在生命周期上与链表并无二致:

        这四种操作,其实就是Verbs(RDMA对上层应用的API)在控制面上对上层用户提供给用户的几个接口:

Create QP

创建一个QP的软硬件资源,包含QP本身以及QPC。用户创建时会写传入一系列的初始化属性,包含该QP的服务类型,可以储存的WQE数量等信息

Destroy QP

释放一个QP的全部软硬件资源,包含QP本身及QPC。销毁QP后,用户将无法通过QPN索引到这个QP。

Modify QP

修改一个QP的某些属性,比如QP的状态,路径的MTU等等。这个修改过程既包括软件数据结构的修改,也包括对QPC的修改。

Query QP

查询一个QP当前的状态和一些属性,查询到的数据来源于驱动以及QPC的内容。

这四种操作都有配套的Verbs接口,类似于ibv_create_qp()这种形式,我们编写APP时直接调用就可以了。更多关于对上层的API的细节,我们将在后面专门进行介绍。

数据面

数据面上,一个QP对上层的接口其实只有两种,分别用于向QP中填写发送和接收请求。这里的“发送”和“接收”并不是指的发送和接收数据,而是指的是一次通信过程的“发起方”(Requestor)和“接收方”(Responser)

在行为上都是软件向QP中填写一个WQE(对应用层来说叫WR),请求硬件执行一个动作。所以这两种行为都叫做“Post XXX Request”的形式,即下发XXX请求。

Post Send Request

再强调一下,Post Send本身不是指这个WQE的操作类型是Send,而是表示这个WQE属于通信发起方。这个流程中填写到QP中的WQE/WR可以是Send操作,RDMA Write操作以及RDMA Read操作等。

用户需要提前准备好数据缓冲区、目的地址等信息,然后调用接口将WR传给驱动,驱动再把WQE填写到QP中。

Post Receive Request

Post Recv的使用场景就相对比较少了,一般只在Send-Recv操作的接收端执行,接收端需要提前准备好接收数据的缓冲区,并将缓冲区地址等信息以WQE的形式告知硬件。

QP状态机

说到QP的状态,就不得不祭出下面这张图(取自IB协议10.3.1节):

QP的状态机

        所谓状态机,就是描述一个对象的不同状态,以及触发状态间跳转的条件。为一个对象设计状态机可以使这个对象的生命周期变得非常明确,实现上也会使得逻辑更加清晰。

        对于QP来说,IB规范也为其设计了几种状态,处于不同状态的QP的功能是有差异的,比如只有进入到Ready to Send状态之后,QP才能够进行Post Send数据操作。正常状态(绿色的)之间的状态转换都是由用户通过上文介绍的Modify QP的用户接口来主动触发的;而错误状态(红色的)往往是出错之后自动跳转的,当一个QP处于错误状态之后就无法执行正常的业务了,就需要上层通过Modify QP将其重新配置到正常状态上。

        上图中我们只关注QP的部分,EE(End-to-End Context)是专门给RD服务类型使用的一个概念,我们暂不涉及。我们通过Create QP接口来进入这个状态图,通过Destroy QP接口来离开这个状态图。

        QP有以下几种状态,我们仅介绍一下比较重要的点:

RST(Reset)

        复位状态。当一个QP通过Create QP创建好之后就处于这个状态,相关的资源都已经申请好了,但是这个QP目前什么都做不了,其无法接收用户下发的WQE,也无法接受对端某个QP的消息。

INIT(Initialized)

        已初始化状态。这个状态下,用户可以通过Post Receive给这个QP下发Receive WR,但是接收到的消息并不会被处理,会被静默丢弃;如果用户下发了一个Post Send的WR,则会报错。

RTR(Ready to Receive)

        准备接收状态。在INIT状态的基础上,RQ可以正常工作,即对于接收到的消息,可以按照其中WQE的指示搬移数据到指定内存位置。此状态下SQ仍然不能工作。

RTS(Ready to Send)

        准备发送状态。在RTR基础上,SQ可以正常工作,即用户可以进行Post Send,并且硬件也会根据SQ的内容将数据发送出去。进入该状态前,QP必须已于对端建立好链接。

SQD(Send Queue Drain)

        SQ排空状态。顾名思义,该状态会将SQ队列中现存的未处理的WQE全部处理掉,这个时候用户还可以下发新的WQE下来,但是这些WQE要等到旧的WQE全处理之后才会被处理。

SQEr(Send Queue Error)

        SQ错误状态。当某个Send WR发生完成错误(即硬件通过CQE告知驱动发生的错误)时,会导致QP进入此状态。

ERR(Error)

        即错误状态。其他状态如果发生了错误,都可能进入该状态。Error状态时,QP会停止处理WQE,已经处理到一半的WQE也会停止。上层需要在修复错误后再将QP重新切换到RST的初始状态。

总结

        本文先回顾了QP的一些重要基本概念,然后讲解了QPC、QPN等QP强相关的概念,最后介绍了用户操作QP常用的接口以及QP状态机,相信本文过后读者一定对QP有了更深的了解。

        其实作为RDMA的核心概念,QP的内容很多,本文难以全部囊括。我将在后面的文章中逐渐把相关的内容补全,比如QKey的概念将在后续专门介绍各种Key的文章中讲解。

        好了,本文就到这了,感谢阅读。预告下一篇文章将详细讲解CQ。

协议相关章节

3.5.1 10.2.4 QP的基本概念

10.3 QP 状态机

10.2.5 QP相关的软件接口

11.4 Post Send Post Recv