NVMe 与存储

本页目录

同一 namespace 可以有多条路径,一次命令完成也可能仍依赖易失缓存。理解 NVMe,要把存储对象、队列协议、数据地址和恢复状态分别定位。

查阅目标阅读位置
controller、namespace 与 SQ/CQ第 1~2 节
LBA、PRP 与恢复第 3~4 节
多路径、DM 与缓存模式第 5~8 节

1. Controller、namespace 与 subsystem

controller 提供主机访问接口及管理能力;namespace 提供逻辑块地址空间;subsystem 可以包含多个 controller 并提供共享 namespace。namespace 不是必然对应一块独立 NAND 芯片,也不能简单等同于分区。

一个 namespace 可以被不同路径访问,是否能识别为同一存储对象,要结合 subsystem、NSID 和 UUID/NGUID/EUI 等标识及共享属性,而不是只比较容量或 /dev 名字。路径消失后设备编号可能变化,持久定位应使用正确的稳定标识。

Identify 返回能力和格式等信息,实际 I/O 队列资源还需要协商与创建。控制器支持上限、驱动实际使用数量和工作负载当前并发是三种不同值。

2. SQ/CQ:两个生产消费协议

PCIe NVMe:命令环与完成环分别推进 主机写 SQ 命令与 DMA 数据地址 控制器执行 取命令并传输数据 控制器写 CQ CID、状态、phase 等 SQ tail 通知 主机处理完成 检查状态并归还 CQ 空间 完成后才允许回收命令所引用的数据资源 中断可合并,也可轮询;CQ 消费与命令执行不是同一个动作。

PCIe 路径中,主机把命令写入 submission queue,按协议通知控制器新的 tail;控制器执行后将 completion queue entry 写回,主机处理并推进 CQ head。

一个 SQ 关联一个 CQ,协议允许多个 SQ 共享一个 CQ;Linux 驱动常用成对组织方式,但不是协议强制一一对应。MSI-X 向量也不必与每个队列一一绑定,轮询队列可能不用中断。队列数量由硬件、驱动、CPU 拓扑和资源共同决定,不保证每 CPU 独占且完全无锁。

CQ 的 phase tag 帮助区分环形槽位在当前轮次是否已经被控制器写成有效完成。主机发现有效状态后,还要按 DMA/内存顺序规则读取其余字段。命令可以乱序完成,通过 SQ 标识与 command identifier 等定位请求,不能只假设最早提交的先结束。

让完成队列填满并消费一圈,再回到槽 0。图中故意保留消费过的 CID 字节;决定它是否有效的是当前 head 与期待 phase,而不是槽位看起来是否非空。

正在呈现知识画面
槽位里仍有旧字节,为什么不算新完成?

主机在当前 head 比较 CQE phase 与期待 phase,匹配才是这一轮可消费的完成。回绕改变期待值,因此旧字节不会被误认为新一轮完成。

为什么 doorbell 不能写成固定“一次 MMIO”

命令发布前要先保证相关写入可被设备按协议看到。主机可批量发布多个 SQ entry 再通知,也会更新 CQ head;doorbell buffer、轮询与中断合并等机制还会改变访问频率。

所以 NVMe 可以降低控制开销,但不存在跨平台统一“每 I/O 恰好一次 MMIO、比 AHCI 固定快五倍”的推论。完成延迟还受闪存、缓存、队列等待、CPU、NUMA 和电源状态影响。

3. LBA、NLB 与 PRP 的具体例子

以 4096 字节逻辑块为例,从 LBA 256 读取 8192 字节:

字段或计算结果
起点字节偏移256 × 4096 = 1 MiB
逻辑块数量8192 / 4096 = 2
命令 NLB零基计数,填写 1

先确定 namespace 格式,才能正确解释命令单位。

这与 Linux 块层的 512 字节 sector 单位不同,驱动必须转换。真实 struct nvme_command 包含不同命令格式的联合体,字段并不是所有命令通用的同一串平铺成员;多字节字段按协议小端编码。

PRP 描述的是控制器可访问的数据地址

控制器页大小 4096 字节,起始偏移 1024,总长度 8192,需要三张页:

数据段可放字节
第一页剩余部分4096 − 1024 = 3072
第二页4096
第三页剩余数据1024

因此第一页之后还需要两页,接下来才能判断 PRP2 的含义。

  • PRP1 指向第一段起点,允许相应页内偏移。
  • 若剩余数据只需一页,PRP2 可直接指向该数据页。
  • 本例剩余需要两页,因此 PRP2 指向 PRP list,由列表给出后续页地址。

PRP list 不是“PRP2 永远是第二个数据指针”,列表跨页时又有链接规则。地址通过 DMA API 建立,不能把名字里的 Physical 理解成可直接填 CPU 虚拟地址或未经转换的物理地址。

SGL 使用带类型和长度等信息的描述符,支持能力依命令集、controller 与 transport 不同。NVMe/RDMA、NVMe/TCP 的数据传输也不能原样套用 PCIe 主机内存 PRP 机制。阅读入口:NVM Express 规范及内核 include/linux/nvme.h。

4. Linux 提交与恢复路径

