• SPDK Underprinning

  • SPDK Practice

  • SPDK Architecture

  • SPDK Initiator Architecture

    • SPDK Application Layer

      • SPDK Reactor

      • SPDK THREAD

      • SPDK POLLER

    • SPDK Initiator NVMf Core

      • NVMf Initiator Subsystem

      • NVMf Initiator Controller

      • NVMf Initiator Namespace

      • NVMf NVMe QueuePair(QP)

      • NVMf NVMe I/O Channel

  • SPDK Transport Layer

  • SPDK Target Architecture

    • SPDK Target NVMf Core

    • SPDK Target BDEV Layer

      • SPDK BDEV

      • SPDK BDEV IO-CHANNEL

      • SPDK BDEV IO

      • SPDK BDEV OPS

    • SPDK Target Backend Layer

      • NVMe

      • MALLOC

      • RAID

      • IO-Uring

      • LVS

  • Environment Abstraction Layer(DPDK)

  • In-Depth Research on DPDK Mechanisms

    • DPDK

    • Blobstore/BlobFS

    • iSCSI Target

    • NBD(Network Block Device)

    • FUSE

    • DAOS

    • Mooncake With SPDK

在高性能存储需求日益增长的时期,几乎所有 I/O 都不得不穿过一层厚重的内核协议栈. 一次看似简单的读写,实际要经历系统调用陷入内核、权限与上下文的切换、文件系统的逻辑处理、Block Layer 的请求合并与重新调度,再构建 BIO、提交到驱动,最后通过中断艰难地返回用户态. 这条路径上,每一次陷入与返回都伴随着寄存器保存与恢复,每一层抽象都可能引入锁的争夺与缓存行的来回失效,软件栈的“通用性”被层层叠加成实际的延迟与抖动. 在当时,这些开销被视为必须付出的代价: 为了换取统一的接口、权限隔离和硬件无关性,开发者接受了这条沉重却“正确”的路径.

当 NVMe 与 NVMe-oF 真正进入大规模部署后,这种矛盾被瞬间撕开. 硬件已经能在微秒级完成一次 I/O,单盘轻松支撑数十万 IOPS,多队列设计更是让并行提交变得轻而易举. 然而软件路径却几乎没有跟上, 每一次请求依然要反复陷入内核、争夺锁、经历调度与中断返回. 原本被高延迟设备掩盖的软件开销,此时赤裸裸地暴露出来,常常占满端到端延迟的大半. CPU 的大量周期被消耗在中断处理与自旋等待上,硬件提供的队列深度难以被有效填满,尾延迟也变得反复无常、难以预测. 对那些真正在意确定性和微秒级响应的场景来说,这条曾经“足够好”的通用路径,已经从可接受的实现,变成了必须正视的瓶颈. 在矛盾日益尖锐的情况下,社区并非没有尝试, 开发者先后探索了几条过渡路径,试图在不彻底推倒既有模型的前提下缓解问题:

  • 第一条路是继续在内核内部深挖: 以 io_uring 为代表的异步接口大幅减少了系统调用次数,多队列 Block Layer、per-CPU 数据结构以及更细粒度的无锁化改造,也确实降低了部分路径的开销. 然而这些优化始终运行在内核的调度与中断框架之内,抢占、锁、上下文切换的基本约束并没有消失,只是被尽量推迟或分摊. 它像是给旧房子不断加固和局部改造,能撑得更久,却改变不了结构本身的沉重.
  • 第二条路是局部绕过内核: 借助 VFIO 将 NVMe 设备直接交给用户进程,再在用户态自行实现队列管理与门铃操作,确实能避开大部分内核路径. 但这类方案往往止步于”能跑”,缺少统一的抽象、成熟的内存管理与多设备协同能力,最终变成每个项目自己维护的一套私有驱动. 性能上去了,工程复杂度和可维护性却成了新的负担,像是拆了东墙的砖去补西墙,解决了眼前的延迟,却留下了长期的技术债
  • 第三条路则更为务实,也更为无奈: 在应用层用批量提交、深度队列和异步流水线去”掩盖”延迟, 通过堆积足够多的 in-flight 请求,让硬件的并行能力尽量被吃满,从而在吞吐上获得可观数字. 但这种做法本质上是在用复杂度和资源换取平均值的好看,尾延迟依然起伏不定,CPU 消耗与同步逻辑也愈发臃肿. 它没有真正去掉路径中的固定开销,只是让开销不那么容易被直接看见.

这三条路都在特定场景下取得了成绩,却也共同暴露出一个事实: 只要核心 I/O 路径仍然受制于内核的通用模型,优化就只能是局部的、补偿性的. 就在这些过渡方案不断被提出、又不断暴露出新局限的同时,另一条线索也在悄然酝酿.

网络领域已经先行一步, DPDK 用事实证明: 当硬件具备多队列与低延迟能力时,把驱动和数据路径完全放到用户态、用轮询取代中断、用 per-core 的无共享设计消除锁,可以换来数量级的性能提升和远更高的确定性. 这套方法论在高速网络上站稳脚跟后,自然有人开始反问: 存储为什么不能走同一条路?

NVMe 的硬件特征与现代网卡高度相似: 多队列、门铃机制、completion queue、低延迟. 此前在内核里做的各种优化,以及借助 VFIO 进行的局部用户态尝试,都已经从侧面证明了”用户态操作 NVMe 队列”在技术上是可行的. 缺的不是可行性,而是一次系统性的重新设计: 不再把用户态驱动当作临时绕过手段,而是把它提升为完整的运行时: 统一的内存管理、线程模型、模块化后端、以及面向应用的抽象层.

与此同时,推动这一切真正落地的力量也逐渐清晰起来. 一端是硬件能力与业务需求的共同挤压: 延迟与确定性的要求被不断推高,内核通用路径里那些固定成本变得越来越难以忍受. 另一端则是跨领域经验的自然迁移. 网络侧已经用轮询模式证明,把控制权交还给用户态软件能够换来完全不同的效率与确定性,而 NVMe 在队列机制与低延迟特征上与现代网卡高度相似,存储没有理由止步于局部绕过. 于是有人开始把 DPDK 已经验证过的核心思想: 环境抽象、reactor 模型、无锁通信、大页与 DMA 内存管理系统地引入存储场景,并围绕 NVMe 队列、块设备抽象以及上层协议,重新设计整条 I/O 路径.