本页目录

NVMe 与存储

覆盖: NVMe 协议 (SQ/CQ/PRP/SGL) → Linux NVMe 驱动 → NVMe multipath → NVMe-oF → SCSI 子系统 (sd/sr) → device mapper (dm-crypt/LVM/dm-thin) → Bcache/dm-cache 内核版本: 3.3 ~ 6.x

NVMe 协议基础

队列模型

NVMe 使用 PCIe + 环形队列模型,替代 AHCI 的固定深度命令槽:

每对 SQ (Submission Queue) + CQ (Completion Queue) 构成一个 I/O 通道
  SQ: 主机 → 控制器 (host 写, 控制器读)
  CQ: 控制器 → 主机 (控制器写, host 读)

多队列优势:
  - 每个 CPU 核独享 SQ/CQ → 零锁竞争
  - 最大支持 64K 队列, 每队列 64K 命令 → 极高的并行度
  - 中断: MSI-X per-CQ → 每个队列独立中断 → IRQ affinity 天然绑核

命令格式

// include/linux/nvme.h
struct nvme_command {
    u8  opcode;        // NVME_CMD_READ, NVME_CMD_WRITE, NVME_CMD_FLUSH, ...
    u8  flags;
    u16 command_id;    // 在所属 SQ 内的唯一 ID

    u32 nsid;          // namespace ID

    // PRP (Physical Region Page) 或 SGL (Scatter-Gather List)
    __le64 prp1;       // 第一个物理页或 PRP list 指针
    __le64 prp2;

    // 逻辑块地址 (LBA) + 传输长度
    __le64 slba;       // Starting LBA
    __le16 nlb;        // Number of Logical Blocks (0-based)
};

PRP vs SGL:

  • PRP: 简单链表,每个 entry 指向一个物理页面。最多 2 个内联 + PRP list。适合小 IO
  • SGL: 通用 scatter-gather 列表,支持任意长度的段。NVMe 1.2+ / NVMe-oF 必需

Doorbell 机制

提交命令:
  1. host 写 NVMe command 到 SQ entry (环形缓冲区)
  2. host 写 SQ Tail Doorbell (MMIO 写, 通知控制器有新命令)
  3. 控制器取命令 → 执行 → DMA 数据 → 写 CQ entry
  4. 控制器发 MSI-X 中断
  5. host 写 CQ Head Doorbell (通知控制器 CQ entry 已消费)

整个流程: 1 次 MMIO 写提交, 0 次 MMIO 读 (CQ 在 host DDR 中)
对比 AHCI: 每次提交需要多轮 MMIO 读 (读 PxCI 等寄存器)
  → NVMe 延迟低 3~5x

Linux NVMe 驱动

驱动分层

// drivers/nvme/host/
// nvme-core.c:  协议实现 (admin queue, IO queue, identify, abort, ...)
// nvme.h:       数据结构 (nvme_command, nvme_completion)
// pci.c:         PCIe 传输层 (MMIO doorbell, MSI-X, PRP mapping)
// fabrics.c:     NVMe-oF 传输层 (TCP/RDMA/FC)

队列初始化流程

nvme_probe():从 PCIe 使能到 IO 队列就绪 nvme_probe() 使能 PCIe 设备 + 映射 BAR nvme_dev_map() 创建 Admin SQ/CQ(单对,用于管理命令) nvme_alloc_admin_tags() → blk_mq_init_queue() identify controller + namespace(通过 admin queue) nvme_identify_ctrl() → 获取队列数, 能力, 固件版本 创建 IO SQ/CQ · 根据 CPU 拓扑分配(num_possible_cpus() 或 nr_io_queues) · 每个 IO queue → 一个 blk-mq hardware context (hctx) · MSI-X 中断:request_irq() per-CQ blk_mq_alloc_tag_set() → 关联到 request_queue Admin queue 先行,拿到队列数与能力后才按 CPU 拓扑铺开 IO queue;每个 IO queue 独立 hctx + 独立 MSI-X 中断,为后续 IRQ affinity 绑核打基础。

queue_rq: 提交路径 (热路径)

// drivers/nvme/host/pci.c
static blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx,
                                    const struct blk_mq_queue_data *bd) {
    struct nvme_queue *nvmeq = hctx->driver_data;
    struct request *rq = bd->rq;

    // 1. 构造 NVMe command
    struct nvme_command *cmd = &nvmeq->sq_cmds[nvmeq->sq_tail];
    nvme_map_rq(rq, cmd);  // 构造 PRP/SGL + LBA + 长度

    // 2. 写 NVMe command 到 SQ
    //    (已在 SQ 环形缓冲区中, 不需要拷贝)

    // 3. 更新 SQ tail pointer
    nvmeq->sq_tail = (nvmeq->sq_tail + 1) & (nvmeq->q_depth - 1);

    // 4. 写 doorbell (MMIO write)
    writel(nvmeq->sq_tail, nvmeq->q_db);

    return BLK_STS_OK;
}

