【RDMA】17. RDMA之RDMAP(Remote Direct Memory Access Protocol)--未消化
内容概要
RDMAP的主要功能是提供RDMA语义,支撑上层用户的Send/RDMA Read/RDMA Write等请求。用户下发这些请求后,软硬件会根据其内容封装出RDMAP的报文发给对端,对端硬件再解析RDMAP报文执行对应的数据搬移操作。
RDMAP协议在iWARP协议栈中的位置
对上层用户的API接口,即之前我们介绍过的Verbs。目前iWARP跟RoCE和Infiniband一样使用libibverbs库提供的同一套接口。Verbs和协议栈的关系如下图所示:
Verbs API和iWARP协议栈的关系
正文
原文:17. RDMA之RDMAP(Remote Direct Memory Access Protocol) - 知乎
本文欢迎非商业转载,转载请注明出处。
本文给大家介绍一下RDMAP协议,请注意它是iWARP协议族(栈)中的一层而不是指RDMA技术。RDMAP主要由RFC 5041描述,之后在2014年IETF又发布了RFC 7306,补充了Atomic和立即数等特性。阅读本文需要《DDP》一文中的前置知识。
概述
RDMAP的全称为Remote Direct Memory Access Protocol,是iWARP协议栈中最靠近用户的一层。向上为中间件或者应用程序提供基于RDMA技术的服务,向下使用DDP(Data Placement Protocol)提供的零拷贝特性。
RDMAP协议在iWARP协议栈中的位置
RDMAP的主要功能是提供RDMA语义,支撑上层用户的Send/RDMA Read/RDMA Write等请求。用户下发这些请求后,软硬件会根据其内容封装出RDMAP的报文发给对端,对端硬件再解析RDMAP报文执行对应的数据搬移操作。
DDP层不依赖于底层的协议,下面可以是TCP/SCTP甚至是UDP,而RDMAP必须依赖于下层的DDP。RDMAP会使用我们之前讲解的DDP报文格式中的ULP预留的域段,所以从报文来看它们之间并没有像TCP/IP协议族各层之间清晰的分界,比如Send with Invalidate操作的报文长这个样子(绿色为RDMAP层相关的域段):
Send with Invalidate操作的DDP + RDMAP报文格式
术语
Advertisement/Placement/Delivery/ULP/LLP/RNIC...
这些术语已经在《DDP》一文中做过介绍,不再赘述
Verbs
对上层用户的API接口,即之前我们介绍过的Verbs。目前iWARP跟RoCE和Infiniband一样使用libibverbs库提供的同一套接口。Verbs和协议栈的关系如下图所示:
Verbs API和iWARP协议栈的关系
Completion
指的是RDMAP给上层用户(ULP)其下发的某个操作已经完成的指示,和IB Specification中的定义是相同的。ULP可以通过轮询的模式主动获取,也可以通过事件模式被动得到通知。
Delivery和Completion与各层次间的关系
Invalidate
使Stag失效的机制。当一个STag被无效化之后,远端的用户将不再能够通过这个STag进行远端操作。
如下图所示,本端本来可以用STag对对端的某一块内存进行读写。当所需要的业务执行完毕后,本端可以主动发起一个Send with Invalidate操作,使这个STag无效。那么之后,它就无法使用这个STag对之前的内存区域进行读写了。Invalidate操作是出于安全考虑,就像退房的时候还房卡一样,用完了别人的资源,就主动把权限交回去。
Send with Invalidate操作的功能
Event
在Completion或者其他需要引起上层用户立即注意的情况发生时(比如错误),RDMAP层可以选择产生一个事件的方式来通知上层用户。
Solicited (征集)
我们在《CQ》一文中介绍过,用户获取CQE有两种方式,一种是Poll轮询模式,一种是Notify中断模式(或者叫Event事件模式)。在事件模式中,发送端和接收端可以通过配合使用Solicited机制,来达到只有特定的消息才会产生WC给用户的效果。这种机制可以保证在事件模式中接收端能够迅速且有效地处理一些重要的消息。
如下图所示,当本端进行了多次Write操作之后,因为对端的用户并不感知Write操作,这时本端可以发送Send with Solicited消息告知对端这些任务已经完成了。对端如果配置了响应Solicited操作,那么就会产生一个Event给上层用户。
Solicited功能示意
Stream
流是一个抽象的概念,像水流一样,我们把从源头发往目的地的有序的内容称为流。不同层次都有流的概念,比如RDMAP Stream,DDP Stream,TCP Stream等。对于本文中所描述的层次结构,RDMAP、DDP和TCP的流是一对一映射的。
Connection
连接也是一个抽象的概念,经过一些信息交换(协商)并做好记录之后(比如TCP的三次握手),可以视为两个节点间建立了联系,可以按照约定收发数据,这种联系我们称为连接。
流需要关联到连接上,一个TCP的连接上只能有一个Stream;而SCTP允许一个连接上有多个Stream。
Termination/Terminate
指的是关闭一条Stream的行为,在检测到错误的时候发起,Termination消息中会包含错误信息用于定位。
Termination有两种方式,一种称为Abortive Termination,所有当前处理中和未处理的操作都不会被完成;另外一种是体面的终止Graceful Termination,顾名思义,所有的处理中和未处理的操作都会被继续完成。
操作类型
我们曾经在《RDMA操作类型》中介绍过IB Specification中定义的Send/RDMA Write/RDMA Read这三种最常用的操作类型,RDMAP协议中的三种操作类型的含义跟IB规范中的含义是一致的。RFC 5040中的RDMAP将Send操作进行了细分,分为Send、Send with Invalidate、Send with Solicited Event和Send with Solicited Event and Invalidate,但是从ULP来看,这些类型只是在Send操作的基础上加了一些标志位而已。除此之外,RDMAP还有一种独有的Terminate操作,所以一共加起来是7种操作类型。
RFC 7306为RDMAP又增加了对Atomic和立即数的支持。因为Atomic操作和立即数本专栏中还没有介绍,所以本文不涉及RFC 7306中描述的操作类型,我们会在以后的文章中讲解。
需要注意的是,RDMAP协议中,将所有以上的操作类型都称为RDMA Operation,而IB Specification中,只有Read/Write/Atomic这种不需要对端CPU感知的类型称为RDMA操作。
下面我们来介绍一下每一种操作类型,下文的描述中我们不关心发送端是如何把ULP的数据传递给RNIC的。
Send
Send的接收端需要提前准备好一组Untagged Buffer用于接收数据,一般Send以及下文的Send with XX操作主要用于发送业务的控制信息。
发送端将ULP的数据(Payload)放到一个或者多个DDP分段中,接收端RNIC解析收到的分段后,根据Header中指定的Queue Number来找到一个队列(Send固定使用Queue 0),然后从队列中取出一个提前准备好的Untagged Buffer存放这些数据。数据存放完毕后,RDMAP层会产生Completion,并把数据长度等信息传递给ULP。
Send with Invalidate操作的数据流向
Send with Invalidate
在普通的Send操作发送数据的功能的基础上携带了Invalidate的功能,在报文中会携带想要无效的STag,接收端收到数据包后,会将STag标记为无效。此后对端将无法通过这个STag来进行Write/Read等操作。
Send with Invalidate操作的数据流向
Send with Solicited Event
在普通的Send操作发送数据的功能的基础上,携带了Solicited标志位。在对端的CQ使用事件模式,并且只会对携带Solicited标志的操作产生WC时,会产生一个事件通知上层用户。
Send with Solicited Event and Invalidate
上面两者的结合,既带有invalidate某个STag的功能,又具备触发Solicited事件的能力。
Write和Read操作才是真正不需要对端CPU感知的RDMA操作,所以在实际业务中一般用这两种操作类型进行数据传输:
Write
接收端提前告知发送端STag(通过Send消息等方式),发送端将ULP的数据(Payload)放到一个或者多个报文中,接收端RNIC解析收到的报文头后, 根据其中的STag和Tagged Offeset把数据直接拷贝到指定的内存位置。数据搬移完毕后,接收端不会产生通知上层的动作。
Write操作的数据流向
Read
相比于Send和Write,Read是最复杂的,因为需要进行一次报文交互。首先本端发送一个Read Request到对端,请求的报文中,包含了两端的Tagged Buffer的Stag和Tagged Offset等信息。对端接收到报文后,从内存中取出数据并且组装一个Read Request报文,其头部包含之前本端(Data Sink)传递给它的接收数据的Tagged Buffer的信息。本端接收到报文后,把Payload提取出来放到Buffer中。
Read操作的数据流向
Terminate
Terminate操作专门用于传递错误相关的信息,有其特定的格式。报文中会包含错误层次、错误码、错误类型等信息。当本端发送了一个Terminate消息之后,就不应该在这条流上再发送任何消息了;但是当对端接收到Terminate消息之后,对端仍然可以在这条流上发送消息,处理未完成的任务,当对端也发送Terminate消息之后,它也会停止在流上发送消息,这样这条流算是彻底断开了。
因为包含着断开连接的意图以及错误信息,Terminate消息必须逐级上报最终到达ULP,以便ULP进行进行后续的处理。当RDMAP层收到Terminate消息之后,需要把所有的未下发给DDP层的消息置为完成错误。
消息格式
本小节是RFC 5040第4章的翻译和再整理,建议读者先阅读原文以获得更精确的描述。
在本文的最开始我们提到过,RDMAP和DDP的设计在报文上不是完全分离的,RDMAP的控制信息使用了DDP为ULP预留的域段。
RDMAP的消息采用以下格式:
Control Field + Invalidate STag Field + Header + Payload
除了Control Field之外,其他域段都是不一定会携带的,根据具体的操作类型有所差异。
前文讲过RDMAP有7种操作类型,但是因为Read操作需要往返消息,即Read Request和Read Response,所以一共有8种消息类型/格式。另外需要注意的是,iWARP和RoCE不同,因为它是基于TCP或者SCTP这种可靠传输协议的,由底层来保证了数据一定能够正确的送达对端,所以RDMAP层不需要设计ACK(Acknowledge)报文。
控制域段
RDMAP使用DDP的RsvdULP的第一个字节(Tagged和Untagged Buffer Model都有此域段)来指示版本信息以及标识具体的操作类型,下图中的绿色背景部分,即为RDMAP的Control Field。
Control Field格式
- RV - RDMAP Version: 2 bits
RDMAP版本。RDMAP存在两个版本,1表示IETF的一系列RFC定义的版本,0表示RDMA Consortium组织定义的早期版本。这两个版本之间的差异和互操作性在RFC 5044中有描述,不在本文的讨论范围之内。 - Rsv - Reserved: 2 bits
保留域段,发送方必须设置为0,接收端会忽略这个域段。 - OpCode - Operation Code: 4 bits
表示本RDMAP消息的操作类型,具体请查看本小节总结中的表格。
Invalidate STag
只有Send with Invalidate或者Send with Solicited and Invalidate会使用这个域段,它实际上是DDP Untagged Buffer Model报文中的RsvdULP域段的后4个字节。其他使用Untagged Buffer Model的RDMAP操作,不使用该域段,填0。
Invalidate STag格式
- Invalidate STag - Invalidate Steering Tag: 32bits
表示Send with Invalidate或者Send with Solicited and Invalidate要无效化的对端的STag。
Header
RDMAP中只有两种Header,这些Header只在对应的操作类型的消息中携带,其余的操作类型都使用DDP现有的域段来实现功能。
RDMA Read Request Header
RDMA Read操作需要将对端指定内存区域的数据读回,放到本端指定内存区域中,而数据是由对端回复的消息携带的。交互流程是:本端先发送一个Read Request,告知想要读取对端Tagged Buffer的信息,以及本地承接数据的Tagged Buffer的信息。然后对端回复Read Response,携带有本端的STag信息以及数据。Read Request使用Untagged Buffer Model,本端和对端的STag信息被组织成了RDMA Read Request Header格式。对端固定用Queue 1来接收它对应的DDP消息,从DDP的视角来看,这个Header是该层的Payload。Header格式如下:
RDMA Read Request Header格式
本端(数据接收端/Data Sink)信息:
- SinkSTag - Data Sink Steering Tag: 32bits
本端将要接收数据的Tagged Buffer的STag。 - SinkTO - Data Sink Tagged Offset: 64bits
本端将要接收的数据在Tagged Buffer中相对起始地址的偏移。
Data Source将会把上述两个域段拷贝到Read Response消息之中,本端RNIC收到Read Response之后,直接把数据拷贝到对应的内存地址。
对端(数据所在端/Data Source)信息:
- RDMARDSZ - RDMA Read Message Size: 32bits
从Data Source内存中读取数据的长度。 - SrcSTag - Data Source Steering Tag: 32bits
被读取数据所在的Tagged Buffer的STag。 - StcTO - Data Source Tagged Offset: 64bits
被读取数据在Tagged Buffer中相对于起始位置的偏移。
这些域段的关系如下图所示,比较好理解:
RDMA Read Request Header中域段间的关系
Terminate Header
Terminate消息使用DDP的Untagged Buffer Model,接收端固定使用Queue 2来处理。它也有自己专属的Header格式,作为DDP的Payload携带在报文中。DDP Segment Length、Terminated DDP Header和Terminated RDMA Header都是可选项,其是否存在由M、D、R三个bit位表示,这三位统称为HdrCt(Header Control),它们置位取决于错误类型和错误原因,具体请参考RFC 5040 Figure 10。
Terminate Header格式
- Layer: 4bits
用来标识具体出错的层级,出问题的层级可能是RDMAP、DDP以及下层的MPA等。下层的错误最终会传递给RDMAP层并体现在该域段。 - EType - Error Type: 4bits
表示出错的类型,不同的层级有不同的错误类型,比如RDMAP定义了三种:本地致命错误Local Catastrophic Error、远端保护域错误Remote Protection Error和远端操作错误Remote Operation Error。 - Error Code: 8bits
表示出错的具体原因。不同的错误类型都定义了一组错误原因,每个原因对应了一个错误码。具体请参考DDP(RFC 5041)、RDMAP(RFC 5042)、MPA(RFC 5044)。 - M: 1bit
表示DDP Segment Length是否存在。 - D - DDP Header Exist: 1bit
表示DDP Header是否存在。 - R - RDMAP Header Exist: 1bit
表示RDMAP Header是否存在。 - DDP Segment Length: 16bits
可选域段,表示错误发生时,对应的DDP Segment的长度。 - Terminated DDP Header: 112bits(Taggged)/144bits(Untagged)
可选域段,仅当RDMAP层发生Remote Protection Error的时候会有此域段,里面会包含出错的DDP头部。 - Terminated RDMA Header: 224 bits
可选域段,仅当RDMAP层的RDMA Read Request出错时会有此域段,里面包含了对应Message的RDMAP头部。
总结
我们来总结一下各个消息类型的域段,这里强调一点,RDMAP的RDMA Read Request、Send XX和Terminate都使用DDP的Untagged Buffer Model,这些操作类型使用固定的Queue Number,这个Queue和Verbs用户看到的Queue Pair不是同一个东西,是DDP层的概念,请不要混淆。
消息类型 | OpCode | Buffer类型 | 队列号 | 特殊域段 | Payload |
---|---|---|---|---|---|
RDMA Write | 0000 | Tagged | - | - | 有 |
RDMA Read Request | 0001 | Untagged | 1 | RDMA Read Request Header | 无 |
RDMA Read Response | 0010 | Tagged | - | - | 有 |
Send | 0011 | Untagged | 0 | - | 有 |
Send with Invalidate | 0100 | Untagged | 0 | Invalidate STag | 有 |
Send with Solicited | 0101 | Untagged | 0 | - | 有 |
Send with Solicited and Invalidate | 0110 | Untagged | 0 | Invalidate STag | 有 |
Terminate | 0111 | Untagged | 2 | Terminate Header | 无 |
最后我们来展示一下各个操作的完整的报文格式(DDP+RDMAP层):
Send和Send with Solicited
Send with Invalidate和Send with Solicited and Invalidate
RDMA Write
RDMA Read Request
RDMA Read Response
Terminate
实验
实验的准备工作请参考《DDP》一文,我们用Wireshark抓了一次Read操作的报文,包含一个Read Request消息,和被拆分成两个DDP Segment的Read Response消息。
Read Request消息
首先看这个10.1.13.107发给10.1.13.104的Read请求:
Read Request示意
实验中抓取的某个Read Request报文
如前文所述,它使用Untagged Buffer Model,所以Tagged flag为false。而Reserved for use by the ULP域段值为0x41 0000 0000会被解析成RDMAP control field和RDMAP层的Reserved。
Read请求固定使用Queue Number 1,在报文中也有体现。
下面我们关注下RDMAP的Header,其版本是1,Read请求的OpCode是0x1,因为Read Request不带有Invalidate STag,所以紧跟了一个全零的Reserved域段。上述几个域段拼起来就是上文的0x41 0000 0000。
最后是Read Request Header,其中包含了双方的Tagged的Buffer的信息以及读取的长度。Read Request不携带用户Payload,所以报文到此为止了。
Read Response消息
当前的MTU是1500 Bytes,而Read Response的Payload长度为1500 Bytes,加上各层头部之后显然无法装在同一个以太网帧里面,所以被拆成了两个DDP分段:
Read Response示意
实验中抓取的Read Response的第一个DDP Segment
先看第一个DDP分段,Last flag为0,说明后面还有一个DDP分段。这里STag 0xa59d8500转换成10进制,就是Read Request消息中的Data Sink STag 2778563840,TO域段也是一样的。
OpCode为0x2,表示Read Response。
Data的总长度是1428Bytes。
实验中抓取的Read Response的第二个DDP Segment
再看第二个DDP分段,Last flag被置1。STag没有变,而Tagged Offset比上一个DDP分段增加了0x594,转换成10进制正是之前已经发送的Payload长度1428。所以很明显这里是紧接着上一个DDP分段末尾的内存位置写入的。
OpCode仍然为0x2,Data总长度为1500 - 1428 = 72。
好了,RDMAP就介绍到这里,下一篇文章我将带大家了解iWARP协议栈的最后一层,也是最复杂的一层——MPA。
参考文献
[1] RFC 5040
[2] RFC 5042
附专栏目录,方便读者跳转: