LWN:ublk 零复制I/O的三种方式!

关注了就能看到更多这么棒的文章哦~

Zero-copy I/O for ublk, three different ways

By Jonathan Corbet
March 16, 2023
DeepL assisted translation
https://lwn.net/Articles/926118/

ublk 子系统可以创建用户空间的块设备(block)驱动程序,并使用 io_uring 与内核通信。以这种方式实现的驱动程序在性能方面看起来前景不错,但有一个性能瓶颈:需要在内核和用户空间驱动程序的地址空间之间复制数据。因此,不出意外,人们希望实现 ublk 的 zero-copy I/O。在邮件列表中,最近出现了关于如何实现这一目标的三种不同方案。

1: Use BPF

内核中几乎没有什么问题是不能通过添加一些 BPF 来解决的,zero-copy ublk I/O 似乎也不例外。Xiaoguang Wang 的一组 patch 添加了一个新的程序类型(BPF_PROG_TYPE_UBLK),可以被 ublk 驱动加载,随后被注册到一个或多个特定的 ublk 设备上。一旦发生了这种情况,那么由内核产生的 I/O 请求将被传递给该程序,而不是被发送到用户空间的驱动程序上执行。也新增了一个 BPF 辅助函数(不是一个 kfunc,原因尚不清楚),叫做 bpf_ublk_queue_sqe(),供 BPF 程序把请求添加到 ring 上;这个辅助函数可以用来把 I/O 操作放到队列里,从而完成原本的 block 读写请求。

完全在内核中处理这些请求会有一些好处,首先是能够消除跟用户空间守护程序的来回交互。但是,最大的优势可能来自于这样一个事实:BPF 程序可以访问内核提供的 buffer,并且可以直接使用它们来完成各个请求所需的 I/O 操作,从而消除了数据的 copy 动作。块设备驱动可以搬移非常多的数据,所以避免 copy 的话会很有好处。不过这个 patch(跟这里讨论的所有其他 patch 一样)都没有提供能展示性能改善数据的基准结果。

2: Fused operations

Ming Lei,原始 ublk patch 的作者,则有一个很不一样的方案。就像 ublk 本身一样,这个方案文档很少,而且很难读懂,所以这个描述完全是来自于逆向工程分析,很可能不一定准确。

io_uring 的 ring 里的各个操作通常是彼此完全独立的。有一种方法可以将它们 link 起来,使一个操作必须在下一个操作被派发之前完成,但除此之外,每个操作都是独立的。Lei 的 patch set 通过增加 "fused" 操作的概念,让这些操作可以更紧密的联系起来,也就是把两个操作捆绑在一起,它们之间可以共享资源。

在运行一个用户空间的 ublk 驱动时,它将通过 ring 来接收来自内核的指令,指令内容是 "从设备 D 的偏移量 O 处读取 N 块数据"。通过合入 Lei 的这组 patch,驱动程序可以选择将该操作变成一个 fused command,并将其放回 ring 里,以便在内核中执行。一个 fused command 是两个绑在一起的 io_uring 命令;它们必须作为一个单元来提交。“master”命令(Lei 采用的术语)是 IORING_OP_FUSED_CMD 类型的;它包含足够的信息让 ublk 子系统把该命令连接到发送给用户空间驱动的那个请求上。而 "slave" 命令则会执行满足该请求所需的实际 I/O 操作。

跟 BPF 的解决方案一样,这里的关键是 slave 命令可以访问与 master 命令关联起来的 buffer;也就是说,slave 命令可以访问与原来的 block I/O 请求相关的内核空间的 buffer。同样,这使得 I/O 的执行不需要将数据复制到用户空间驱动或从用户空间驱动进行复制了。一旦 slave 命令完成了,那么用户空间的驱动程序就可以采用往常的方式向内核发出原本的 block I/O 请求完成的信号了。

fused 命令的功能是一个专用于特殊用途的怪兽;它无法在通常的场景下使用。接收 fused 命令的子系统必须有对它的特殊支持,具体来说,它必须能够为 slave 命令找到内核空间的 buffer,并在 slave 命令执行之前通过调用新函数 io_fused_cmd_provide_kbuf()来建立连接。这对 io_uring 子系统来说是一个相当大的改变,而且不是很清楚是否有其他什么子系统能够利用这个功能。

3:使用 splice()
在 Lei 的 patch set 第 2 版发布后的讨论中,Pavel Begunkov 观察到 "这一切看起来有点复杂,并且有些侵入性"。他认为,也许可以重新使用 splice() 系统调用的机制。io_uring 的 "registered buffer" 功能将被用来帮助零拷贝操作。此后不久,他发布了一个初步的、概念验证形式的实现;它显示了这种方法是如何工作的,但并不完整。

Lei 对这种方法有很多疑问,主要集中在缓冲区是如何管理的。目前还不清楚,如果需要对一个特定的 buffer 进行多次 I/O (例如,在写到一个镜像块设备)时 splice()的方案该如何实现。问题层出不穷,而 Begunkov 还没有(截至本文写作时)发布完整版本的 patch。看起来,splice()方法很可能不会走得更远,尽管我们也经常看到一些意外。

同时,Wang 说,fused-command 的方案似乎是 "支持 ublk 零拷贝的正确方向"。

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

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

format,png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值