NVMe接口学习笔记1 基本概念
NVMe接口学习笔记2 寄存器
NVMe接口学习笔记3 队列和PRP/SGL
NVMe接口学习笔记4 其余数据结构
NVMe接口学习笔记5 管理命令(admin1)
NVMe接口学习笔记6 管理命令(admin2)
我们再复习一下NVMe的基本概念:
1)NVMe通过映射在Host的Memory里的寄存器来做最基础的配置
2)NVMe是通过队列和其中的命令并行处理的协议,从Intel的角度上,每个CPU core搭配一个NVMe IO命令提交队列是一种高效的做法
3)其队列是一个环装结构,为了减少每笔IO对于寄存器和内存的访问,其队列机制使用了Doorbell和Phase Tag的方法,从而最大程度的减小每笔IO指令的CPU cycle数
4)NVMe使用了PRP和SGL的方法来描述数据传输空间,PRP的优点是简单高效,而SGL的好处是灵活且功能强大,在NVMeoF的协议中,SGL是默认使用的数据空间描述方法。
5)NVMe可以灵活配置LBA的数据结构,可包含Protect info和meta data等数据
6)NVMe的几个基础操作对象,NVMe子系统>=NVMe Controller>=NVMe Namespace>=Logical Block Address(LBA),通过管理命令(Admin)可以获取到每一层级的参数配置,可以设置基础参数,为IO操作配置环境。
7)最后一部分就是NVMe对LBA单位的操作,这些操作被称为IO指令,也是作为host使用最多的指令。
所有前面这些操作,其实都是为了一个目标--发送接收IO命令
当CSTS.RDY被drive置位为1的时候,host可以向IO SQ中提交新的SQE,然后通过更新doorbell寄存器通知drive可以开始处理新的SQE。这些可以在IO queue处理的指令就是IO指令:
Flush指令,将缓存在drive中的buffer里的数据固化,这里可以指定Namespace或者所有Namespace。
Write指令,写入一个或者多个LBA。通过PRP或者SGL传输数据buffer的位置。
Read指令,读取一个或者多个LBA。通过PRP或者SGL传输数据buffer的位置。
Write Uncorrectable,这个指令是用来将指定的LBA标记为不可用的。写指令可以将这个uncorrectable的状态清除。
Compare指令,host会传输个buffer给drive,并且让drive比较输入的buffer里的数据和从Media上读取出来的数据,相当于合并了Read指令和一个比较指令,这个指令其实是在用Drive的计算能力来帮助host做事情,如果controller能够用硬件加速这一功能,在系统层面应该可以得到更好的效率。
Write Zeros指令,写零,这个指令相当于drive提供的一个硬件级别的MemSet接口。
Dataset Management指令,这个指令就是大家常说的Trim指令,在原本的HDD时代是没什么用处的,到了SSD时代,因为有GC的存在,老数据要从写满的block上搬移到空block上,造成了写放大,所以厂商专门为SSD设计了trim指令,这个指令的目的是大范围的失效LBA空间,如果硬盘上一段数据没有用了,HDD可以保留这些数据,直到下一次写覆盖,而SSD则需要在文件系统删除文件或者数据库删除数据的时候就调用Trim将数据映射失效,从而降低硬盘的写放大。为了实现高速高效的Dataset Management的功能,多数硬盘厂商都对这个功能进行了优化设计,在原有FTL table的32bits或者64bits映射一个LBA的基础上增加一个单bit映射有效或者无效的功能,从而实现全盘失效的加速。
Verify指令,不需要读取数据到host,需要drive根据数和Metadata检查数据完整性。
Reservation 系列指令,在后面的feature章节我们再学习。
了解了这些指令之后,我们再深入的了解一下每个指令都需要用到的一个域叫做namespace Identifier(NSID),这个ID是用来唯一指定一个namespace的ID。这个ID在全局与的NVMe系统中,是惟一的,可以不和实体的Namespace绑定,这个时间点它是UNallocated ID,如果通过Admin命令里的Namespace Management来创建一个namespace,那对于系统来说,就将状态改变为Allocated Namespace。
nvme create-ns /dev/nvme0 -s 976562500 -c 976562500 -f 0 -d 0 -m 0
在这之后host需要将一个创建出来的namespace附着在一个controller上,就用到了下面这个命令attach,这时候Namespace就变成了Active的状态。
nvme attach-ns /dev/nvme0 -n 1 -c 0
一个namespace有可能是Active+Allocated的可能是Inactive+Unallocated的,也有可能是Inactive+Unallocated的。
我们之前提到了IOcommand里面可以填的参数还有一个叫Fused Operation,这个选项也介绍过其功能,就是将两个command当做一个原子操作进行处理,并保证其先后顺序。
这里提到了指令的顺序问题,spec也明确表示,处理Fused operation能保证顺序之外,其他的指令,不管是是不是在一个队列中提交的指令,是不是按照先后顺序提交的,drive都不能保证顺序。
这里举例说明,如果一笔读和一笔写同时命中了同一个LBA,那么drive是不需要保证完成的时间的,如果host需要保证顺序,那么需要host从更高的层级上自己保证。
原子操作
NVMe系统可以保证一些指令在处理数据的时候的最小单位原子性,比如有两笔写命中了同一个LBA range,那么如果host再读这个区域的数据,有可能拿到的是第一笔里的数据,也有可能是第二笔的数据,还有可能是混合数据,如果是混合数据,那么可能混合到什么程度?
这里系统给出了一个单位最小值,从而保证在任何情况下,一个最小单位内的数据一定是同一笔读或者同一笔写产生的,
举个例子我的当前namespace的写单位最小是8KB,那么只要是大于8KB的写,这一部分的数据一定是生效或者不生效,不会出现4KB生效,4KB不生效的情况。
这里有个官方的例子,描述如下,图351中的每个小格是512B的一个LBA,现在系统中设置的原子操作最小值是2KB(4个LBA),现在host写入了一个command A,从LBA0-LBA3,又写入了一个Command B从LBA1-LBA4,那么根据前面的定义,这里只有两种可能的case就是A覆盖了B的数据或者B覆盖了A的数据,没有可能是AB交互混杂的数据。
End-to-end Protection Information
端到端数据保护,每个LBA有8Bytes数据用来做数据校验和保护,放置的位置可以和数据在一起,也可以和metadata一起放在其他地方。
总结:
到达这里我们可以认为我们已经基本入门了NVMe协议,当和别人聊起一些基本概念的时候,不会冒出这TM是啥?这TM又是啥?的疑问了,但是这还远远不够。想要了解更多吗?让我们一起看看Linux的NVMe驱动是怎么写的吧。
参考文章:
https://blog.csdn.net/weixin_40343504/article/details/82386024