snvme 设计依据:为什么需要一个定制的 NVMe 驱动
Design rationale:本文回答一个常见问题——“snvme 能否用现有内核基础设施替代?” 结论:不能。下面给出逐项对照与根本原因,并给出唯一现实的替代路径(上游化)。
1. snvme 做的三件不可替代的事
在内核 nvme 驱动继续持有控制器的前提下,创建额外的用户态 I/O 队列对 (通过被独占的 Admin Queue 下发
Create I/O CQ/SQ),并把 SQ/CQ DMA 内存、 doorbell BAR 的 mmap 交给用户态;GPU P2P 映射(
peer_memory/后端):pin HBM、建立 NVMe 控制器可见的 DMA 映射,SSD DMA 直达 HBM;poll 模式完成:GPU 线程自己写 SQE、敲 doorbell、轮询 CQ phase bit—— CPU 完全退出数据路径。
2. 现有内核基础设施逐项对照
设施 |
它给了什么 |
为什么不满足 |
|---|---|---|
GDS(nvidia-fs + cuFile) |
不 fork nvme、支持 ext4、DMA 直达 HBM |
每个 IO 仍由 CPU 走 block layer 提交;GPU kernel 无法自主提交(正是 Tutti 论文对比并胜出的基线);且 nvidia-fs 本身也是内核外模块 |
io_uring(含 NVMe passthrough,5.19+) |
用户态提交原始 NVMe 命令、绕过部分 block layer |
提交仍是 CPU( |
AF_XDP |
用户态 ring + NIC 直接 DMA 用户内存 |
网络专用;NVMe 无对应物 |
VFIO 整卡直通 |
用户态 mmap BAR(能摸到 doorbell) |
前提是 unbind 内核 nvme 驱动 → 丢块设备、丢 ext4/FIEMAP;用户态须重写完整 NVMe 栈 |
NVMe 字符设备 passthrough |
原始命令提交 |
每命令一次 CPU syscall;不暴露 doorbell |
3. 根本阻塞点(不随内核版本变化)
Doorbell 所有权:NVMe 的 doorbell 是 BAR0 里的 MMIO 寄存器,只有 bind 到设备的那一个驱动能
ioremap。主线 nvme 驱动从不把 doorbell 页映射给 用户态——这是 NVMe 类设备”OS 所有”的设计立场,不是待实现的功能。Admin Queue 独占:创建额外 I/O 队列必须通过 Admin Queue,而它被内核 驱动私有其 admin tag 与队列结构,无任何导出接口。外部 companion 模块 (gdrdrv / nvidia_peermem 风格)结构上不可能替用户创建队列——这就是 snvme 必须是 fork 而不是伴随模块的原因。
旁证:做同类系统(GPU 自主发起存储 IO)的学术工作——GeminiFS(FAST’25)、 BaM(GPU-initiated storage)——全部实现了自定义内核模块。这个空白是行业性的。
4. RDMA 世界的参照:用户态直敲 doorbell 是什么
RDMA(InfiniBand/RoCE)的 verbs 数据路径以 Mellanox/NVIDIA ConnectX 为例:
─── 控制面(慢路径,内核参与)────────────────────────────
ibv_create_qp / ibv_reg_mr … → uverbs ioctl
内核驱动:创建 QP、pin SQ/RQ/CQ 内存、建 DMA 映射、
并把 doorbell 所在的 BAR 页 mmap 给用户进程
─── 数据面(快路径,0 syscall)──────────────────────────
① 用户直接写 WQE 到自己进程内的 SQ ring(pinned,NIC 可 DMA 读)
② 用户对 mmap 的 doorbell 地址做一次普通内存写(一次 store)
③ NIC 从 SQ ring 取走 WQE,执行 DMA(含直接读写 HBM)
④ NIC 把 CQE DMA 写回用户进程内的 CQ ring
⑤ 用户轮询自己内存里的 CQE(ibv_poll_cq 是纯用户态函数)
Mellanox 的 Blue Flame 优化:doorbell 区域是 write-combining 内存,那次 store 会把 doorbell 值 + WQE 头部一并刷给网卡,省一次 DMA round trip。
安全模型:不是”用户随便写设备寄存器”。内核在 setup 阶段完成全部授权
(ibv_reg_mr pin 内存并登记网卡页表),NIC 只被允许 DMA 到预注册区域;
doorbell 只能投递到内核替你创建的、属于你的队列。越界操作在硬件层被限制。
NIC 世界能做到,是因为设备类协议从第一天就为 user doorbell 设计,且内核 驱动官方支持(uverbs ABI 是稳定接口)。NVMe 的 doorbell 硬件上同样只是 一段可写 MMIO——缺的不是硬件能力,而是主线驱动愿意提供的两样”setup 服务”: 替用户建队列 + 把 doorbell 映射给用户。
5. RDMA ↔ snvme 逐项对照
RDMA 世界 |
snvme 对应物 |
|---|---|
|
ioctl 创建额外 I/O SQ/CQ,ring pin 后映射给用户/GPU |
uverbs mmap doorbell BAR 页 |
|
用户写 WQE + 敲 doorbell,0 syscall |
GPU 线程写 SQE + 敲 doorbell(一次 |
用户轮询 CQE(poll 模式) |
GPU 线程轮询 CQ phase bit,完成随 CUDA stream |
|
|
|
|
一句话:snvme 是把 RDMA 验证了二十年的”user doorbell + pinned ring + poll completion”姿势移植到 NVMe。
6. 替代路径:上游化
摆脱 fork 的唯一实质路径是把该能力提案进 linux-nvme(”驱动管理下的
userspace queue pairs + doorbell mmap + poll completion”)。参考先例:
RDMA 的 uverbs ABI。snvme 的 fork 增量已按 chrdev / 用户队列 / peer_memory
三块收敛(见 kernel_modules/PORTING.md),可进一步整理为按功能拆分的
patch series,降低对每个内核基线的维护成本。