提交成功
返回列表

快上车!让我们走进一个NVMe读I/O

2019 / 10 / 25
0

host是如何读取NVMe SSD上数据的?一个读I/O又有多少步操作?

 

 

“八步。”NVMe协议这样回答。这是个非常底层的问题,其整个流程不仅涉及NVMe协议本身,其中命令和I/O数据传输的机制还与PCIe协议等内容息息相关。理解一个NVMe SSD的I/O处理流程将为SSD使用和优化打下基础。今天我们就走进一个NVMe SSD的读I/O。

 

在介绍NVMe SSD的读IO处理流程之前,需要往下一层到达PCIe的事务层(Transaction)。无论是NVMe的命令本身,还是要传输的数据,最终都会被封装成为TLP包进行传输。AIC、U.2以及M.2等形态的NVMe SSD也都是借助PCIe插槽与host进行交互的。

 

PCI Express Layered Model

 

上图就详细说明了PCIe 协议中每层的内容。后文中我们会通过一个trace记录,结合NVMe记录和TLP包信息对NVMe的读I/O进行解读。

 

NVMe协议通过下图描述了一个完整的命令处理流程:

 

 

Attenion

 

本文使用的是《NVM Express™ Revision 1.2a》(以下简称“NVMe协议”),严谨起见,在此注明版本,需要说明的是不同的NVMe协议版本在读操作中没有本质的不同,也不会影响到本文的描述。

 

 

为了更好的理解上图中命令处理过程的细节,我们总结如下:

 

1. 一个I/O命令执行过程中,host和SSD是两个主角。读I/O也就是host发起读请求,SSD响应请求,并且将数据写到host指定的内存地址上的过程。

 

2. Submission Queue 和Completion Queue是host内存中的两个队列,host通过Submission Queue告知SSD的Controller要处理的命令,Controller通过Completion Queue告知Host已经被处理完毕的命令以及这些命令执行的状态。

 

需要补充的是, NVMe的命令分为Admin Command和NVM Command,Admin Command执行一些SSD管控操作,比如Namespace管理和固件升级等,系统中只有一对Admin Submission Queue和Admin Completion Queue;NVM Command用于执行读写等I/O命令,可以有多对I/O Submission Queue和I/O Completion Queue。

 

 

常见的形式如上图,服务器CPU每个核都会绑定一个I/O Submission Queue 和一个I/O Completion Queue,另一方面,队列上限也不能超过Controller的处理能力。

 

3. Submission Queue Tail Doorbell和Completion Queue Head Doorbell,这是两个Controller Registers中的字段,会映射在host内存中。host通过更新Submission Queue Tail Doorbell映射的内存上的记录,就可以达到告知SSD有新的命令需要处理,同样,在命令执行尾声,host也是通过更新Completion Queue head Doorbell映射的内存上的信息而实现告知SSD命令已经执行完毕。

 

4. 至于SSD要把数据往内存哪段地址上写,怎么写,就要靠PRP或者SGL,下文也会对此解读。

 

前文介绍了一些概念和部分操作细节,接下来是更为形象和深入的解读。在此我们还要借助协议分析仪,分析仪的职责是将host和SSD传输的0和1翻译成人话,借助支持NVMe协议的分析仪,我们获得了一条读I/O的Trace记录,如下图(每行信息记录都可以进一步展开看到相应的TLP包和DLLP包的信息):

 

 

分析仪可以对host和SSD之间传输的数据进行解析,并按照PCIe和NVMe的规范进行展示。有了这张图,我们就可以更为直观的看到每一步host和SSD都做了什么。接下来就开始了。首先是这条命令执行过程信息的概览。

 

 

可以看出,这次我们使用的设备是一个3.2TB的PBlaze5 916 NVMe SSD(以下简称为“PBlaze5”)。host从SSD上读出了1024dwords(4KiB)数据,这段数据在SSD上起始地址(SLBA)是0x8,一共8个LBA(NLB=0x7,这一参数为0’s based value)。数据将从SSD传输到0xFEB84000(PRP1)这个地址上,最终这个操作成功了(SC为Successful Completion)。

 

 

名词&概念

 

SLBA(Starting LBA):数据在SSD上的起始地址,这里的值是0x8。

NLB(Number of Logical Blocks):读取数据的blocks数,这里的值是0x7,需要指出的是这是一个0’s based value,所以最终传的是8个block。

