土法炼钢 · 系统与基础设施

【SPDK 用户态存储】用户态存储全景:从 Reactor 到 NVMe-oF 的坐标系

文章导航

分类入口
storagelinux
标签入口
#spdk#userspace#nvme#bdev#reactor#vfio#nvme-of#v26.01

目录

站内 NVMe 协议 已讲清接口与 NVMe-oF 扩展;O_DIRECT 与 io_uringio_uring 系列 覆盖仍走内核的高 IOPS 提交路径。云块 / 新硬件 提到 DPU 与卸载。读者仍缺一角:驱动与块栈搬进用户态之后,一次 I/O 如何经 reactor → NVMe qpair → bdev →(可选)NVMe-oF/vhost,以及相对内核路径各自保证什么、丢掉什么。

本文是「SPDK / 用户态存储栈」系列第 1 篇,只做三件事:

  1. 画出 SPDK 相对内核 blk / io_uring / 协议百科的生态位与缺口。
  2. 钉住后续章节共用的五条坐标系,并交代用户态轮询存储栈的设计谱系与开放争论。
  3. 给出与站内系列的分工,以及 16 篇阅读路线。

本文是「SPDK / 用户态存储栈」系列第 1 篇(共 16 篇)。→ 系列目录

篇目 核心内容
第 1 篇 · 用户态存储全景 生态位、主线坐标系、系列路线
第 2 篇 · Reactor event framework、poller、message passing
第 3–5 篇 · 本机路径 绑盘 → NVMe 驱动 → DMA/mempool
第 6–8 篇 · 块与远端 bdev → NVMe-oF target

版本锚定:SPDK v26.01(LTS;官方每年一月发 LTS)。官方 Getting Started / Event Framework / NVMe Driver / DMA From User Space 与源码树 tag v26.01。本篇以定位与路线为主;无本机直通 NVMe 则不粘贴伪造 fio / RPC / setup 输出。非 LTS 特性(如仅出现在 v26.05+)须显式标注。


一、生态位:本系列补哪一角

内核 NVMe、O_DIRECT+io_uring、SPDK 常被塞进同一张「高性能块 I/O」表,但默认把什么当一等公民不同——格子不是互斥,而是故障归因时先看哪一层

flowchart TB
  subgraph paths ["Block I/O paradigms"]
    KER["Kernel NVMe + blk<br/>interrupt / softirq"]
    URING["O_DIRECT + io_uring<br/>kernel submit path"]
    SPDK["SPDK userspace<br/>polled stack"]
    DPU["DPU / offload<br/>host-side remnant"]
  end
  subgraph docs ["Site coverage"]
    S03["storage/03<br/>NVMe protocol"]
    S79["storage/79<br/>Direct + io_uring"]
    THIS["This series<br/>userspace stack"]
  end
  S03 -.->|"interface map"| KER
  S03 -.->|"NVMe-oF wire"| SPDK
  S79 --> URING
  THIS --> SPDK
  KER -.->|"same SSD, different owner"| SPDK
  URING -.->|"contrast completion model"| SPDK
  SPDK -.->|"when host stack shrinks"| DPU
形态 一等公民 典型代价中心 站内入口
内核 NVMe + block 中断 / softirq、通用块层 系统调用与共享调度税 storage/03 协议侧
O_DIRECT + io_uring 内核异步提交环 仍经内核驱动与块层语义 storage/79io_uring
SPDK 用户态栈 reactor / poller + 用户态驱动 + bdev CPU 轮询税、设备独占、运维共置 本系列
DPU / 卸载 网卡/智能网卡侧 target 主机侧还剩什么 storage/73

一句话:本系列占住「用户态轮询存储栈」那一格——不重写 NVMe 规范全书,也不复述内核提交路径。

主线引擎取 SPDK v26.01:文档与源码 tag 对齐,适合做「从绑盘到 NVMe-oF」的连续拆解。Ceph OSD、MinIO、云盘产品内核不进正文;仅在选型边界出现。

读者若只带走一句:会跑 hello_world 只是地图入门;能指出延迟与失败落在五条轴的哪一条,才算进入本系列的能力模型。


二、缺口:相对 03 / 79 / io_uring 还缺什么

已有内容 视角 本系列补什么
storage/03 NVMe / NVMe-oF / ZNS 协议与接口演进 用户态驱动与 target 实现路径;不重写规范全书
storage/79 Direct I/O + io_uring 组合 对照「绕过内核块层」的另一条路
io_uringos/57 内核异步提交模型 对照句:完成通知 vs 用户态轮询
storage/7073 云块、DPU 展望 机制前提;不写厂商黑盒 SDK
linux/zero-copy socket ZC 权衡 边界外链;本系列焦点在块/NVMe

