NVMe系列专题之二:队列(Queue)管理

转载链接: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 CommandIO Command。加起来总共只要求13个(10 Admin Commands + 3 IO Commands),相比于ATA Commands,真是一下子清爽了很多~

NVMe系列专题之二:队列(Queue)管理_第1张图片

 

  • 当Host要下发Admin command时,需要一个放置Admin command的队列,这个队列就叫做Admin Submission Queue, 简称Admin SQ
  • Device执行完成Admin command时,会生成一个对应的Completion回应,此时也需要一个放置Completion的队列,这个队列就叫做Admin Completion Queue,简称Admin CQ.

同样,执行IO Command时,也会有对应的两个队列,分别是IO SQIO CQ

NVMe系列专题之二:队列(Queue)管理_第2张图片

NVMe Spec对Admin SQ/CQ和IO SQ/CQ有不同的约定:

  • 系统中只有一对Admin SQ/CQ,则可以有最多64K对 IO SQ/CQ;
  • Admin SQ/CQ的队列深度是2~4K;而IO SQ/CQ的队列深度是2~64K;

注: Admin/IO command大小为64B,对应的Completion大小为16B。

  • Admin SQ和CQ是一对一的,而IO SQ和CQ可以一对一,也可以多对一。多个SQ可以支持多线程工作,不同SQ之间可以赋予不同的优先级; 
  • Admin和IO的SQ/CQ均放在Host端Memory中;
  • SQ由Host来更新,CQ则由NVMe Controller更新。

 

SQ/CQ是队列,那就应该有队列的头(Head)和尾(Tail)。在NVMe Spec中定义SQ/CQ均是循环队列,可以理解为一个圆形(如下图左),但是内存中实际的长条状的(如下图右),其实,队列可以是连续的物理空间,也可以不连续。

NVMe系列专题之二:队列(Queue)管理_第3张图片

Tail: 指向队列中的下一个空位;

Head: 指向下一个将要被执行的命令所在的位置。

  • 当队列为空时,Head==Tail;
  • 当队列为满时,Head==Tail+1;

 

有了SQ/CQ是不是就可以执行Command了呢?

我们先看一下NVMe Spec中Command执行的整个流程再回过来审视一下这个问题。

 

在NVMe Spec中Command执行的流程有八步,Host与Controller之间用PCIe TLP传递信息。

  1. Host提交新的Command。Host下发一个新Command时,将其放入Host内存中SQ;
  2. Host通知Controller提取Command。Host把Command写入SQ之后,此时Device并不知道这件事。所以,Host此时需要给Controller发信息,通知NVMe Controller:"我提交了新的命令请求,麻烦尽快帮忙处理!"。这个过程通过更新在Controller内部的寄存器SQ Tail Doorbell来完成。
  3. NVMe Controller从SQ提取Command。取走Command之后,需要在Controller内部的SQ Head Pointer寄存器中更新Head所在的位置。NVMe没有规定Command存入队列的执行顺序,Controller可以一次取出多个Command进行批量处理。
  4. NVMe Controller执行从SQ提取的Commands。一个队列中的Command执行顺序是不固定的(可能导致先提交的请求后处理),这涉及到NVMe Spec定义的命令仲裁机制,在后续文章中介绍。执行Read/Wirte Command时,这个过程也会与Host Memory进行数据传递。
  5. NVMe Controller将Commands的完成状态写入CQ。此时,Controller需要更新CQ Tail Pointer寄存器。
  6. NVMe Controller通知Host检查Commands的完成状态。Controller通过发送一个中断信息告知Host:"您提交的Commands,我已经执行完毕了,请您检查结果!"。
  7. Host检查CQ中的Completion信息
  8. Host告知Controller已处理完成Completion信息。此时,Host更新Controller内部的CQ Head Doorbell。告知Controller:"您发送回来的Command执行结果,我已处理完毕,非常感谢!"。

NVMe系列专题之二:队列(Queue)管理_第4张图片

 

看完上面NVMe Command执行流程之后,我们再回过头来看一下刚才我们的问题:"有了SQ/CQ是不是就可以放心的执行Command了呢?".

 

