On this page
NVMe and Storage
Coverage: NVMe Protocol (SQ/CQ/PRP/SGL) → Linux NVMe Driver → NVMe Multipath → NVMe-oF → SCSI Subsystem (sd/sr) → Device Mapper (dm-crypt/LVM/dm-thin) → Bcache/dm-cache Kernel Version: 3.3 ~ 6.x
NVMe Protocol Basics
Queue Model
NVMe uses a PCIe + ring queue model, replacing AHCI's fixed-depth command slots:
Each pair of SQ (Submission Queue) + CQ (Completion Queue) forms an I/O channel.
SQ: Host → Controller (Host writes, Controller reads)
CQ: Controller → Host (Controller writes, Host reads)
Advantages of multiple queues:
- Each CPU core has its own SQ/CQ → Zero lock contention
- Supports up to 64K queues, with 64K commands per queue → Extremely high parallelism
- Interrupts: MSI-X per CQ → Independent interrupts per queue → Natural IRQ affinity binding to cores
Command Format
// include/linux/nvme.h
;
PRP vs SGL:
- PRP: Simple linked list, each entry points to a physical page. Supports up to 2 inline entries + a PRP list. Suitable for small I/O.
- SGL: General-purpose scatter-gather list, supporting segments of arbitrary length. Required for NVMe 1.2+ / NVMe-oF.
Doorbell Mechanism
Submitting a command:
1. Host writes the NVMe command to an SQ entry (ring buffer)
2. Host writes the SQ Tail Doorbell (MMIO write, notifying the controller of new commands)
3. Controller fetches the command → Executes → DMA data → Writes to CQ entry
4. Controller sends an MSI-X interrupt
5. Host writes the CQ Head Doorbell (notifying the controller that CQ entries have been consumed)
Entire process: 1 MMIO write for submission, 0 MMIO reads (CQ resides in Host DDR)
Comparison with AHCI: Each submission requires multiple rounds of MMIO reads (reading PxCI, etc.)
→ NVMe latency is 3~5x lower
Linux NVMe Driver
Driver Layering
// drivers/nvme/host/
// nvme-core.c: Protocol implementation (admin queue, IO queue, identify, abort, ...)
// nvme.h: Data structures (nvme_command, nvme_completion)
// pci.c: PCIe transport layer (MMIO doorbell, MSI-X, PRP mapping)
// fabrics.c: NVMe-oF transport layer (TCP/RDMA/FC)
Queue Initialization Flow
queue_rq: Submission Path (Hot Path)
// drivers/nvme/host/pci.c
static blk_status_t
// Interrupt: nvme_irq()
// → nvme_process_cq() → Iterate through CQ entries
// → nvme_handle_completion() → blk_mq_complete_request()
// → mq_ops->complete() → end_that_request_last()
NVMe Multipath
// drivers/nvme/host/multipath.c
// The same namespace can be accessed via multiple I/O paths:
// - NVMe-oF: Multiple controllers under the same subsystem
// - PCIe: Dual-port NVMe drives
// nvme-core automatically aggregates multiple paths for the same namespace into one block device
// I/O Scheduling:
// Default: round-robin (ANA: Asymmetric Namespace Access)
// Optimized Path: I/O policy knob
NVMe-oF (NVMe over Fabrics)
Extends the NVMe protocol to networks:
NVMe/TCP: Standard TCP transport (Most universal)
NVMe/RDMA: InfiniBand/RoCE (Lowest latency, ~10μs)
NVMe/FC: Fibre Channel (Traditional data center stack)
Linux Implementation:
Target (Server): drivers/nvme/target/ + nvmet-tcp/nvmet-rdma
Host: drivers/nvme/host/fabrics.c + nvme_tcp/nvme_rdma
SCSI Subsystem
// drivers/scsi/
// Three-layer architecture:
// Upper Level: sd (Disk), sr (CD-ROM), st (Tape)
// Mid Level: Generic command construction, Error recovery (eh_*)
// Lower Level: Vendor HBA drivers
//
// Key Structures:
// scsi_cmnd: A single SCSI command
// scsi_host_template: Operations registered by the HBA driver
// queuecommand(): Submit SCSI command → HBA
// SCSI 5.x+ uses blk-mq by default (scsi_mod.use_blk_mq=1)
// Maps SCSI tags to blk-mq tags → Unified queue management
Device Mapper: Block Device Virtualization
// drivers/md/
// The Device Mapper (DM) framework allows the creation of virtual block devices:
// Common DM Targets:
// dm-linear: Linear mapping (Foundation of LVM)
// dm-crypt: Block device encryption (LUKS)
// dm-thin: Thin provisioning
// dm-cache: SSD caching for HDD (dm-cache / dm-writecache)
// dm-raid: MD RAID is no longer developed; DM RAID takes over
// dm-multipath: Multipath I/O (SAN)
// CRYPT (dm-crypt):
// Key: LUKS header on disk → Contains encryption algorithm, key slots, PBKDF
// Data Path: bio → encrypt/decrypt per sector → Submit to underlying device
Bcache: SSD Caching for HDD
// drivers/md/bcache/
// Bcache uses SSD as a read/write cache for HDDs
//
// Cache Modes:
// writeback: Writes go to SSD first → Async writeback to HDD (High performance, risk on power loss)
// writethrough: Writes go to both SSD and HDD simultaneously (Safe, but write performance is limited)
// writearound: Writes bypass SSD directly to HDD (SSD used only for reads)
// Kernel 6.x natively supports bcache (Merged into mainline)
Debugging and Observation
# NVMe Driver Information
# NVMe Command-level Tracing
# SMART Data
# Key Metrics: temperature, percentage_used (Lifespan), media_errors
# DM Devices
# Bcache
# SCSI Layer Tracing
References and Further Reading
- Source Code:
drivers/nvme/host/pci.c(PCIe NVMe Driver),drivers/nvme/target/(NVMe-oF),drivers/scsi/,drivers/md/,drivers/md/bcache/ - NVMe Specification: nvmexpress.org (base spec 1.4c/2.0)
- Kernel Documentation:
Documentation/nvme/,Documentation/device-mapper/ - LWN: "The Linux NVMe driver", "Bcache design"
Keywords: NVMe, SQ/CQ, doorbell, PRP, SGL, blk-mq, nvme_queue_rq, NVMe-oF, SCSI, device mapper, dm-crypt, Bcache