上一篇钉住了 reactor 上「谁在跑」。本篇钉运行之前的前提:内存从哪来、设备归谁、CPU 如何钉住——以及把盘从内核手里抢走之后,运维要付什么账。
常见误区:把 scripts/setup.sh
当成「装驱动」;或以为绑到 VFIO
之后,nvme-cli、内核多路径、同机数据库的
/dev/nvme*
路径仍可同时用同一块盘。独占是特性也是税——排障时「盘消失」往往不是坏了,而是换了主人。
本文说明:hugepage 与 DMA
安全内存的关系;setup.sh 的分配/解绑/reset
语义;VFIO(IOMMU)相对 UIO
的推荐位置;设备独占与共置代价。
本文是「SPDK / 用户态存储栈」系列第 3 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 2 篇 · Reactor 事件循环与阻塞警告 第 3 篇 · 环境与绑盘 大页、VFIO/UIO、独占与回退 第 4 篇 · NVMe 驱动 probe / qpair 第 5 篇 · DMA / mempool 缓冲约束
版本锚定:SPDK v26.01 Getting Started、Direct Memory Access (DMA) From User Space;
scripts/setup.sh@ tag v26.01。命令给出可执行语义;无本机直通 NVMe 则不粘贴伪造 setup / lspci / 绑定输出。
一、先问:用户态驱动缺什么内核已经给过的东西
内核 NVMe 驱动拥有:
- 设备节点与 sysfs;
- 把页面钉住并做 DMA 映射的内核 API;
- 中断与 blk-mq 队列;
- 与页回收、热插拔、电源管理的协作。
用户态驱动要自己(或经 DPDK EAL / SPDK env)补齐至少三块:
- 可 DMA 且地址可翻译的内存(大页 + 固定映射叙事;IOMMU 下则为 IOVA)。
- PCI 资源访问权(BAR 映射、配置空间;通常经 VFIO 或 UIO)。
- CPU 与中断局部性(亲和;轮询模式下业务核与残留内核中断的关系仍要管)。
Getting Started
把「跑示例前」收成一句可操作流程:分配 hugepage,并把
NVMe(及文档提及的 I/OAT 等)从原生内核驱动解绑。入口脚本是
root 下的
scripts/setup.sh,一般每台机器准备一次即可。
二、Hugepage:不是「越大越好」的口号
2.1 为何是大页
DMA From User Space 解释了约束链:
- NVMe 用 DMA 在 PCI 上搬数据;无 IOMMU 时消息里是物理地址。
- 用户态只有虚拟地址;虚拟→物理映射可能因换页、compaction、透明压缩、热加内存等改变。
- POSIX
mlock防换出 ≠ 钉死物理页框映射;POSIX 也没有可移植的「pin」API。 - SPDK 依赖 DPDK 分配钉住的内存;在 Linux 上默认路径是 hugepage(常见 2MiB)——内核长久以来对大页的处理使物理位置实际上保持稳定(文档同时警告:这不是永恒承诺,长期正确做法是 VFIO + IOMMU)。
因此:业务数据缓冲不能 malloc
完事;必须走 spdk_dma_malloc()
或其兄弟(第 5 篇)。setup.sh
分配的大页是整机/进程环境的原料,不是自动把任意指针变成 DMA
安全。
2.2 setup.sh 与
HUGEMEM
官方语义(Getting Started @ v26.01):
# 准备环境:大页 + 解绑 NVMe 等(需 root;每机通常一次)
sudo scripts/setup.sh
# 默认分配 2048MB 大页;用 HUGEMEM(MB)覆盖
sudo HUGEMEM=4096 scripts/setup.sh
# 查看全部参数
scripts/setup.sh help
# 把设备绑回内核驱动
sudo scripts/setup.sh reset文档说明:Linux 上 HUGEMEM
会按系统默认大页尺寸向上取整。本篇不声称某次运行后
/proc/meminfo 的具体数字——无本机直通
NVMe
则不粘贴伪造输出;成功判据是:大页计数增加、目标
PCI 设备不再挂在内核 nvme 驱动上(可用
lspci -k 等核对立查,仍以实机为准)。
大页耗尽时的失败模式通常表现为:应用初始化 EAL/env 失败、DMA 分配失败、或运行中无法再扩缓冲——表象是「SPDK 起不来 / I/O 提交失败」,根因在 env,不在 bdev 逻辑。
三、绑盘:VFIO、UIO 与设备独占
3.1 setup.sh
在做什么(语义级)
脚本的核心副作用:
- 配置并预留 hugepage。
- 把匹配的存储类 PCI 设备从内核驱动 unbind。
- 绑定到用户态可用的驱动框架——现代推荐路径是 vfio-pci(配合 IOMMU);历史上/受限环境常见 uio 系路径。
精确的设备匹配规则、白名单/黑名单、驱动选择以
scripts/setup.sh help 与脚本实现 @
v26.01
为准;正文不复述脚本全文。排障时把「绑错盘、绑了系统盘、IOMMU
未开却走了错误路径」当成第一清单。
flowchart TD
NVMe["NVMe PCI function"]
KER["Kernel nvme driver<br/>/dev/nvmeXnY"]
VFIO["vfio-pci<br/>IOMMU-backed"]
UIO["UIO path<br/>limited/no IOMMU"]
APP["SPDK process"]
NVMe --> KER
setup["setup.sh"] -->|"unbind + bind"| VFIO
setup -->|"fallback / legacy"| UIO
VFIO --> APP
UIO --> APP
reset["setup.sh reset"] --> KER
3.2 VFIO + IOMMU:官方长期地基
DMA From User Space 把 IOMMU
写成:外设侧的「MMU」——给 PCI 设备一套总线地址,经 IOMMU
再落到进程虚拟映射与物理页。Linux vfio-pci
允许用户态配置这套映射。文档结论:
高度推荐用 vfio 且启用 IOMMU 部署;这是 SPDK/DPDK 内存管理的长期地基,且今日已完整支持。
Getting Started 补充:若系统 IOMMU 已启用,示例可以以普通用户运行;否则需要特权用户。含义是:安全与权限模型跟着设备暴露方式变,不是「用户态一定更不安全」或「一定更安全」的空话。
3.3 UIO:可用性,不是默认美学
UIO 路径降低了对 IOMMU 的依赖,却把更多正确性压在「大页物理地址稳定」与特权访问上。适合实验或硬件/固件限制场景;写生产叙事时应标明:能跑 ≠ 与 VFIO+IOMMU 同级的地址翻译故事。
3.4 独占代价(必须写进运维手册的那种)
| 代价 | 含义 |
|---|---|
| 内核路径消失 | 同盘不再有常规 /dev/nvme* 块设备(除非另做
CUSE 等暴露,见驱动篇边界) |
| 工具链分裂 | 习惯走 sysfs / 内核 nvme-cli
的流程要改入口;部分能力改走 SPDK RPC 或 CUSE |
| 共置变难 | 同一 PCI 功能不能同时被内核驱动与 SPDK 主进程各管各的(多进程共享是有约束的特例,见 NVMe 驱动文档 Multi Process) |
| 回退要显式 | 升级内核驱动排查、救砖、给别的团队还盘:setup.sh reset |
| 系统盘风险 | 若误解绑启动盘/根盘所用 NVMe,机器可能直接失去根文件系统——绑盘前必须核对 BDF |
独占换来的是:进程内映射 BAR、零拷贝 DMA、与 reactor 同进程的完成轮询。税开在运维与共置,不是开在「少写两行配置」。
四、CPU 亲和:轮询核不是「越多越好」
Reactor 模型默认「核 ≈ 一条忙轮询循环」。亲和要回答三个问题:
- 哪些核跑 SPDK
reactor(
spdk_app的 CPUs / mask 类选项,以应用帮助与 env 文档为准)。 - 哪些核留给内核、网络软中断、同机其他数据面(例如仍用 io_uring 的数据库)。
- NUMA:盘、网卡、大页、reactor 是否同节点——跨节点 DMA 与消息都会变贵。
与 HAProxy Management Guide 类似的反直觉在存储侧同样成立:把所有核都交给轮询,可能饿死内核网络与管理面,表现为「盘很快、集群却抖」。本篇不给未实测的绑核配方;只钉原则——轮询核是专用预算,不是免费午餐。
五、失败模式清单(env 层)
| 现象(语义) | 先查 |
|---|---|
| 应用初始化失败,抱怨大页 / EAL | HUGEMEM、大页是否被别的进程吃光、cgroup/hugetlb
限制 |
probe 看不到盘 |
是否仍绑定内核 nvme;BDF 是否正确;权限 /
IOMMU 组 |
| 盘「从系统消失」 | 是否执行过 setup.sh 且未
reset |
| 普通用户无法跑 | IOMMU/VFIO 是否按文档就绪;是否仍需 root |
reset 后仍异常 |
驱动模块是否加载;是否需重扫 PCI;是否有残留用户态进程占着设备 |
第 14 篇会把这些收进排障坐标系;本篇只要求读者承认:一半「SPDK 很难用」的工单停在 env,而不是 bdev。
六、设计谱系与开放问题
谱系上,这是 DPDK 式用户态 I/O 环境在存储设备上的落地:大页、VFIO、绑核三位一体。争论焦点不在「能不能 map BAR」,而在:
- 专用核空转税是否可接受(尤其是昼夜负载差大的集群)。
- 设备独占与容器/多租户编排是否兼容——编排系统默认假设块设备经内核。
- 与 io_uring 同机:一套盘给内核、一套盘给 SPDK 是常见物理隔离;共享同一控制器的多 namespace 策略则要读清硬件与多进程文档,不能想当然。
开放问题留给第 15–16 篇:何时「内核 NVMe + io_uring」在运维总账上优于用户态独占;DPU 把 target 卸走后,主机侧 env 还剩哪些义务。
七、与容器 / 编排共置时多出来的问题
裸金属上手动 setup.sh
已经锋利;把同一模型塞进容器后,还会多三层摩擦:
- 设备与 IOMMU 组如何进 Pod/容器:VFIO 设备通常要以设备插件或特权方式注入;IOMMU 组隔离失败时,轻则看不到盘,重则权限模型被掏空。
- 大页如何宣告与计费:hugetlb 与 cgroup 记账不一致时,节点「看起来有内存」但 EAL 初始化失败;编排器按普通内存调度会系统性低估存储面进程。
- 生命周期谁先死:容器停了但
reset没跑,宿主机上设备仍停在 vfio-pci,下一个内核态租户会认为「盘丢了」。反之,编排强杀进程时,飞行中 DMA 与热拔语义要靠驱动的 SIGBUS/错误完成路径兜底(第 4 篇),不能假设「进程没了映射就安全没了」。
这些不是劝退容器,而是要求把 env 当成与应用镜像同等重要的节点状态:绑盘、大页、核隔离策略应进 runbook / DaemonSet,而不是开发者笔记本上的一次性命令。
对照内核路径:块设备经 CSI/本地卷进容器时,所有权故事仍由内核统一;SPDK 把所有权挪到用户态进程后,编排系统默认假设被打破。第 16 篇选型排除树会问:你的平台能不能承受「盘是进程附属物」?
八、参考资料
规范 / 官方文档(A)
- SPDK, Getting Started @
v26.01:
setup.sh、HUGEMEM、reset、IOMMU 与权限。 - SPDK, Direct Memory Access (DMA) From User Space @ v26.01:大页、pin、IOMMU/VFIO。
- Linux VFIO 文档(内核 Doc):用户态设备暴露与 IOMMU 组概念(与 SPDK 部署交叉验证)。
源码(A)
spdk/spdktag v26.01:scripts/setup.sh、scripts/setup.sh help输出的参数表。
站内对照
实验台账
- 命令语义来自官方文档;无本机直通 NVMe,不粘贴伪造输出。
九、小结
setup.sh做大页 + 解绑/绑定,不是「安装一个内核模块就结束」。- Hugepage 服务 DMA 安全与地址稳定;缓冲仍须走 DMA 分配 API。
- VFIO+IOMMU 是官方推荐长期路径;UIO 是受限场景的可用性出口。
- 设备独占会拆掉同盘内核工具链——
reset是一等运维动作。 - CPU 亲和按专用轮询预算规划,并给内核与其它数据面留核。
- 容器/编排要把绑盘与大页当成节点状态;默认 CSI 假设在这里不成立。
→ 上一篇:Reactor 与线程模型 · 系列目录 · 下一篇:用户态 NVMe 驱动
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【SPDK 用户态存储】DMA 与 Mempool:可 DMA 内存、对齐与零拷贝约束
解释为何 SPDK 数据缓冲必须经 DMA 安全分配:物理/IOVA 翻译、PRP 约束、hugepage 与 IOMMU;并说明 mempool 复用、对齐失败模式及零拷贝相对显式拷贝的边界。
【SPDK 用户态存储】用户态存储全景:从 Reactor 到 NVMe-oF 的坐标系
定位 SPDK 相对内核块层、O_DIRECT+io_uring 与 NVMe 协议百科的生态位;钉住 I/O 路径、线程模型、设备独占、块抽象、远端传输五条坐标系,并给出 16 篇阅读路线。
【SPDK 用户态存储】排障坐标系:绑盘、空转、队列满、传输超时与多路径
把 SPDK 常见症状映射到绑盘/环境、reactor CPU、bdev 队列、传输超时、多路径五轴;给出可核对清单与观测入口,不虚构延迟数字与 RPC dump。
【SPDK 用户态存储】Reactor 与线程模型:Event、Poller 与 Message Passing
钉住 SPDK event framework:每核 reactor、事件队列、poller 轮询与跨核消息;说明 shared-nothing 边界,以及在 reactor 上阻塞等于饿死同核所有工作。