---
title: NVMe とストレージ
url: https://doc.liz6.com/ja/linux-kernel/07-block-device-layer/02-nvme-and-storage
locale: ja
area: linux-kernel
tags:
- linux-kernel
- block-device-layer
date: 2026-06-30
modified: 2026-07-16
description: '対象範囲: NVMe プロトコル (SQ/CQ/PRP/SGL) → Linux NVMe ドライバ → NVMe マルチパス → NVMe-oF → SCSI サブシステム (sd/sr) → デバイスマッパー (dm-crypt/LVM/dm-thin) → Bcache/dm-cache カーネルバージョン: 3.3 ~…'
---

# NVMe とストレージ

> 対象範囲: NVMe プロトコル (SQ/CQ/PRP/SGL) → Linux NVMe ドライバ → NVMe マルチパス → NVMe-oF → SCSI サブシステム (sd/sr) → デバイスマッパー (dm-crypt/LVM/dm-thin) → Bcache/dm-cache
> カーネルバージョン: 3.3 ~ 6.x

## NVMe プロトコルの基礎

### キューモデル

```
NVMe は PCIe + リングキューモデルを使用し、AHCI の固定深さの命令スロットに代わります:

各 SQ (Submission Queue) + CQ (Completion Queue) のペアは 1 つの I/O チャネルを構成します。
  SQ: ホスト → コントローラ (ホストが書き込み、コントローラが読み込み)
  CQ: コントローラ → ホスト (コントローラが書き込み、ホストが読み込み)

マルチキューの利点:
  - 各 CPU コアが SQ/CQ を専有 → ロック競合ゼロ
  - 最大 64K キュー、キューあたり 64K 命令までサポート → 極めて高い並列性
  - 割り込み: MSI-X per-CQ → 各キューが独立した割り込み → IRQ アフィニティによるコア紐付けが自然に可能
```

### 命令フォーマット

```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 リストへのポインタ
    __le64 prp2;

    // ロジカルブロックアドレス (LBA) + 転送長
    __le64 slba;       // 開始 LBA
    __le16 nlb;        // ロジカルブロック数 (0 ベース)
};
```

PRP と SGL の比較:
- PRP: 単純なリンクリスト。各エントリは物理ページを指します。最大 2 つのインライン + PRP リスト。小規模な I/O に適しています。
- SGL: 汎用の scatter-gather リスト。任意長のセグメントをサポートします。NVMe 1.2+ および NVMe-oF で必須です。

### ドアベルメカニズム

```
命令の送信:
  1. ホストが NVMe 命令を SQ エントリ (リングバッファ) に書き込み
  2. ホストが SQ Tail Doorbell (MMIO 書き込み) を書き込み (コントローラに新しい命令があることを通知)
  3. コントローラが命令を取得 → 実行 → DMA データ → CQ エントリに書き込み
  4. コントローラが MSI-X 割り込みを送信
  5. ホストが CQ Head Doorbell を書き込み (コントローラに CQ エントリが消費されたことを通知)

一連の流れ: MMIO 書き込みは送信時に 1 回、MMIO 読み込みは 0 回 (CQ はホスト DDR 上にある)
AHCI との比較: 送信ごとに複数の MMIO 読み込みラウンドが必要 (PxCI などのレジスタの読み取りなど)
  → NVMe のレイテンシは 3〜5 倍低い
```

---

## 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">コントローラ + namespace の identify (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 キュー → 1 つの blk-mq ハードウェアコンテキスト (hctx)</text>
  <text x="124" y="306" font-size="10.5" fill="#0f766e">・ MSI-X 割り込み: per-CQ で request_irq()</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 キューは独立した hctx と独立した MSI-X 割り込みを持ち、後続の IRQ アフィニティによるコア紐付けの基盤を形成します。</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 命令の構築
    struct nvme_command *cmd = &nvmeq->sq_cmds[nvmeq->sq_tail];
    nvme_map_rq(rq, cmd);  // PRP/SGL + LBA + 長さの構築

    // 2. SQ への NVMe 命令の書き込み
    //    (SQ のリングバッファ内に既に存在するため、コピーは不要)

    // 3. SQ タイルポインタの更新
    nvmeq->sq_tail = (nvmeq->sq_tail + 1) & (nvmeq->q_depth - 1);

    // 4. doorbell の書き込み (MMIO 書き込み)
    writel(nvmeq->sq_tail, nvmeq->q_db);

    return BLK_STS_OK;
}

// 割り込み: nvme_irq()
//   → nvme_process_cq() → CQ エントリの走査
//     → nvme_handle_completion() → blk_mq_complete_request()
//       → mq_ops->complete() → end_that_request_last()
```

### NVMe マルチパス

```c
// drivers/nvme/host/multipath.c
// 同一 namespace への I/O パスは複数存在する可能性があります:
//   - NVMe-oF: 同一サブシステム内の複数のコントローラ
//   - PCIe: デュアルポート NVMe ドライブ

// nvme-core は同一 ns の複数のパスを自動的に 1 つのブロックデバイスとして集約します。
// I/O スケジューリング:
//   デフォルトは round-robin (ANA: Asymmetric Namespace Access)
//   最適化パス: I/O ポリシーのノブ
```

---

## NVMe-oF (NVMe over Fabrics)

```
NVMe プロトコルをネットワークに拡張:
  NVMe/TCP:  標準 TCP 転送 (最も汎用的)
  NVMe/RDMA: InfiniBand/RoCE (最低レイテンシ、約 10μs)
  NVMe/FC:   ファイバーチャネル (データセンターの伝統的なスタック)

Linux 実装:
  target (サーバー): drivers/nvme/target/ + nvmet-tcp/nvmet-rdma
  ホスト:             drivers/nvme/host/fabrics.c + nvme_tcp/nvme_rdma
```

---

## SCSI サブシステム

```c
// drivers/scsi/
// 3 層アーキテクチャ:
//   Upper Level: sd (ディスク), sr (光学ドライブ), st (テープ)
//   Mid Level:   汎用命令の構築、エラー回復 (eh_*)
//   Lower Level: ベンダー HBA ドライバ
//
// 主要構造体:
//   scsi_cmnd: 1 つの SCSI 命令
//   scsi_host_template: HBA ドライバの登録操作
//   queuecommand(): SCSI 命令の送信 → HBA

// 5.x 以降の SCSI はデフォルトで blk-mq を使用 (scsi_mod.use_blk_mq=1)
// SCSI タグを blk-mq タグにマッピング → キュー管理の統一
```

---

## デバイスマッパー: ブロックデバイスの仮想化

```c
// drivers/md/
// Device Mapper (DM) フレームワークにより、仮想ブロックデバイスを作成できます:

// 一般的な DM ターゲット:
//   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: マルチパス I/O (SAN)

// CRYPT (dm-crypt):
//   キー: ディスク上の LUKS ヘッダー → 暗号化アルゴリズム、キースロット、PBKDF を含む
//   データパス: bio → セクタごとの暗号化/復号化 → 下位デバイスへの送信
```

```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
# 主要指標: 温度、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*
