NVMe总体介绍以及SQ、CQ、DB
NVMe系列的笔记主要参考博客:http://www.ssdfans.com/?p=8137,蛋蛋是一个很酷的人
NVMe总体介绍
为了充分发挥SSD的性能,取代为HDD设计的AHCI和SATA协议,Intel等厂商联合设计了NVMe协议。NVMe协议制定了Host与SSD的通信命令以及命令的执行方式。NVMe命令包括Host用来管理和控制SSD的Admin Command以及用来在Host与SSD之间传输数据的I/O Command。
NVMe有三个重要部分,Submission Queue(SQ),Completion Queue(CQ)和Doorbell Register(DB)。SQ和CQ位于Host的内存中,DB则位于SSD的控制器内部。如下图所示,NVMe Subsystem一般就是SSD,其作为一个PCIe Endpoint通过PCIe连接Root Complex(RC),然后RC连接着CPU。SQ位于Host内存中,Host要发送命令时,先把准备好的命令放在SQ中,然后通知SSD来取;CQ也是位于Host内存中,一个命令执行完成,成功或失败,SSD总会往CQ中写入命令完成状态。Host通过写SSD端的DB寄存器来告知SSD有需要执行的命令。
NVMe处理命令流程:
- 第一步:Host写命令到SQ;
- 第二步:Host写DB,通知SSD取指;
- 第三步:SSD收到通知,于是从SQ中取指;
- 第四步:SSD执行指令;
- 第五步:指令执行完成,SSD往CQ中写指令执行结果;
- 第六步:然后SSD发短信通知Host指令完成;
- 第七步:收到短信,Host处理CQ,查看指令完成状态;
- 第八步:Host处理完CQ中的指令执行结果,通过DB回复SSD:指令执行结果已处理
NVMe SQ、CQ、DB介绍
Host往SQ中写入命令, SSD往CQ中写入命令完成结果。SQ与CQ的关系,可以是一对一的关系,也可以是多对一的。
有两种SQ和CQ,一种是Admin,另外一种是I/O,前者放Admin命令,用以Host管理控制SSD,后者放置I/O命令,用以Host与SSD之间传输数据。Admin SQ/CQ 和I/O SQ/CQ各司其职,你不能把Admin命令放到I/O SQ中,同样,你也不能把I/O命令放到Admin SQ里面。正如上图所示,系统中只有一对Admin SQ/CQ,它们是一一对应的关系,I/O SQ/CQ却可以很多。Host端每个Core只有一个CQ,但是可以有多个SQ,首先是性能需求,一个Core中有多线程,可以做到一个线程独享一个SQ;其次,出于对服务质量QoS的考量,可以把重要的命令放入高优先级SQ。
作为队列,每个SQ和CQ都有一定的深度:对Admin SQ/CQ来说,其深度可以是2-4096(4K);对I/O SQ/CQ,深度可以是2-65536(64K)。队列深度也是可以配置的。SQ/CQ的个数可以配置,每个SQ/CQ的深度又可以配置,因此NVMe的性能是可以通过配置队列个数和队列深度来灵活调节的。此外PCIe接口可以有1,2,4,8,12,16,32条lane,因此NVMe+PCIe的灵活性非常大。
每个SQ放入的是命令条目,无论是Admin还是I/O命令,每个命令条目大小都是64字节;每个CQ放入的是命令完成状态信息条目,每个条目大小是16字节。对SQ和CQ小结如下:
1 | - SQ用于Host发命令,CQ用于SSD回命令完成状态; |
SQ/CQ中的”Q”,是Queue,队列的意思,无论SQ还是CQ,都是队列,并且是环形队列。队列有几要素,除了队列深度,队列内容,还有两个重要的,就是队列的头(Head)和尾巴(Tail)。队列的头尾很重要,头决定谁会被马上服务,尾巴决定了新来的人站的位置。DB,就是用来记录了一个SQ或者CQ的Head和Tail。每个SQ或者CQ,都有两个对应的DB: Head DB和Tail DB。DB是在SSD端的寄存器,记录SQ和CQ的头和尾巴的位置。
上面是一个队列的生产/消费模型。生产者往队列的Tail写入东西,消费者往队列的Head取出东西。对一个SQ来说,它的生产者是Host,因为它往SQ的Tail位置写入命令,消费者是SSD,因为它往SQ的Head取出指令执行;对一个CQ来说,刚好相反,生产者是SSD,因为它往CQ的Tail写入命令完成信息,消费者则是Host,它从CQ的Head取出命令完成信息。
SQ、CQ、DB作用举例
1,开始假设SQ1和CQ1是空的,Head = Tail = 0.
2,这个时候,Host往SQ1中写入了三个命令,SQ1的Tail则变成3。 Host在往SQ1写入三个命令后,同时漂洋过海去更新SSD Controller端的SQ1 Tail DB寄存器,值为3。Host更新这个寄存器的同时,也是在告诉SSD Controller:有新命令了,需要你去取。
3,SSD Controller收到通知后,于是派人去SQ1把3个命令都取回来执行。SSD把SQ1的三个命令都消费了,SQ1的Head从而也调整为3,SSD Controller会把这个Head值写入到本地的SQ1 Head DB寄存器。
4,SSD执行完了两个命令,于是往CQ1中写入两个命令完成信息,同时更新CQ1对应的Tail DB 寄存器,值为2。SSD并且发消息给Host:有命令完成,请注意查看
5,Host收到SSD的短信通知,于是从CQ1中取出那两条完成信息处理。处理完毕,Host又漂洋过海的往CQ1 Head DB寄存器中写入CQ1的head,值为2。
整个过程中DB起到的作用:
- 记住了SQ和CQ的头尾。对SQ来说,SSD是消费者,它直接和队列的头打交道,很清楚SQ的头在哪里,所以SQ head DB由SSD自己维护;但它不知道队伍有多长,尾巴在哪,后面还有多少命令等待执行,相反,Host知道,所以SQ Tail DB由Host来更新。SSD结合SQ的头和尾,就知道还有多少命令在SQ中等待执行了。对CQ来说,SSD是生产者,它很清楚CQ的尾巴在哪里,所以CQ Tail DB由自己更新,但是SSD不知道Host处理了多少条命令完成信息,需要Host告知,因此CQ Head DB由Host更新。SSD根据CQ的头和尾,就知道CQ能不能以及能接受多少命令完成信息。
- 通知作用:Host更新SQ Tail DB的同时,也是在告知SSD有新的命令需要处理;Host更新CQ Head DB的同时,也是在告知SSD,你返回的命令完成状态信息我已经处理
Host对DB只能写SQ Tail DB和CQ Head DB,不能读取DB。
Host如何维护SQ和CQ?
SQ的尾巴没有问题,Host是生产者,对新命令来说,它清楚自己应该站在队伍哪里。但是Head呢?SSD在取指的时候,是偷偷进行的,Host对此毫不知情。Host发了取指通知后,它并不清楚SSD什么时候去取命令,取了多少命令。解决方法是SSD往CQ中写入完成状态信息(16 B)如下,其中附加了SQ Head DB的信息,这样Host就得到了SQ Head的信息。对于CQ,Host是消费者,知道Head,但是不知道Tail,SSD通过返回命令状态。一开始CQ中每条命令完成条目中的”P” bit初始化为0,SSD在往CQ中写入命令完成条目时,会把”P”写成1。记住一点,CQ是在Host端的内存中,Host可以检查CQ中的所有内容,当然包括”P”了。Host记住上次的Tail,然后往下一个一个检查”P”,就能得出新的Tail了。
总结,Host通过SSD写的CQ中的状态信息来获取SQ的Head以及CQ的Tail信息。