NSID(Namespace Identifier):指明了此次读操作的namespace id,这里值为0x1。

 

命令信息被存放在ID为0x004C的Submission Queue(SQID)中,SSD处理完毕的信息会存放在ID为0x004C的Completion Queue(CQID)中。此外,这条记录还标注出了SSD在服务器上的ID(Device ID)等信息。

 

第一步:host准备一条命令,并将之加入到内存中的Submission Queue中。这步属于host自身的行为,还没有开始和SSD交换数据。

 

第二步:host告知SSD有新的待处理。这步操作包含很多细节,前文说过,SQyTDBL(Submission Queue y Tail Doorbell)是Controller Registers信息,映射在host的BAR 0空间中,其具体的地址是0xC6421260,这步中,host更新了这个地址中的值,将Submission Queue中有新记录的信息告知了SSD。

 

 

上图展示了host更新SQyTDBL的细节。第一行是NVMe协议层面的内容,下面则是具体的TLP包信息和ACK信息。到这SSD终于知道了有新的需求。

 

第三步:SSD取Command。从上文可知,命令具体内容被存放在Submission Queue中,SSD也知道了有新的命令需要处理,这里SSD首先发起一个Memory Read的请求。

 

 

host把命令内容信息通过另一个TLP包返回给SSD。一个Submission Queue记录是64bytes,所以,可以看到一共传输了16个dwords,共64Bytes。而这16dwords中的内容翻译出来就是第一行NVMe 的内容,可以看到命令的地址、队列和命令的ID、操作类型(OPC)、数据的地址、以及SSD在系统中的Device ID都在其中了。有了这些内容,SSD就可以往指定的内存地址上写数据了。

 

第四步:SSD处理,将数据写到相应的内存地址上。对于host来讲,这个I/O是一个读取数据的操作。对于SSD来讲,就需要将相应的数据(SLBA为0x8,NLB为0x7)使用DMA的方式写到指定的内存地址(PRP1 Address为0xFEB84000)上。

 

 

系统的MaxPayloadSize为256bytes,超过就需要分开传输。所以这段1024dwords(4KiB)数据传输操作被拆分成了16个64dwords(256bytes)的TLP包。另一个重要的概念是PRP(Physical Region Page),本例中PRP1是一个host内存中的地址——0xFEB84000。SSD从内存的0xFEB84000开始写,最后一个TLP包的起始地址是0xFEB84F00。

 

NVMe规定了PRP和SGL两种数据传输方式,后者在NVMe over Fabrics中使用比较普遍,这里不再赘述。采用PRP方式的I/O命令都会有PRP1和PRP2两个地址,它们可以指向一个内存地址,也可以是一个物理地址列表,被称为PRP list,如果传输数据量超过两个内存页,就需要PRP list指明数据存放的地址。本文中例子数据量为4KiB,恰好为Linux的1个内存页大小,所以使用PRP1传一个物理地址就可以。更为复杂的PRP记录规范,可以参看NVMe协议的《Physical Region Page Entry and List》等内容。

 

第五步:到现在,SSD已经把数据传到了host指定的内存地址上。SSD会把命令执行的状态总结成为一条记录添加在Completion Queue中,具体的操作就是SSD向host内存的Completion Queue中添加一个4dwords(16bytes)的记录。

 

 

上图中NVMe返回的信息可见,SSD成功的完成了写,并在Status Code(SC)中记下Successful Completion的信息。如果整个命令过程有错误出现,那么SC也会记录下相应的错误信息。

 

第六步:SSD发起中断,通知host处理Completion Queue中的记录。数据传输完毕,执行状态也记录在了Completion Queue中,SSD通过MSI-X中断告知host做进一步的处理。

 

 

第七步:host会处理相应的Completion Queue中的记录。但是这部分细节发生在host内部,分析仪的trace记录不会捕捉到具体内容。

 

第八步:Host更新内存中Completion Queue y Head Doorbell信息,相应的Controller Registers中Doorbell信息也会进行更新。至此,一个读I/O已经处理完毕。

 

 

NVMe协议中还规定了很多其他的命令,根据其命令类型不同,返回值和对内存的操作也各不相同,但原理和本文中介绍的读I/O相似,相信看完本文,您可以触类旁通,理解NVMe 协议中host和SSD的交互机制。