NVMe协议详解(二)

host与SSD交互流程

host在给SSD加载驱动(linux 内核驱动)后,将在host ddr上建立admin queue和I/O queue,如下图所示。
在这里插入图片描述

注意看上图,每个CPU core上一般只会创建一个I/O,这是SSD的硬件I/O,而我们在linux系统用户态使用FIO创建的queue是block层的软件queue,也就是说,block层上可能多个软件queue对应一个SSD的I/O。

下面所说的I/O queue都是指SSD的硬件I/O,host提交I/O请求过程如下图所示。
在这里插入图片描述

① Host在DDR内, 按照SQE的格式准备好命令, 塞进SQ buff中
② Host敲Controller的SQ TAIL Doorbell,通知SSD对应SQ有新的请求
③ Controller来host DDR对应的SQ buff中取走命令并开始解析和执行
④ 如果该命令需要传输数据, Controller则从Host DDR拿数据或者将数据送给Host DDR
⑤ 命令结束后, Controller需要向Host回一条CQE
⑥ Controller继续向Host发一个中断,一般是MSI-X中断
⑦ Host收到中断后从CQ buff中拿到这条CQE处理
⑧ Host处理完该CQE后往Controller敲CQ Head Doorbell通知SSD

队列空与队列满

admin queue和I/O queue都有SQ,那么对于host和SSD来说,如何知道SQ是空还是满呢?
在这里插入图片描述

协议规定当SQ的head等于tail,认为SQ是空的。这里的tail是由host维护的,其实就是SQ pi,当host向SQ中提交SQE后,将使tail加1,然后将tail写入到SSD的BAR空间(SQyTDBL)。SSD手上握着SQ的head,其实就是SQ ci,当ci与tail相等时,就认为SQ为空。
在这里插入图片描述
协议规定当SQ的tail与head满足以下公式时,认为SQ是满的。

[(tail + SQ深度) - head] % (SQ深度) = (SQ深度 - 1)

假设SQ深度为64。那么当SQ tail为63、head为0时,则认为SQ满。当SQ tail为0、head为1时,则也认为SQ为0,其实就是tail加到63后翻转到起始0了。

也就说SQ中最多能提交SQ深度减1个SQE。

这里提出一个问题,host如何知道SSD手中握的head到哪了?不然host怎么知道SQ是空是满呢?带着这个问题往下看。

说完SQ,我们再来说说CQ,CQ是怎么知道队列空和满的呢?

SSD加驱动时候host手中握着head,其实就是ci,SSD中握着tail,其实就是pi。对于SSD来说,tail和head都知道(host会将head写入CQyHDBL),所以很容易知道CQ是空是满,但对host来说,明面上只知道head,而tail需要借助CQE中一个特殊标志——phase tag。来看看CQE的格式,如下图所示。
在这里插入图片描述
DW3中的P bit位就是phase tag。初始化时,host将手中握着的phase tag设为0,而SSD则将手中握着的phase tag设置为1。当SSD处理完SQE产生CQE并将手中握着的phase tag(1)填入CQE中,然后将整个CQE写入到host DDR中,产生一个中断通知host。host接收到中断后,根据head位置查看下一个CQE的phase tag是否和host手中phase tag(0)一致,这样就知道CQ的tail到哪了。

前面提到host是怎么知道SQ的head到哪了,其实就是靠CQE中的SQ head pointer字段(DW2)。

数据描述结构

SQE DW0中有个字段用来描述数据的组织形式,即PSDT,当其为0时,表示使用PRP描述结构,当其为01b或10b时,表示使用SGL描述结构,如下图所示。
在这里插入图片描述
SQE中第24到39字节用于填充PRP或SGL,如下图所示。
在这里插入图片描述

PRP——Physical Region Page Entry and List

需要注意的是,在NVMe over PCIe中,admin queue只能使用PRP结构来描述数据组织形式,而I/O queue则两者都可以使用。

PRP有3中组织可能,如下。