// 中断: nvme_irq()
//   → nvme_process_cq() → 遍历 CQ entries
//     → nvme_handle_completion() → blk_mq_complete_request()
//       → mq_ops->complete() → end_that_request_last()

NVMe Multipath

// drivers/nvme/host/multipath.c
// 同一 namespace 可经由多条 I/O 路径访问:
//   - NVMe-oF: 同一 subsystem 下多个 controller
//   - PCIe: 双端口 NVMe 硬盘

// nvme-core 自动聚合同一 ns 的多个路径为一个 block device
// IO 调度:
//   默认 round-robin (ANA: Asymmetric Namespace Access)
//   优化路径: I/O policy knob

NVMe-oF (NVMe over Fabrics)

将 NVMe 协议扩展到网络:
  NVMe/TCP:  标准 TCP 传输 (最通用)
  NVMe/RDMA: InfiniBand/RoCE (最低延迟, ~10μs)
  NVMe/FC:   光纤通道 (数据中心传统栈)

Linux 实现:
  target (server): drivers/nvme/target/ + nvmet-tcp/nvmet-rdma
  host:             drivers/nvme/host/fabrics.c + nvme_tcp/nvme_rdma

SCSI 子系统

// drivers/scsi/
// 三层架构:
//   Upper Level: sd (磁盘), sr (光驱), st (磁带)
//   Mid Level:   通用命令构造, 错误恢复 (eh_*)
//   Lower Level: 厂商 HBA 驱动
//
// 关键结构:
//   scsi_cmnd: 一条 SCSI 命令
//   scsi_host_template: HBA 驱动注册的操作
//   queuecommand(): 提交 SCSI 命令 → HBA

// 5.x+ SCSI 默认使用 blk-mq (scsi_mod.use_blk_mq=1)
// 将 SCSI tag 映射到 blk-mq tag → 统一队列管理

Device Mapper: 块设备虚拟化

// drivers/md/
// Device Mapper (DM) 框架允许创建虚拟块设备:

// 常用 DM target:
//   dm-linear:    线性映射 (LVM 的基础)
//   dm-crypt:    块设备加密 (LUKS)
//   dm-thin:     精简置备 (thin provisioning)
//   dm-cache:    SSD 缓存 HDD (dm-cache / dm-writecache)
//   dm-raid:     MD RAID 不再开发, DM RAID 接替
//   dm-multipath: 多路径 IO (SAN)

// CRYPT (dm-crypt):
//   密钥: LUKS header on disk → 含加密算法, key slots, PBKDF
//   数据路径: bio → encrypt/decrypt per sector → 提交到下层设备
cryptsetup luksFormat /dev/sda2
cryptsetup open /dev/sda2 cryptroot
dmsetup table  # 查看所有 DM 设备

Bcache: SSD 缓存 HDD

// drivers/md/bcache/
// Bcache 将 SSD 作为 HDD 的读/写缓存
// 
// 缓存模式:
//   writeback:    写入先到 SSD → 异步回写 HDD (高性能, 断电风险)
//   writethrough: 写入同时到 SSD 和 HDD (安全, 但写性能受限)
//   writearound:  写入绕过 SSD 直接到 HDD (SSD 仅用于读)

// 内核 6.x 原生支持 bcache (主线合入)

调试与观测

# NVMe 驱动信息
nvme list
nvme id-ctrl /dev/nvme0  # 控制器能力 (队列数, 支持的功能)
nvme id-ns /dev/nvme0n1  # namespace 详情

# NVMe 命令级追踪
echo 1 > /sys/kernel/debug/tracing/events/nvme/enable
cat /sys/kernel/debug/tracing/trace_pipe

# SMART 数据
nvme smart-log /dev/nvme0
# 关键指标: temperature, percentage_used (寿命), media_errors

# DM 设备
dmsetup ls --tree

# Bcache
cat /sys/block/bcache0/bcache/state
echo 1 > /sys/block/sda/bcache/writeback_rate_debug

# SCSI 层追踪
echo 1 > /sys/kernel/debug/tracing/events/scsi/enable

参考与延伸

  • 源码⁠: drivers/nvme/host/pci.c (PCIe NVMe 驱动), drivers/nvme/target/ (NVMe-oF), drivers/scsi/, drivers/md/, drivers/md/bcache/
  • NVMe 规范⁠: nvmexpress.org (base spec 1.4c/2.0)
  • 内核文档⁠: Documentation/nvme/, Documentation/device-mapper/
  • LWN: "The Linux NVMe driver", "Bcache design"

关键词: NVMe, SQ/CQ, doorbell, PRP, SGL, blk-mq, nvme_queue_rq, NVMe-oF, SCSI, device mapper, dm-crypt, Bcache