转载链接:https://mp.weixin.qq.com/s?__biz=MzIwNTUxNDgwNg==&mid=2247484355&idx=1&sn=04f0617bf774fa3c6020d90288b679e8&chksm=972ef29aa0597b8ca79b040f3222eef85835a5cd693167aa6f7ffd34a78ae15f696d7b736304&scene=21#wechat_redirect
一、故事前传
上一篇文章中我们对NVMe作了概况性的介绍,详细请见历史消息。
1. NVMe技术概述;
二、队列管理
在上一篇文章(NVMe系列专题之一:NVMe技术概述)中,我们提到了NVMe有一个很大的优势就是队列深度达到了64K,并且支持队列个数最大可达64K。所以呢,这里我们就先聊聊NVMe中队列相关的一些知识点。
队列,在NVMe协议中,是专门为NVMe命令服务的。介绍队列之前,我们还是先看看NVMe定义的命令种类:
NVMe定义的命令很简单,只有两种: Admin Command和IO Command。加起来总共只要求13个(10 Admin Commands + 3 IO Commands),相比于ATA Commands,真是一下子清爽了很多~
同样,执行IO Command时,也会有对应的两个队列,分别是IO SQ和IO CQ。
NVMe Spec对Admin SQ/CQ和IO SQ/CQ有不同的约定:
注: Admin/IO command大小为64B,对应的Completion大小为16B。
SQ/CQ是队列,那就应该有队列的头(Head)和尾(Tail)。在NVMe Spec中定义SQ/CQ均是循环队列,可以理解为一个圆形(如下图左),但是内存中实际的长条状的(如下图右),其实,队列可以是连续的物理空间,也可以不连续。
Tail: 指向队列中的下一个空位;
Head: 指向下一个将要被执行的命令所在的位置。
有了SQ/CQ是不是就可以执行Command了呢?
我们先看一下NVMe Spec中Command执行的整个流程再回过来审视一下这个问题。
在NVMe Spec中Command执行的流程有八步,Host与Controller之间用PCIe TLP传递信息。
看完上面NVMe Command执行流程之后,我们再回过头来看一下刚才我们的问题:"有了SQ/CQ是不是就可以放心的执行Command了呢?".
答案是否定的。从上述Command执行流程中,我们发现除了SQ/CQ之外,还有两个关键的"人物": PCIe TLP和寄存器.
在之前的PCIe专题(PCIe系列专题之二:2.2 TLP事务处理方式解析)中,我们有介绍过PCIe TLP的类型有很多,如下图,不过,NVMe只挑选了Memory Read/Wirte传递信息。
注:Non-Posted: 需要completion返回响应包;Posted: 不需要completion返回响应包
NVMe Command执行过程中所需的寄存器有两种:Doorbell Register和Pointer Register。Doorbell,可以简称DB,是NVMe Spec定义的寄存器;Pointer register 是主控厂商自定义的寄存器,归纳一下:
寄存器 |
用途 |
位置 |
Update By |
Defined By |
SQ Tail Doorbell |
记录SQ Tail的位置 |
Controller Memory |
Host |
NVMe Spec |
SQ Head Pointer |
记录SQ Head的位置 |
Controller Memory |
Controller |
Vendor Specific |
CQ Tail Pointer |
记录CQ Tail的位置 |
Controller Memory |
Controller |
Vendor Specific |
CQ Head Doorbell |
记录CQ Head的位置 |
Controller Memory |
Host |
NVMe Spec |
从上表的信息中,不知道聪慧的你,有没有发现:
很显然,Host与Controller之间的信息是不对等的。不过,还好Controller是个乐于分享的人。Controller会与Host共享自己所知的信息。那Controller怎么把SQ Head和CQ Tail的信息告知Host呢?
不怕,聪明如你!Controller把SQ Head和CQ Tail的信息写入了Completion报文中,如下图:
前面说了理论,我们还是通过真实的NVMe/PCIe Trace再回顾一下CMD执行流程,以Admin Command: Set Feature和IO Command: Read 为例:
1. Set Feature
首先,看全局,如下图,
结合前面介绍的Command执行流程的八步分解Trace:
(1)第一步是Host向Host内存中的SQ写入新的Command。因为这部分是在Host内部执行的,所以在PCIe Trace中没有体现。
(2)第二步是Host更新Controller内存中的SQ Tail DB,告知Controller过来提取Command。
PCIe通过Memory Write TLP(Posted)完成Host对SQ Tail DB的更新。从下图的Trace中,我们可以看到SQ Tail=0x1E(先记住这个值哈,后面有用). SQ Tail DB在Controller内存中的位置=0xFB301000.
(3)第三步是Controller去Host内存中的SQ中取回Command。Controller通过发送Memory Read TLP(Non-Posted)从Host内存提取Command。SQ在Host内存中的位置=0x10040C740. 读出数据的长度=0x40(64),这个也是NVMe规定Command的长度64B。
因为Memory Write是Non-Posted TLP,所以Host会发回一个Memory Write对应的Completion TLP。(需要注意的是在Xgig PCIe设备中,此时MRd的CpID被叫做了ASubmQ,实际就是CpID)
从CpID中包含取回Command的一些信息,比如这个Set feature命令是为了改变NVMe设备的Power state.
(4)第四步是在Controller内部开始执行上一步取回的Set feature命令。此时在PCIe链路中没有交互,所以在PCIe Trace中看不到执行过程的Trace。
(5)第五步是Controller更新CQ,反馈Set feature命令执行结果。
Controller发送Memory Write TLP将set feature命令执行结果写入CQ。此时CQ在Host内存中的位置=0x10041090. 同时在告知Host SQ Head的位置=0x1E。还记得第二步中SQ Tail=0x1E这个信息吗? Head==Tail就说明了SQ现在空了。此外,还有一个信息就是Phase Tag=1, 代表Host CQ中有新增Completion信息。
(6)第六步是Controller通知Host检查Set feature命令执行结果。这个过程中,Controller通过发送MSI-X中断告知Host去检查CQ中的返回结果。
(7)第七步是Host检查CQ中的Set feature命令执行结果。这个过程时在Host内部实现的,在PCIe Trace中也没有体现。
(8)第八步是Host更新Controller内存中的CQ Head DB,告知Controller:"您的完成报告我已经处理完了,非常感谢您!" 从Trace中可以看到,CQ Head的位置=0x1A. CQ Head DB在Controller内部的位置=0xFB301004.
2. Read
IO command是与Admin command处理的流程基本一致,有一点不同的是:IO Command处理过程中会涉及到数据的传输。在这里就不展开解析咯~