NVME概述
Non-Volatile Memory Express,非易失性内存主机控制器接口规范。
NVME像SATA和SAS一样,定义了硬件接口和传输协议。
几个关于存储的概念的对应关系:
尺寸外形 | 接口 | 协议 |
---|---|---|
2.5寸或3.5寸:在SFF标准中定义 | SATA接口 | AHCI或ATA协议 |
M.2和PCIe:在PCI-SIG标准中定义 | PCIe接口 | NVME协议 |
SAS和FC接口:仅用于服务器和数据中心 |
- 判断一个PCI设备是NVMe控制器:class code是1,subclass code是8。
- 访问NVMe设备的寄存器:通过BAR0。
- NVMe控制器执行命令的次序是由自己决定的。
- 重置后只有一个提交队列,一个完成队列。这两个队列就是管理队列。驱动程序会在ASQ和ACQ寄存器设置它们的基址。
- 管理队列可以处理管理器命令,如创建I/O队列、检索控制器和驱动的信息。
- 管理队列的id是0。
BAR0里的寄存器
offset(字节) | 大小(bit) | 名字 | 作用 |
---|---|---|---|
0x0 | 64 | CAP | 控制器capabilities |
0x8 | 32 | VS | 版本 |
0xc | 32 | INTMS | 中断掩码设置 |
0x10 | 32 | INTMC | 中断掩码清空 |
0x14 | 32 | CC | 控制器配置 |
0x18 | 32 | 保留 | |
0x1c | 32 | CSTS | 控制器状态 |
0x20 | 32 | 保留 | |
0x24 | 32 | AQA | 关于管理队列的属性 |
0x28 | 64 | ASQ | 管理队列之提交队列 |
0x30 | 64 | ACQ | 管理队列之完成队列 |
NVMe基本原理
NVMe设备称为Controller(控制器),主机称为Host。主机和控制器之间通过共享内存队列实现交互。
NVMe队列
NVMe队列从功能上可分为2种:
- 管理队列(Admin Queue):用于管理,仅一个
- 命令队列(Command Queue):最多可以有65535个
NVMe队列从发送方向上分也可分为2种:
主机向NVMe发送命令使用提交队列(Submission Queue)。
NVMe控制器会执行提交队列上的命令。
提交队列里一个条目有64字节,按双字排列:
双字 内容 0 命令(格式:0-7opcode, 8-9fused操作,10-13保留,14-15PRP或SGL选择,16-31命令ID) 1 NSID(命名空间的ID) 2,3 保留 4,5 元数据指针 6~9 数据指针。两个PRP。PRP是64位的物理地址,意思是数据是传进或传出内存。 10~15 命令相关 NVMe设备向主机反馈命令的执行情况使用完成队列(Completion Queue)。
当创建提交队列时就会指定完成队列。
完成队列一个条目有16字节:
比特 大小(bit) 内容 0-31 32 命令相关 32-63 32 保留 64-79 16 提交队列的头指针 80-95 16 提交队列的ID 96-111 16 命令ID 112 1 阶段位。当写条目的时候会切换。 113-127 15 状态字段。0表示成功。
提交队列和完成队列的实体是一个内存区域,从结构上看是一个环形缓冲区。
NVMe命令处理流程
NVMe使用了门铃机制(Doorbell)。每个队列都有一个门铃指针。
- 对于发送队列,门铃指针表示的是发送队列的尾指针。驱动程序把命令写入发送队列后,此队列的尾指针寄存器被更新,控制器端从而知道有新命令到来。
- 当完成队列上有可用的命令,NVMe控制器通过中断机制(INTx, MSI或MSIx)通知主机端。主机端上的驱动程序会处理队列里的新条目,然后更新当前队列的头指针寄存器。
NVMe命令的格式
位置(字节) | 内容 |
---|---|
0~7 | 命令编号(代表一个具体的命令)+命名空间的编号(命令发到哪个命令空间) |
8~15 | 保留 |
16~23 | 元数据指针 |
24~31 | 数据指针1 |
32~39 | 数据指针2 |
40~47 | 命令10+命令11 |
48~55 | 命令12+命令13 |
56~63 | 命令14+命令15 |
命令编号:
大小 | 名字 | 含义 |
---|---|---|
2字节 | CID | |
2比特 | PSDT | 存储数据在内存的组织形式 |
4比特 | 保留 | |
2比特 | FUSE | 本命令是普通命令还是复合命令 |
1字节 | OPC | 操作码 |
命令
管理命令
作用 | 命令(双字0) | NSID(双字1) | 数据指针(双字6~9) | 命令相关(双字10~15) |
---|---|---|---|---|
创建提交队列 | opcode:0x01 | 队列的基址:双字6,7 | 双字10:低字队列ID,高字队列大小 双字11:低字flag,高字是对应完成队列的ID |
|
创建完成队列 | opcode:0x05 | 队列的基址:双字6,7 | 双字10:低字队列ID,高字队列大小 双字11:低字flag,高字是中断向量 |
|
identify | opcode:0x06 | 如双字10 identify对象是命名空间, 则设置为命名空间的ID |
输出的基址(单个页):双字6,7 | 双字10的低字节:identify的对象,0命名空间,1控制器,2命名空间列表 |
IO命令
作用 | 命令(双字0) | NSID(双字1) | 数据指针(双字6~9) | 命令相关(双字10~15) |
---|---|---|---|---|
读 | opcode:0x02 | 包含NSID | 包含用于数据传递的PRP列表 | 双字10~11:起始LBA 双字12:低字是要传输的块数量 |
写 | opcode:0x01 | 包含NSID | 包含用于数据传递的PRP列表 | 双字10~11:起始LBA 双字12:低字是要传输的块数量 |