このページの目次
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
;
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)
キュー初期化フロー
queue_rq: 送信パス (ホットパス)
// drivers/nvme/host/pci.c
static blk_status_t
// 割り込み: 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 → セクタごとの暗号化/復号化 → 下位デバイスへの送信
Bcache: SSD による HDD のキャッシュ
// drivers/md/bcache/
// Bcache は SSD を HDD の読み書きキャッシュとして使用します。
//
// キャッシュモード:
// writeback: 書き込みはまず SSD へ → 非同期で HDD にバックフィル (高性能、停電時のリスクあり)
// writethrough: 書き込みは SSD と HDD の両方に同時に (安全だが、書き込み性能が制限される)
// writearound: 書き込みは SSD をバイパスして直接 HDD へ (SSD は読み込み専用)
// カーネル 6.x では bcache がネイティブサポートされています (メインラインにマージ済み)
デバッグと観測
# NVMe ドライバの情報
# NVMe 命令レベルのトレース
# SMART データ
# 主要指標: 温度、percentage_used (寿命)、media_errors
# DM デバイス
# Bcache
# SCSI 層のトレース
参考と拡張
- ソースコード:
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