1)PRP1指向的数据物理内存页,正好能放下数据长度len。
在这里插入图片描述
假设一个page大小为4k,如果请求数据长度len加上页内偏移offset小于一个page大小(4k),那么只需要使用PRP1即可完整描述数据位置,此时PRP2为0。

2)PRP1与PRP2指向的两个物理内存页,正好能放下数据长度len。
在这里插入图片描述
PRP1与PRP2指向的内存页中有效数据长度正好为len,注意,此时PRP2的页内偏移offset必须为0。

3)PRP1与PRP2指向的两个物理内存页,无法放下数据长度len。

也就是说,PRP1的offset加上数据长度len大于两个页,此时,就无法使用PRP1与PRP2直接指向数据内存页,所以协议规定当出现这种情况时,PRP2所指向的就不是数据内存页了,而是PRP list所在的内存页,如下图所示。
在这里插入图片描述
通过PRP2找到PRP list,然后再根据PRP list中的entry进一步找到真正的数据物理内存,注意当PRP2指向的是PRP list时,PRP2的offset可以非0,但必须8betye对齐。

SGL——Scatter Gather List

SGL list的类型如下图所示。
在这里插入图片描述

0:表示这个SGL Descriptor是描述数据的
1:表示这个SGL Descriptor是描述host不需要的数据的,举个栗子(炭烤栗子 -。- ),host下发命令说从LBA 100开始读4个NLB(0-3),然后使用这个描述说第2个NLB不需要放到DDR上,实际上只读了3个NLB
2:表示这个SGL Descriptor是描述下一组SGL Descriptor的,其实SGL list就是一小块一小块内存链起来的,所以小块内存的尾部需要一个描述符来指向下一小块内存
3:这个SGL Descriptor还是用来指向下一组SGL Descriptor的,只不过还多了一种含义,就是告诉SSD这是倒数第二组SGL Descriptor,下一组之后就没了
4:也是用来描述数据的,但是除了数据之外还多一个密钥key,这个SGL Descriptor是用于NVMe over fabric的,举个栗子(糖炒栗子 -.-),以NVMe over RDMA为例,initiator端发送一条命令给target,使用这个描述符描述数据在地址addr上,长度为len,这块内存区域的密钥为key,那么target将通过RDMA read将数据读过来
5:传输层SGL Descriptor,笔者了解不多,跳过吧

这样讲解一下可能还是难以理解,还是放一张官方的示例图吧。
在这里插入图片描述
可以看到上面有三小块内存也就是三组SGL Descriptor,前两组的最后一个SGL Descriptor都指向下一组SGL Descriptor,倒数第二组的最后一个SGL Descriptor类型为3,表示后面只剩一组SGL Descriptor了(这一组至少有一个SGL Descriptor)。可以看到第二组SGL Descriptor中有个类型为1的SGL Descriptor,它描述2KiB数据不需要写到host提供的内存中,为什么要这样玩?因为SSD的读命令中只有start LBA和NLB,所以对于上面的示例,如果不支持类1的SGL Descriptor的话,那就只能将一笔读命令拆成两笔读命令下发。

SGL除了大类之外还有sub type,如下图所示。
在这里插入图片描述
对于NVMe over PCIe来说,这个sub type是保留的,或者说默认值为0。sub type主要服务于NVMe over fabric,举个栗子(蜂蜜栗子 = = ),以NVMe over RDMA为例,type类型为0时,那么sub type必须为1,表示数据就在nvme命令的尾部,偏移为offset,简单来说,就是initiator使用RDMA发送一个send,其数据前64byte是nvme命令,64byte后面是真正的数据。type类型为4时且sub type为0时,表示数据还在远端,地址为addr、长度为len、密钥为key,需要target使用RDMA read把数据读回来。

每种类型的SGL Descriptor详细格式请参考NVMe协议,这里就不展开了。

咋办,越来越懒了,文章懒得写 = 。 = 给个点赞支持一下呗

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值