このページの目次

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 アフィニティによるコア紐付けが自然に可能

命令フォーマット

// 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 ドライバ

ドライバの階層構造

// 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() コントローラ + namespace の identify (admin queue を介して) nvme_identify_ctrl() → キュー数、能力、ファームウェアバージョンの取得 IO SQ/CQ の作成 ・ CPU トポロジーに基づいて割り当て (num_possible_cpus() または nr_io_queues) ・ 各 IO キュー → 1 つの blk-mq ハードウェアコンテキスト (hctx) ・ MSI-X 割り込み: per-CQ で request_irq() blk_mq_alloc_tag_set() → request_queue への関連付け Admin queue が先行し、キュー数と能力を取得してから、CPU トポロジーに基づいて IO queue が展開されます。各 IO キューは独立した hctx と独立した MSI-X 割り込みを持ち、後続の IRQ アフィニティによるコア紐付けの基盤を形成します。

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 命令の構築
    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 マルチパス

// 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 サブシステム

// 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 タグにマッピング → キュー管理の統一

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

// 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 → セクタごとの暗号化/復号化 → 下位デバイスへの送信
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
# 主要指標: 温度、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