三种不同实现ublk的零拷贝I/O的方法

用户态块设备ublk,就是提供/dev/ublkbX这样的标准块设备给业务,业务读写这个块的实际IO处理由编写的用户态的代码决定。这就好比使用FUSE,所有对挂载于FUSE的目录的读写都是编写的IO handler来处理一样。使用用户态块设备,可以方便地向上层业务以块设备/dev/ublkbX的形式提供您的自定义存储系统(如ceph)的服务,上层业务只需要对块设备执行标准的读写操作即可。

ublk是社区提出的基于最新的、流行的io_uring passthrough机制实现的用户态块设备,目前已经在block社区引起广泛讨论,并进入到了内核主线。社区提供的代码包括内核态的ublk_drv.ko内核模块和用户态的ublksrv。

三种不同实现ublk的零拷贝I/O的方法_第1张图片

ublk子系统可以创建使用 io_uring 与内核通信的用户空间块驱动程序。使用这种方式实现的驱动程序在性能方面表现出一定的潜力,但存在一个瓶颈:内核和用户空间驱动程序之间存在数据复制。因此,实现 ublk 零拷贝 I/O 显得尤为重要。最近在内核邮件列表中提出三种不同的实现,介绍如何实现这一点。

1、使用 BPF

 

三种不同实现ublk的零拷贝I/O的方法_第2张图片

内核中几乎没有什么问题是无法通过将一些 BPF 混入其中来解决的,零拷贝 ublk I/O 似乎也不例外。 Xiaogang Wang 的这个补丁集添加了一个新的类型(BPF_PROG_TYPE_UBLK),它可以由 ublk 驱动程序加载,并随后注册到一个或多个特定的 ublk 设备。在这种情况下,内核生成的 I/O 请求将传递给该程序,而不是发送给用户空间驱动程序执行。有一个名为 bpf_ublk_queue_sqe() 的新 BPF 辅助函数(不是 kfunc,原因不明),它允许 BPF 程序向环中添加请求,可用于对满足原始块请求的 I/O 操作进行排队。

完全在内核中处理这些请求有一些优势,首先是能够消除与用户空间守护进程的切换。不过,最大的价值可能来自于这样一个事实,即 BPF 程序可以访问内核提供的缓冲区,并可以直接将它们用于满足每个请求所需的任何 I/O,从而消除该数据的副本。块驱动程序可以移动相当多的数据,因此极大的避免复制。目前这个补丁(像其他补丁一样)缺乏基准测试结果来显示它所带来的性能改进。

Patch:https://lwn.net/ml/linux-block/[email protected]/

2、 Fused CMD

三种不同实现ublk的零拷贝I/O的方法_第3张图片

最初 ublk 补丁的作者 Ming Lei 有一种截然不同的方法。与 ublk 本身一样,这项工作的文档很少且难以阅读,因此此描述是逆向工程工作的结果,在某些方面很可能是错误的。

io_uring 环中的操作通常彼此完全分开。有一种方法可以将它们链接起来,这样一个操作必须在下一个操作被分派之前完成,否则每个操作都是不同的。 这个补丁集通过添加“FUSED”的概念,提供了一个相当紧密的链接——两个操作绑定在一起并且可以在它们之间共享资源。

当用户空间 ublk 驱动程序运行时,它将通过环接收来自内核的命令,其中包含诸如“从偏移量 O 处的设备 D 读取 N 个块”之类的指令。驱动程序将可以选择将该操作转换为FUSED命令,该命令将放回环中以在内核中执行。FUSED命令是两个绑定在一起的 io_uring 命令;它们必须作为一个整体提交。 “master”命令(Patch中的术语)是 IORING_OP_FUSED_CMD 类型;它包含足够的信息供 ublk 子系统将命令连接到发送到用户空间驱动程序的请求。相反,“slave”命令执行满足该请求所需的实际 I/O。

与 BPF 解决方案一样,这里的关键是 slave 命令可以访问与 master 关联的缓冲区;在这种情况下,slave 命令可以访问与原始块 I/O 请求关联的内核空间缓冲区。并且,这允许在不将数据复制到用户空间驱动程序或从用户空间驱动程序复制数据的情况下执行 I/O。一旦slave命令完成,用户空间驱动程序可以以通常的方式向内核发出原始块 I/O 请求的完成信号。

Fused命令功能是一种特殊用途,它在任何一般情况下都不起作用。接收Fused命令的子系统必须对它有特殊的支持,具体来说,它必须能够找到slave命令的内核空间缓冲区,并在slave执行之前调用新函数 io_fused_cmd_provide_kbuf() 建立连接,这是对 io_uring 子系统的相当大的更改,并且还不完全清楚任何其他子系统是否能够利用它。

Patch:https://lwn.net/ml/linux-block/[email protected]/

3、 使用 splice()

三种不同实现ublk的零拷贝I/O的方法_第4张图片

在 Lei 的补丁集(第2个版本 ) 发布后的讨论中,Pavel Begunkov 观察到“它看起来有点复杂且带有侵入性”。他认为也许可以重用 splice() 系统调用的机制。 io_uring“注册缓冲区”功能将用于促进零复制操作。此后不久,他发布了初步的概念验证实现,它展示了这种方法如何运作但并不完整。

Lei 对这种方法有很多疑问,主要集中在缓冲区管理的工作原理上。如果 I/O 需要在给定缓冲区上执行多次,则 splice() 方法的工作效果尚不清楚——例如,在写入镜像块设备时。问题不断涌现,Begunkov 尚未(截至撰写本文时)发布补丁的完整版本。 splice() 方法似乎不会走得更远,尽管意外总是会发生。

与此同时,Wang 表示,Fused命令方法似乎是“支持 ublk 零拷贝的正确方向”。

正如最初的 ublk 文章中指出的那样,阻碍操作系统设计微内核方法的关键实际问题之一是组件之间的通信成本。 ublk 已经设法大大降低了该成本,但如果可以消除在内核和用户空间之间复制数据的成本,还有更多的收获。因此,开发人员很可能会继续解决这个问题,直到找到某种可行的解决方案。

Patch:https://lwn.net/ml/linux-block/[email protected]/

参考:

SPDK: ublk Target

ublk: add io_uring based userspace block driver [LWN.net]

Zero-copy I/O for ublk, three different ways [LWN.net]

Commits · ming1/ubdsrv · GitHub

你可能感兴趣的:(服务器,网络,java)