NVMe是一个针对基于PCIe的固态硬盘的高性能的、可扩展的主机控制器接口。
NVMe的显著特征是提供多个队列来处理I/O命令。单个NVMe设备支持多达64K个I/O 队列,每个I/O队列可以管理多达64K个命令。
当主机发出一个I/O命令的时候,主机系统将命令放置到提交队列(SQ),然后使用门铃寄存器(DB)通知NVMe设备。
当NVMe设备处理完I/O命令之后,设备将处理结果写入到完成队列(CQ),并引发一个中断通知主机系统。
NVMe使用MSI/MSI-X和中断聚合来提高中断处理的性能。
NVMe驱动是一个C函数库,可直接链接到应用程序从而在应用与NVMe固态硬盘之间提供直接的、零拷贝的数据传输。这是完全被动的,意味着不会开启线程,只是执行来自应用程序本身的函数调用。这套库函数直接控制NVMe设备,通过将PCI BAR寄存器直接映射到本地进程中然后执行基于内存映射的I/O(MMIO)。I/O是通过队列对(QP)进行异步提交,其一般的执行流程跟Linux的libaio相比起来,并非完全不同。
NVMe协议
NVMe概述
NVMe是一个针对基于PCIe的固态硬盘的高性能的、可扩展的主机控制器接口。
NVMe的显著特征是提供多个队列来处理I/O命令。单个NVMe设备支持多达64K个I/O 队列,每个I/O队列可以管理多达64K个命令。
当主机发出一个I/O命令的时候,主机系统将命令放置到提交队列(SQ),然后使用门铃寄存器(DB)通知NVMe设备。
当NVMe设备处理完I/O命令之后,设备将处理结果写入到完成队列(CQ),并引发一个中断通知主机系统。
NVMe使用MSI/MSI-X和中断聚合来提高中断处理的性能。
NVMe驱动概述
NVMe驱动是一个C函数库,可直接链接到应用程序从而在应用与NVMe固态硬盘之间提供直接的、零拷贝的数据传输。这是完全被动的,意味着不会开启线程,只是执行来自应用程序本身的函数调用。这套库函数直接控制NVMe设备,通过将PCI BAR寄存器直接映射到本地进程中然后执行基于内存映射的I/O(MMIO)。I/O是通过队列对(QP)进行异步提交,其一般的执行流程跟Linux的libaio相比起来,并非完全不同。
NVM Express(NVMe)是一个寄存器级接口,允许带内主机软件与NVM子系统通信。NVMe管理界面(NVMe-MI)允许管理控制器通过一个或多个外部接口与NVMe NVM子系统进行带外通信。
NVMe是一种Host与SSD之间通讯的协议
图1:NVMe管理接口协议分层
NVMe有三宝:Submission Queue (SQ),Completion Queue(CQ)和Doorbell Register (DB)。 SQ和CQ位于Host的内存中,DB则位于SSD的控制器内部。
SQ和CQ在Host的memory中以及DB在SSD端,上图中的NVMe Subsystem一般就是SSD。
SQ位于Host内存中,Host要发送命令时,先把准备好的命令放在SQ中,然后通知SSD来取;CQ也是位于Host内存中,一个命令执行完成,成功或失败,SSD总会往CQ中写入命令完成状态。
DB又是干什么用的呢?Host发送命令时,不是直接往SSD中发送命令的,而是把命令准备好放在自己的内存中,那怎么通知SSD来获取命令执行呢?Host就是通过写SSD端的DB寄存器来告知SSD的。
NVM Express基于配对的提交和完成队列机制
-
命令由主机软件放入提交队列。完成被放入控制器关联的完成队列。多个提交队列可以使用相同的完成队列。提交和完成队列在内存中分配。
-
存在管理员提交和关联的完成队列以用于控制器管理和控制(例如,创建和删除I / O提交和完成队列,中止命令,等等)。只有属于管理员命令集的命令才可以提交给管理员提交队列。
-
I / O命令集与I / O队列对一起使用。该规范定义了一个I / O命令集,命名为NVM命令集。主机选择一个用于所有I / O队列的I / O命令集对。
主机软件创建队列,最高可达控制器支持的最大值。通常的数量创建的命令队列基于系统配置和预期的工作负载。例如, 在基于四核处理器的系统上,每个核心可能有一个队列对,以避免锁定和确保数据结构在适当的处理器核心缓存中创建。
NVME命令
命令执行: 1. Host写命令到SQ 2. Host更新SQ的TailDB, 通知SSD取命令 3. SSD收到命令,于是从SQ中取出命令 4. SSD执行命令 5. 命令执行完成后,SSD往CQ中写入命令执行结果,同时修改CQ的TailDB 6. SSD发短信通知Host命令已经执行完成 7. Host收到命令后,到CQ中查看命令完成状态 8. Host处理完CQ中的命令执行结果,更新CQ中的HeadDB, 回复SSD, "命令执行结果已经处理完毕"
NVMe有两种命令,制定了Host与SSD之间通讯的命令,以及命令如何执行的。 一种叫Admin Command,用以Host管理和控制SSD; 一种就是I/O Command,用以Host和SSD之间数据的传输。
Admin 指令
Admin指令与NVM指令根据放置的的队列组(Queue Pair)来区分,Admin指令在Admin CQ与SQ里,NVM指令在I/O CQ与SQ里。
通过Dword0中的8位操作码定义不同指令,注意并不是绝对的顺序增加(eg,没有03h)。每一种指令都对应有其完成命令,通过SQID(提交队列ID)+CID(命令ID)唯一标识完成的命令。
操作码 | 指令 | 作用 |
00h | 删除I/O SQ, | 释放SQ空间 |
01h | 创建 I/O SQ, | 保存host分配给SQ的地址、队列优先权、队列大小 |
02h | 获取日志, | 返回所选日志页于缓冲区 |
04h | 删除 I/O CQ, | 释放CQ空间 |
05h | 创建 I/O CQ, | 保存host分配给CQ的地址、中断向量、队列大小等 |
06h | Identify | 返回关于controller与namespace能力和状态的数据结构(2k字节) |
08h | 撤销, | 用来撤销之前完成的指令,best-effort |
09h | 设置features | 根据FID设置相应的features |
0Ah | 获取 features, | 根据FID返回队列数量、仲裁信息等 |
0Ch | 异步事件请求, | Controller向host报告运行信息(error or health) |
10h | 固件激活, | 验证下载的镜像,提交到Firmware Slot(1-7)中 |
11h | 固件镜像下载, | 下载固件镜像 |
NVM指令
与Admin指令结构完全相同,也是通过Dword0中的8位操作码来定义不同指令。
操作码 | 指令 | 作用 |
00h | Flush | 将数据(和元数据)提交到NVM中,所有命令都要执行 |
01h | Write | 将数据(和元数据)写入NVM中 |
02h | Read | 读NVM中的数据(和元数据) |
04h | Wirte Uncorrectable | 标记无效数据块 |
05h | Compare | 比较从NVM端读出的数据和比较数据缓冲区的数据 |
09h | Dataset Management | 标识一定范围数据的特点,eg,频繁读、频繁写(提升性能) |
寄存器
寄存器定义
NVMe寄存器主要分为两部分,一部分定义了Controller整体属性,一部分用来存放每组队列的头尾DB寄存器。
- CAP——控制器能力,定义了内存页大小的最大最小值、支持的I/O指令集、DB寄存器步长、等待时间界限、仲裁机制、队列是否物理上连续、队列大小;
- VS——版本号,定义了控制器实现NVMe协议的版本号;
- INTMS——中断掩码,每个bit对应一个中断向量,使用MSI-X中断时,此寄存器无效;
- INTMC——中断有效,每个bit对应一个中断向量,使用MSI-X中断时,此寄存器无效;
- CC——控制器配置,定义了I/O SQ和CQ队列元素大小、关机状态提醒、仲裁机制、内存页大小、支持的I/O指令集、使能;
- CSTS——控制器状态,包括关机状态、控制器致命错误、就绪状态;
- AQA——Admin 队列属性,包括SQ大小和CQ大小;
- ASQ——Admin SQ基地址;
- ACQ——Admin CQ基地址;
- 1000h之后的寄存器定义了队列的头、尾DB寄存器。
寄存器理解
- CAP寄存器标识的是Controller具有多少能力,而CC寄存器则是指当前Controller选择了哪些能力,可以理解为CC是CAP的一个子集;如果重启(reset)的话,可以更换CC配置;
- CC.EN置一,表示Controller已经可以开始处理NVM命令,从1到0表示Controller重启;
- CC.EN与CSTS.RDY关系密切,CSTS.RDY总是在CC.EN之后由Controller改变,其他不符合执行顺序的操作都将产生未定义的行为;
- Admin队列有host直接创建,AQA、ASQ、ACQ三个寄存器标识了Admin队列,而其他I/O队列则有Admin命令创建(eg,创建I/O CQ命令);
- Admin队列的头、尾DB寄存器标识为0,其他I/O队列标识由host按照一定规则分配;只有16bit的有效位,是因为队列深度最大64K。
性能提升
如果要完全释放NVMe SSD的IOPS性能,设计人员要先做好评估呢,因为当队列深度达到一定程度后,NVMe SSD的IOPS才会达到最佳。如下图(来自Tom'Hardware),六块NVMe SSD均在队列深度128以上才达到最佳的性能。
NVMe白皮书中对企业级SSD和消费级SSD设计时需要的队列深度的建议是:
企业级SSD: 16~128 Queues;
消费级SSD: 2-8 Queues.