答案是否定的。从上述Command执行流程中,我们发现除了SQ/CQ之外,还有两个关键的"人物": PCIe TLP寄存器

 

在之前的PCIe专题(PCIe系列专题之二:2.2 TLP事务处理方式解析)中,我们有介绍过PCIe TLP的类型有很多,如下图,不过,NVMe只挑选了Memory Read/Wirte传递信息。

NVMe系列专题之二:队列(Queue)管理_第5张图片

注:Non-Posted: 需要completion返回响应包;Posted: 不需要completion返回响应包

 

NVMe Command执行过程中所需的寄存器有两种:Doorbell RegisterPointer 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

 

从上表的信息中,不知道聪慧的你,有没有发现:

  • 这个四个寄存器全部放在Controller内存中。也就是说Controller知道这SQ Tail/Head和CQ Tail/Head的全部信息。
  • 而Host仅仅知道自己更新的两个信息SQ Tail和CQ Head。

 

很显然,Host与Controller之间的信息是不对等的。不过,还好Controller是个乐于分享的人。Controller会与Host共享自己所知的信息。那Controller怎么把SQ Head和CQ Tail的信息告知Host呢?

 

不怕,聪明如你!Controller把SQ Head和CQ Tail的信息写入了Completion报文中,如下图:

NVMe系列专题之二:队列(Queue)管理_第6张图片

  • SQ Head Pointer就是SQ Head的信息。
  • P代表着Phase bit。CQ队列中所有的位置会被初始为0, 当有新的Completion信息写入时,Phase bit被置为1. Host通过读取Host 内存中CQ的Phase bit信息就能判断中CQ Tail的位置。

NVMe系列专题之二:队列(Queue)管理_第7张图片

 

前面说了理论,我们还是通过真实的NVMe/PCIe Trace再回顾一下CMD执行流程,以Admin Command: Set Feature和IO Command: Read 为例:

1. Set Feature

首先,看全局,如下图,

NVMe系列专题之二:队列(Queue)管理_第8张图片

结合前面介绍的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.

NVMe系列专题之二:队列(Queue)管理_第9张图片

 

(3)第三步是Controller去Host内存中的SQ中取回Command。Controller通过发送Memory Read TLP(Non-Posted)从Host内存提取Command。SQ在Host内存中的位置=0x10040C740. 读出数据的长度=0x40(64),这个也是NVMe规定Command的长度64B。

NVMe系列专题之二:队列(Queue)管理_第10张图片

 

因为Memory Write是Non-Posted TLP,所以Host会发回一个Memory Write对应的Completion TLP。(需要注意的是在Xgig PCIe设备中,此时MRd的CpID被叫做了ASubmQ,实际就是CpID

从CpID中包含取回Command的一些信息,比如这个Set feature命令是为了改变NVMe设备的Power state.

NVMe系列专题之二:队列(Queue)管理_第11张图片

 

(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信息。

NVMe系列专题之二:队列(Queue)管理_第12张图片

 

(6)第六步是Controller通知Host检查Set feature命令执行结果。这个过程中,Controller通过发送MSI-X中断告知Host去检查CQ中的返回结果。

NVMe系列专题之二:队列(Queue)管理_第13张图片

 

(7)第七步是Host检查CQ中的Set feature命令执行结果。这个过程时在Host内部实现的,在PCIe Trace中也没有体现。

 

(8)第八步是Host更新Controller内存中的CQ Head DB,告知Controller:"您的完成报告我已经处理完了,非常感谢您!" 从Trace中可以看到,CQ Head的位置=0x1A. CQ Head DB在Controller内部的位置=0xFB301004.

NVMe系列专题之二:队列(Queue)管理_第14张图片

 

2. Read

NVMe系列专题之二:队列(Queue)管理_第15张图片

IO command是与Admin command处理的流程基本一致,有一点不同的是:IO Command处理过程中会涉及到数据的传输。在这里就不展开解析咯~

 

 

 

 

 

你可能感兴趣的:(NVMe系列专题之二:队列(Queue)管理)