结论:读者知道 NVMe 多队列、会配 O_DIRECT+io_uring,但不知道 SPDK 如何绑盘、reactor 上阻塞意味着什么、bdev 队列满如何失败、NVMe-oF RDMA/TCP 与内核 initiator 如何对接。

HAProxy 第 1 篇 / Envoy 第 1 篇 对称:那边钉的是代理数据面;这边钉的是存储数据面换宿主之后的失败层


三、设计谱系:用户态轮询存储从哪分叉

3.1 工作定义

本系列默认的 SPDK 用户态存储栈

这与「内核里开一个 /dev/nvme0n1」共享「块设备」一词,但运行时不变量不同:SPDK 把设备所有权与完成模型做成用户态一等路径。

3.2 学术与工程谱系(本系列回指)

  1. 用户态驱动 + 轮询:与 DPDK 同宗——把设备从内核驱动解绑,用 hugepage / VFIO 做 DMA 安全内存与设备访问;存储侧把同一范式落到 NVMe。官方 DMA From User Space 明确:缓冲必须经 spdk_dma_malloc()(或其兄弟)一类路径分配。
  2. Message passing 取代热路径锁:官方 Message Passing and Concurrency 把目标写成「随硬件近似线性扩展」,并指出传统共享堆 + 锁在核数上升后会把时间耗在争用上;SPDK 倾向把状态钉在单线程,跨核用「函数指针 + 上下文」消息。Erlang / Go 的消息并发是谱系参照,不是 SPDK 运行时依赖。
  3. 被动库 + 可选 app 框架:NVMe 驱动可单独链接进任意事件循环;lib/event 的 reactor/spdk_app_start() 是示例与多数 SPDK 应用的默认宿主,不是唯一宿主(Event Framework:framework is optional)。

谱系收束句:产品层争论「要不要上用户态」;内核层争论「轮询 CPU 税与设备独占是否换得起延迟与确定性」。 后者才是本系列主战场。

3.3 开放争论(先立靶,后各章拆)

争论 A 侧 B 侧 本系列落点
轮询 vs 中断 / 内核完成通知 尾延迟更可控、少一次调度 空闲时 CPU 税高 第 2、13、15 篇;26.01 起部分 interrupt mode 边界见第 9 篇
用户态独占 vs 内核共置 绕过块层、进程内零拷贝 同机工具链、热升级、容器共享盘变难 第 3、12、14–16 篇
SPDK vs O_DIRECT+io_uring 用户态栈端到端 内核路径够用、运维面小 第 15–16 篇
RDMA vs TCP(NVMe-oF) 延迟与卸载 部署面与兼容性 第 9–10 篇
bdev 叠层深度 功能组合快 每层排队与回调税 第 6–7 篇

四、五条坐标系(后续章节回指)

后面每一篇都会落到下面某一条轴上。第 1 篇只钉名字,不展开源码。

4.1 I/O 路径轴

app / bdev → (optional) nvmf/vhost
           → NVMe qpair submit → device DMA
           → poll CQ / poll group → completion callback

延迟与失败的归因必须先回答:卡在绑盘、提交、轮询完成、bdev 排队,还是传输层? → 第 2–10、14 篇。

4.2 线程与消息轴

Reactor 每核一条循环;跨核协调走 event / spdk_thread_send_msg() 一类消息,而不是在 I/O 热路径上抢全局锁。在 reactor 上阻塞等于饿死同核所有 poller。→ 第 2 篇。

4.3 设备独占与 env 轴

scripts/setup.sh 分配大页并把 NVMe 从内核驱动解绑到 VFIO/UIO;setup.sh reset 再绑回。独占换来的是用户态可见性,代价是同机内核路径暂时不可用。→ 第 3 篇。

4.4 块抽象轴(bdev)

bdev 统一「块设备」读写与完成回调;模块决定后端是本机 NVMe、aio、内存盘还是 RAID/lvol。相对 Linux block 层,砍掉的是通用调度与大量内核语义,留下的是用户态队列与回调契约。→ 第 6–7 篇。

4.5 远端与控制面轴

NVMe-oF target 把远端命令落到本地 bdev;传输(RDMA/TCP)与 initiator 互通决定「线缆另一端是谁」。JSON-RPC 管生命周期与热插拔,不管热路径 IOPS。→ 第 8–12 篇。


