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

【SPDK 用户态存储】环境与绑盘:Hugepage、CPU 亲和与 VFIO/UIO

文章导航

分类入口
storagelinux
标签入口
#spdk#vfio#uio#hugepage#setup-sh#cpu-affinity#dma#v26.01

目录

上一篇钉住了 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 StartedDirect Memory Access (DMA) From User Spacescripts/setup.sh @ tag v26.01。命令给出可执行语义;无本机直通 NVMe 则不粘贴伪造 setup / lspci / 绑定输出


一、先问:用户态驱动缺什么内核已经给过的东西

内核 NVMe 驱动拥有:

用户态驱动要自己(或经 DPDK EAL / SPDK env)补齐至少三块:

  1. 可 DMA 且地址可翻译的内存(大页 + 固定映射叙事;IOMMU 下则为 IOVA)。
  2. PCI 资源访问权(BAR 映射、配置空间;通常经 VFIO 或 UIO)。
  3. CPU 与中断局部性(亲和;轮询模式下业务核与残留内核中断的关系仍要管)。

Getting Started 把「跑示例前」收成一句可操作流程:分配 hugepage,并把 NVMe(及文档提及的 I/OAT 等)从原生内核驱动解绑。入口脚本是 root 下的 scripts/setup.sh,一般每台机器准备一次即可。


二、Hugepage:不是「越大越好」的口号

2.1 为何是大页

DMA From User Space 解释了约束链:

因此:业务数据缓冲不能 malloc 完事;必须走 spdk_dma_malloc() 或其兄弟(第 5 篇)。setup.sh 分配的大页是整机/进程环境的原料,不是自动把任意指针变成 DMA 安全。

2.2 setup.shHUGEMEM

官方语义(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 在做什么(语义级)

脚本的核心副作用:

  1. 配置并预留 hugepage。
  2. 把匹配的存储类 PCI 设备从内核驱动 unbind
  3. 绑定到用户态可用的驱动框架——现代推荐路径是 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 模型默认「核 ≈ 一条忙轮询循环」。亲和要回答三个问题:

  1. 哪些核跑 SPDK reactorspdk_app 的 CPUs / mask 类选项,以应用帮助与 env 文档为准)。
  2. 哪些核留给内核、网络软中断、同机其他数据面(例如仍用 io_uring 的数据库)。
  3. 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」,而在:

  1. 专用核空转税是否可接受(尤其是昼夜负载差大的集群)。
  2. 设备独占与容器/多租户编排是否兼容——编排系统默认假设块设备经内核。
  3. 与 io_uring 同机:一套盘给内核、一套盘给 SPDK 是常见物理隔离;共享同一控制器的多 namespace 策略则要读清硬件与多进程文档,不能想当然。

开放问题留给第 15–16 篇:何时「内核 NVMe + io_uring」在运维总账上优于用户态独占;DPU 把 target 卸走后,主机侧 env 还剩哪些义务。


七、与容器 / 编排共置时多出来的问题

裸金属上手动 setup.sh 已经锋利;把同一模型塞进容器后,还会多三层摩擦:

  1. 设备与 IOMMU 组如何进 Pod/容器:VFIO 设备通常要以设备插件或特权方式注入;IOMMU 组隔离失败时,轻则看不到盘,重则权限模型被掏空。
  2. 大页如何宣告与计费:hugetlb 与 cgroup 记账不一致时,节点「看起来有内存」但 EAL 初始化失败;编排器按普通内存调度会系统性低估存储面进程。
  3. 生命周期谁先死:容器停了但 reset 没跑,宿主机上设备仍停在 vfio-pci,下一个内核态租户会认为「盘丢了」。反之,编排强杀进程时,飞行中 DMA 与热拔语义要靠驱动的 SIGBUS/错误完成路径兜底(第 4 篇),不能假设「进程没了映射就安全没了」。

这些不是劝退容器,而是要求把 env 当成与应用镜像同等重要的节点状态:绑盘、大页、核隔离策略应进 runbook / DaemonSet,而不是开发者笔记本上的一次性命令。

对照内核路径:块设备经 CSI/本地卷进容器时,所有权故事仍由内核统一;SPDK 把所有权挪到用户态进程后,编排系统默认假设被打破。第 16 篇选型排除树会问:你的平台能不能承受「盘是进程附属物」?


八、参考资料

规范 / 官方文档(A)

源码(A)

站内对照

实验台账


九、小结

  1. setup.sh 做大页 + 解绑/绑定,不是「安装一个内核模块就结束」。
  2. Hugepage 服务 DMA 安全与地址稳定;缓冲仍须走 DMA 分配 API。
  3. VFIO+IOMMU 是官方推荐长期路径;UIO 是受限场景的可用性出口。
  4. 设备独占会拆掉同盘内核工具链——reset 是一等运维动作。
  5. CPU 亲和按专用轮询预算规划,并给内核与其它数据面留核。
  6. 容器/编排要把绑盘与大页当成节点状态;默认 CSI 假设在这里不成立。

上一篇:Reactor 与线程模型 · 系列目录 · 下一篇:用户态 NVMe 驱动

读完这篇,下一步读什么

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


By .