drivers/nvme/host/core.c 负责共享协议和 controller/namespace 生命周期,pci.c 处理 PCIe 传输,tcp.c、rdma.c、fc.c 等处理各自传输,fabrics.c 提供 fabrics 共用部分。

PCIe 初始化通常先使设备和管理队列可用,再获取能力、协商资源、创建 I/O 队列并扫描 namespace。探测和恢复中有异步 work,不能把某个版本函数名列表视为永远同步执行的固定顺序。

提交 request 时,驱动设置命令、映射数据、处理资源失败,再把命令发布给 controller。队列环绕不能未经证明就用 index & (depth - 1):这种位运算只对二次幂深度成立。

超时说明主机尚未确认完成,不证明设备没有执行写入。驱动会根据状态实施 abort、reset、重连或失败处置,并处理旧请求与迟到完成;上层不能据“重试成功”推断旧副作用完全没发生。完成仍要区分普通写缓存、Flush 与 FUA 的持久化语义。

5. 原生 multipath 与 ANA

Linux 原生 NVMe multipath 在满足标识和配置条件时,把多个 controller 的 namespace 路径汇聚成一个逻辑访问对象。ANA 描述路径的访问状态,例如 optimized 与 non-optimized;主机据此及 I/O policy 选择路径。

NUMA、round-robin、queue-depth 等策略在支持它们的版本中各有用途,不能把默认策略写死为 round-robin;实际状态应读取运行系统。路径选择也不是 I/O elevator 的另一名字:前者选访问路径,后者安排队列中的请求。

多路径提高路径冗余,不自动提供数据副本、备份或跨独立磁盘复制。故障恢复期间请求可能排队或失败,其时延受控制器和连接恢复策略影响。见 NVMe multipath。

NVMe/TCP、RDMA、FC 将 NVMe 访问扩展到相应传输;网络拥塞、认证/加密配置、重连和 target 后端都会改变性能与可靠性,不应给 RDMA 写一个脱离条件的固定微秒延迟。

6. SCSI 与 device-mapper 在哪里

SCSI 上层驱动包括磁盘 sd、光驱 sr、磁带 st;中层处理通用命令管理和错误恢复;低层驱动连接 HBA 或具体传输。SATA 经 libata 等路径也可能在 Linux 中呈现为 SCSI 磁盘,所以 /dev/sdX 不能单凭名字判断物理总线。

device-mapper 按 target 将逻辑块地址映射到下层设备,可组合多层:

Target/机制作用不提供的保证
linear拼接或偏移映射不自动增加冗余
crypt块加密不单独解析 LUKS 格式,也不默认认证所有篡改
thin / thin-pool按需分配与共享块快照逻辑容量不等于已经保留物理空间
cache快设备缓存慢设备的块dirty 数据仍依赖缓存设备存活
writecache特定持久介质上的写缓存不是通用读写缓存的改名
multipath将多条路径汇聚不等于 RAID 副本
raid在 DM 中使用相应 RAID 机制不能据此声称 MD RAID 已停止开发

LVM 是用户态卷管理系统,可配置多种 DM target,不仅是“dm-linear 命令包装”。MD、DM RAID 与 LVM 的接口和管理关系需要分清。

Thin pool 为何要同时监控数据和元数据

thin volume 先声明逻辑容量,实际写入才分配数据块;快照共享块,后续写入可能触发分配和映射更新。数据池满与元数据池满是不同故障,可能进入排队、只读或错误路径,具体行为由配置和状态决定。

快照仍依赖原 pool 的数据和元数据,不等于独立备份。文件删除也不保证 pool 立即回收空间,还要看 discard、快照引用和目标支持。见 thin provisioning。

7. 缓存模式改变故障时数据在哪里

缓存模式确认时需要关注的数据位置
writeback最新版本可能只在缓存设备,后端仍旧
writethrough按模式完成后端写入后确认
writearound写入绕过缓存,失效规则依实现

模式改变故障时的恢复依据,不能只比较正常运行时的速度。

不是“SSD 就安全、HDD 就危险”,而是要确定 dirty 数据、映射元数据及 flush 约定的持久性。具有断电保护的缓存设备能缓解某些断电风险,但设备本身损坏仍可能丢失尚未回写的数据。

dm-cache、dm-writecache 和 bcache 是不同实现,管理接口与迁移/拆除流程不通用。bcache 也不是 bcachefs 的缩写。参见 dm-cache、bcache。

8. 只读观察与定位

lsblk -o NAME,TYPE,SIZE,LOG-SEC,PHY-SEC,PKNAME,MOUNTPOINTS

安装 nvme-cli 后,可用 nvme list、nvme list-subsys 查看对象和路径,nvme id-ctrl / id-ns 查看指定对象能力。SMART 的 percentage_used 是估计耐久消耗,不是“剩余寿命倒计时”;错误计数需要结合变化趋势和具体错误解释。

从应用看到 I/O 错误时,依次确认逻辑卷、DM/RAID、namespace/path、controller 和下层设备。不要把同一块数据在多个堆叠层的统计重复累加成物理吞吐。块层测量与完成语义见块设备层,密钥层次见Crypto API 与存储加密。