---
title: NVMe 与存储
url: https://doc.liz6.com/linux-kernel/07-block-device-layer/02-nvme-and-storage
locale: zh
area: linux-kernel
tags:
- linux-kernel
- 块设备层
date: 2026-06-30
modified: 2026-07-11
description: '覆盖: 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 ~…'
---

# 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 天然绑核
```

### 命令格式

```c
// 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 驱动

### 驱动分层

```c
// 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)
```

### 队列初始化流程

<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="nvme_probe 队列初始化流程">
  <defs><marker id="nvah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="420" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">nvme_probe():从 PCIe 使能到 IO 队列就绪</text>

  <rect x="260" y="44" width="200" height="26" rx="8" fill="#4f46e5"/>
  <text x="360" y="61" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">nvme_probe()</text>
  <line x1="360" y1="70" x2="360" y2="79" stroke="#475569" stroke-width="1.6" marker-end="url(#nvah)"/>

  <rect x="110" y="80" width="500" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="124" y="97" font-size="12" font-weight="700" fill="#3730a3">使能 PCIe 设备 + 映射 BAR</text>
  <text x="124" y="113" font-size="11" fill="#4f46e5">nvme_dev_map()</text>
  <line x1="360" y1="120" x2="360" y2="129" stroke="#475569" stroke-width="1.6" marker-end="url(#nvah)"/>

  <rect x="110" y="130" width="500" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="124" y="148" font-size="12" font-weight="700" fill="#3730a3">创建 Admin SQ/CQ(单对,用于管理命令)</text>
  <text x="124" y="165" font-size="11" fill="#4f46e5">nvme_alloc_admin_tags() → blk_mq_init_queue()</text>
  <line x1="360" y1="176" x2="360" y2="185" stroke="#475569" stroke-width="1.6" marker-end="url(#nvah)"/>

  <rect x="110" y="186" width="500" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="124" y="204" font-size="12" font-weight="700" fill="#3730a3">identify controller + namespace(通过 admin queue)</text>
  <text x="124" y="221" font-size="11" fill="#4f46e5">nvme_identify_ctrl() → 获取队列数, 能力, 固件版本</text>
  <line x1="360" y1="232" x2="360" y2="241" stroke="#475569" stroke-width="1.6" marker-end="url(#nvah)"/>

  <rect x="110" y="242" width="500" height="76" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="124" y="260" font-size="12" font-weight="700" fill="#115e59">创建 IO SQ/CQ</text>
  <text x="124" y="276" font-size="10.5" fill="#0f766e">· 根据 CPU 拓扑分配(num_possible_cpus() 或 nr_io_queues)</text>
  <text x="124" y="291" font-size="10.5" fill="#0f766e">· 每个 IO queue → 一个 blk-mq hardware context (hctx)</text>
  <text x="124" y="306" font-size="10.5" fill="#0f766e">· MSI-X 中断:request_irq() per-CQ</text>
  <line x1="360" y1="318" x2="360" y2="327" stroke="#475569" stroke-width="1.6" marker-end="url(#nvah)"/>

  <rect x="160" y="328" width="400" height="38" rx="6" fill="#dcfce7"/>
  <text x="360" y="352" text-anchor="middle" font-size="12" font-weight="600" fill="#166534">blk_mq_alloc_tag_set() → 关联到 request_queue</text>

  <rect x="60" y="376" width="600" height="36" rx="8" fill="#eef2ff"/>
  <text x="72" y="398" font-size="12" fill="#3730a3">Admin queue 先行,拿到队列数与能力后才按 CPU 拓扑铺开 IO queue;每个 IO queue 独立 hctx + 独立 MSI-X 中断,为后续 IRQ affinity 绑核打基础。</text>
</svg>

### queue_rq: 提交路径 (热路径)

```c
// 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

```c
// 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 子系统

```c
// 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: 块设备虚拟化

```c
// 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 → 提交到下层设备
```

```bash
cryptsetup luksFormat /dev/sda2
cryptsetup open /dev/sda2 cryptroot
dmsetup table  # 查看所有 DM 设备
```

---

## Bcache: SSD 缓存 HDD

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

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

---

## 调试与观测

```bash
# 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*
