让我们走进一个NVMe读I/O

Host是如何读取NVMe SSD上数据的?一个读I/O又有多少步操作?
“八步。”NVMe协议这样回答。这是个非常底层的问题,其整个流程不仅涉及NVMe协议本身,其中命令和I/O数据传输的机制还与PCIe协议等内容息息相关。理解一个NVMe SSD的I/O处理流程将为SSD使用和优化打下基础。今天我们就走进一个NVMe SSD的读I/O。
在介绍NVMe SSD的读IO处理流程之前,需要往下一层到达PCIe层。无论是NVMe的命令本身,还是要传输的数据,最终都会被封装成为TLP包进行传输。AIC、U.2以及M.2等形态的NVMe SSD也都是借助PCIe插槽与host进行交互的。
让我们走进一个NVMe读I/O_第1张图片
PCI Express Layered Model
上图就详细说明了PCIe 协议中每层的内容。后文中我们会通过一个trace记录,结合NVMe记录和TLP包信息对NVMe的读I/O进行解读。
NVMe协议通过下图描述了一个完整的命令处理流程:
让我们走进一个NVMe读I/O_第2张图片

本文使用的是《NVM ExpressTM Revision 1.2a》(以下简称“NVMe协议”),严谨起见,在此注明版本,需要说明的是不同的NVMe协议版本在读操作中没有本质的不同,也不会影响到本文的描述。
本文介绍的是NVMe over PCIe的读I/O,不涉及NVMe over Fabrics或者其他网络传输。

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

  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。
    让我们走进一个NVMe读I/O_第3张图片

前文介绍了一些概念和部分操作细节,接下来是更为形象和深入的解读。在此我们还要借助协议分析仪,分析仪的职责是将host和SSD传输的0和1翻译成人话,借助支持NVMe协议的分析仪,我们获得了一条读I/O的Trace记录,如下图(每行信息记录都可以进一步展开看到相应的TLP包和DLLP包的信息):
让我们走进一个NVMe读I/O_第4张图片
分析仪可以对host和SSD之间传输的数据进行解析,并按照PCIe和NVMe的规范进行展示。有了这张图,我们就可以更为直观的看到每一步host和SSD都做了什么。接下来就开始了。首先是这条命令执行过程信息的概览。
compeltion queue entry
可以看出,这次我们使用的设备是一个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。
PBlaze5-sq-doorbell
上图展示了host更新SQyTDBL的细节。第一行是NVMe协议层面的内容,下面则是具体的TLP包信息和ACK信息。到这SSD终于知道了有新的需求。
第三步。**SSD取Command。**从上文可知,命令具体内容被存放在Submission Queue中,SSD也知道了有新的命令需要处理,这里SSD首先发起一个Memory Read的请求。
让我们走进一个NVMe读I/O_第5张图片
host把命令内容信息通过另一个TLP包返回给SSD。一个Submission Queue记录是64byte,所以,可以看到一共传输了16个dwords,共64Bytes。而这16dwords中的内容翻译出来就是第一行NVMe 的内容,可以看到命令的地址、队列和命令的ID、操作类型(OPC)、数据的地址、以及SSD在系统中的Device ID都在其中了。有了这些内容,SSD就可以往指定的内存地址上写数据了。
第四步:**SSD处理Command。**对于host来讲,这是一个读取数据的操作。对于SSD来讲,就需要将相应的数据(SLBA为0x8,NLB为0x7)使用DMA的方式写到指定的内存地址(PRP1 Address为0xFEB84000)上。
让我们走进一个NVMe读I/O_第6张图片
系统的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的交互机制。
相关阅读:
《深入浅出SSD 固态存储核心技术、原理与实战》第6章 NVMe介绍
《NVM Express Revision 1.2a》

你可能感兴趣的:(NVMe)