五、与站内系列的分工

站内内容 负责 本系列不重复
storage/03 协议与接口演进 用户态实现路径
storage/79 Direct + io_uring 组合 内核提交细节
io_uring 内核异步模型 对照句即可
storage/73 DPU / 新硬件展望 不写 DOCA 教程
distributed/38 Ceph / RADOS 用户态 OSD 栈仅选型边界
linux/zero-copy socket ZC 块路径 DMA 约束在第 5 篇

读法:只要「本机数据库盘用不用 O_DIRECT+io_uring」→ 79 足够;要「用户态 target 卡在哪、为何 CPU 空转」→ 本系列 01 → 02 → 04 → 06 → 08 → 14。

写给架构师的一句:若组织还没有「设备独占 + 专用轮询核」的运维编制,先读第 3、15、16 篇再决定是否开用户态栈——机制再漂亮,也填不平编制缺口。

本系列后半(观测、排障、选型)会反复回指这五条轴;若第 1 篇只当目录略过,后面读到「卡在独占轴」会对不上号。建议至少把第三节争论表与第四节坐标名留下书签。


六、16 篇地图与阅读建议

部分 一句话
运行时与本地 NVMe 01–06 从坐标系到 bdev 核心
模块、远端与虚拟化 07–12 叠层、NVMe-oF、vhost、RPC
观测与收束 13–16 口径、排障、对照内核、选型

最小路径:01 → 02 → 04 → 06 → 08 → 14。
本机用户态盘:再补 03 → 05 → 07。
做路径选型:01 → 15 → 16,并外链 03 / 79 / io_uring。

本篇不展开 lib/nvme/ 函数表,也不给出跨方案延迟排名——那需要声明环境的实测;无直通硬件时,只保留机制与失败模式。


七、一次 I/O 在用户态栈里「停」在哪(预览)

把五条轴压成排障时的第一问,避免一上来就怀疑固件:

停顿表象 先落哪条轴 后续篇
进程起不来 / 看不到盘 设备独占与 env 03
CPU 单核打满但 IOPS 上不去 线程与消息(空转 poller、错误绑核) 02、13
提交成功、回调迟迟不来 I/O 路径(未泵 CQ / poller 被堵) 02、04
回调报错或队列满 块抽象(bdev 排队)或驱动队列深度 06–07、14
远端超时、本地盘却空闲 远端与控制面(传输 / listener / mapping) 08–10、14
同机其它进程打不开盘 独占轴的预期副作用,不是「盘坏了」 03、15

这张表刻意不含延迟微秒数:它是坐标系,不是 benchmark。读者若只能记住一件事——先点名轴,再下钻模块——第 1 篇就够本。

与内核路径的对照句也适合放在这里一次说清:io_uring 的完成仍由内核驱动与块层语义托底;SPDK 把托底换成「你的 reactor 是否还在跑、你的缓冲是否 DMA 安全、你的设备是否仍在 VFIO 上」。保证集变窄,归因路径变短,但运维面变锋利。

学术谱系上,用户态轮询 I/O 与高频交易/NFV 叙事共享「用 CPU 换尾延迟确定性」的假设;假设不成立的场景(混部严重、昼夜负载差大、核是租来的)会把同一设计打成成本中心。本系列后文用机制拆这些场景,不在第 1 篇宣布胜负。


八、参考资料(本篇)

规范 / 官方文档(A)

源码(A)

站内对照

实验台账


九、小结

  1. SPDK 的生态位是用户态轮询存储栈,不是「内核 NVMe 的另一个配置方言」。
  2. 五条坐标系(I/O 路径、线程消息、设备独占、bdev、远端控制面)是后续排障的共用语言。
  3. 与 03 / 79 的分工:协议与内核组合路径归百科;本系列回答「搬进用户态之后卡在哪一层」。
  4. 开放争论先立靶:CPU 税、独占运维、与 io_uring 的边界——各章用机制拆,不提前做未实测排行榜。
  5. 排障第一问是「停在哪条轴」,不是「先重跑 fio」。

系列目录 · 下一篇:Reactor 与线程模型

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-08-13 · storage / linux

SPDK / 用户态存储栈:从 Reactor 到 NVMe-oF

补齐站内 NVMe 协议与 O_DIRECT/io_uring 组合路径之上的用户态轮询存储层:reactor、绑盘、NVMe 驱动、bdev、NVMe-oF 与 vhost,并以排障与选型收束。


By .