Linux 上真的没有异步块 I/O 吗?

2024-10-17 08:47:00
admin
原创
61
摘要:问题描述:考虑一个受 CPU 限制但也有高性能 I/O 要求的应用程序。我正在将 Linux 文件 I/O 与 Windows 进行比较,我看不出 epoll 对 Linux 程序有什么帮助。内核会告诉我文件描述符“已准备好读取”,但我仍然必须调用阻塞 read() 来获取数据,如果我想读取兆字节,很明显这将...

问题描述:

考虑一个受 CPU 限制但也有高性能 I/O 要求的应用程序。

我正在将 Linux 文件 I/O 与 Windows 进行比较,我看不出 epoll 对 Linux 程序有什么帮助。内核会告诉我文件描述符“已准备好读取”,但我仍然必须调用阻塞 read() 来获取数据,如果我想读取兆字节,很明显这将被阻塞。

在 Windows 上,我可以创建一个设置了 OVERLAPPED 的文件句柄,然后使用非阻塞 I/O,并在 I/O 完成时收到通知,然后使用该完成函数中的数据。我不需要花费应用程序级别的挂钟时间来等待数据,这意味着我可以根据内核数量精确调整线程数,并获得 100% 的高效 CPU 利用率。

如果我必须模拟 Linux 上的异​​步 I/O,那么我必须分配一些线程来执行此操作,这些线程将花费一点时间执行 CPU 操作,并花费大量时间阻塞 I/O,此外,这些线程之间的消息传递也会产生开销。因此,我要么过度订阅 CPU 核心,要么未充分利用 CPU 核心。

我将 mmap() + madvise() (WILLNEED) 视为“穷人的异步 I/O”,但它仍然没有完全完成,因为当它完成时我无法收到通知——我必须“猜测”,如果我猜“错了”,我最终会阻塞内存访问,等待数据来自磁盘。

Linux 似乎在 io_submit 中已经开始使用异步 I/O,而且它似乎也有一个用户空间 POSIX aio 实现,但是这种情况已经持续了一段时间了,而且据我所知没有人会保证这些系统适用于关键的高性能应用程序。

Windows 模型的工作原理大致如下:

  1. 发出异步操作。

  2. 将异步操作绑定到特定的 I/O 完成端口。

  3. 等待该端口上的操作完成

  4. 当 I/O 完成时,等待端口的线程解除阻塞,并返回对待处理 I/O 操作的引用。

步骤 1/2 通常作为单个操作完成。步骤 3/4 通常由工作线程池完成,不一定是发出 I/O 的同一个线程。此模型与 boost::asio 提供的模型有些相似,只是 boost::asio 实际上不提供基于块的异步(磁​​盘)I/O。

与 Linux 中的 epoll 的不同之处在于,在步骤 4 中,尚未发生任何 I/O ——它会提升步骤 1 到步骤 4 之后,如果您已经确切知道自己需要什么,那么这就是“向后”的。

我编写了大量嵌入式、桌面和服务器操作系统,可以说这种异步 I/O 模型对于某些类型的程序来说非常自然。它还具有很高的吞吐量和很低的开销。我认为这是 Linux I/O 模型在 API 级别上仍然存在的真正缺点之一。


解决方案 1:

(2020) 如果您使用的是5.1 或更高版本的 Linux 内核,您可以使用类似文件的 I/Oio_uring接口并获得出色的异步操作。

与现有的libaio/KAIO接口相比,io_uring具有以下优点:

  • 在执行缓冲 I/O 时保留异步行为(而不仅仅是在执行直接 I/O 时)

  • 更易于使用(尤其是使用liburing辅助库时)

  • 可以选择以轮询方式工作(但您需要更高的权限才能启用此模式)

  • 减少每次 I/O 的簿记空间开销

  • 由于用户空间/内核系统调用模式切换更少,从而降低了 CPU 开销(由于spectre/meltdown 缓解措施的影响,这在当今尤为重要)

  • 可以预先注册文件描述符和缓冲区以节省映射/取消映射时间

  • 更快(可以实现更高的总吞吐量,I/O 延迟更低)

  • “链接模式”可以表达 I/O 之间的依赖关系(>=5.3 内核)

  • 可以使用基于套接字的 I/O(从 >=5.3 开始支持recvmsg()/ ,请参阅io_uring.c 的 git 历史记录sendmsg()中提到支持一词的消息)

  • 支持尝试取消排队 I/O (>=5.5)

  • 可以请求始终从异步上下文执行 I/O,而不是默认仅在内联提交路径触发阻塞时才将 I/O 转移到异步上下文(>=5.6 内核)

  • 越来越多地支持执行超出read/write范围的异步操作(例如fsync(>=5.1)、fallocate(>=5.6)、splice(>=5.7) 等)

  • 更高的发展动力

  • 不会因为星星没有完全对齐而造成阻碍

相对于glibc的POSIX AIO来说,io_uring有以下优点:

  • 速度更快,效率更高(上述较低开销的好处在这里更加适用)

  • 接口由内核支持,不使用用户空间线程池

  • 执行缓冲 I/O 时,数据副本较少

  • 无需与信号搏斗

  • Glibc 的 POSIX AIO 不能在单个文件描述符上进行多于一个的 I/O,而io_uring大多数肯定可以!

使用 io_uring实现高效IO 的文档更详细地介绍了io_uring的好处和用法。io_uring的新增功能文档描述了在io_uring5.2 - 5.5 内核之间添加的新功能,而LWN 文章 io_uring 的快速发展描述了 5.1 - 5.5 内核中可用的功能,并展望了 5.6 中将包含哪些功能(另请参阅LWN 的 io_uring 文章列表)。此外,作者 Jens Axboe 于2019 年末推出了通过 io_uring 内核配方实现更快的 IO 视频演示(幻灯片),并于 2022 年中推出了 io_uring 内核配方的新增功能视频演示(幻灯片)io_uring。最后,io_uring 教程之王介绍了io_uring用法。

可以通过io_uring 邮件列表io_uring联系社区,io_uring邮件列表存档显示了 2021 年初的每日流量。

recv()关于“支持vs意义上的部分 I/O read()”:5.3 内核中有一个补丁,它将自动重试io_uring短读取,5.4 内核中还有一个提交,它将行为调整为仅在处理未设置标志的请求的“常规”文件时自动处理短读取REQ_F_NOWAIT(看起来您可以REQ_F_NOWAIT通过IOCB_NOWAIT或通过使用 打开文件来请求)。因此,您也可以从中O_NONBLOCK获得“短”I/O 行为。recv()`io_uring`

使用软件/项目io_uring

尽管该界面还很年轻(其第一个版本于 2019 年 5 月问世),但一些开源软件正在io_uring“广泛”使用它:

  • fio(也是由 Jens Axboe 编写)有一个io_uring ioengine后端(实际上它是在 2019 年 2 月的 fio-3.13 中引入的!)。两位英特尔工程师的“使用新的 Linux 内核 I/O 接口 SNIA 演示文稿提高存储性能”(幻灯片io_uring)指出,当将ioengine 与Optane 设备上的ioengine 进行比较时,他们能够在一个工作负载上获得两倍的 IOPS,而在另一个工作负载上,队列深度为 1 时的平均延迟不到一半libaio

  • SPDK项目在其 v19.04 版本中添加了使用 io_uring (!) 进行块设备访问的支持(但显然这不是您通常使用 SPDK 进行基准测试以外的后端)。最近,他们似乎还在 v20.04 中添加了将其与套接字一起使用的支持……

  • Ceph 于 2019 年 12 月提交了 io_uring 后端,这是其 15.1.0 版本的一部分。提交作者发布了一条 github 评论,显示某些io_uring 后端与 libaio 后端相比,根据工作负载的不同,有优有劣(在 IOPS、带宽和延迟方面)。

  • RocksDB于 2019 年 12 月io_uring为 MultiRead 提供了后端,并成为其6.7.3 版本的一部分。Jens 表示io_uring这有助于大幅缩短延迟。

  • libev 于 2019 年 12 月发布了带有初始io_uring后端的4.31 版本。虽然作者最初的一些观点在较新的内核中得到了解决,但在撰写本文时(2021 年中), libev 的作者对其成熟度做出了一些评论io_uring,并在实施进一步改进之前采取了观望态度。

  • QEMU 于 2020 年 1 月提交了一个 io_uring 后端,它是QEMU 5.0 版本的一部分。在“ QEMU 中的 io_uring:Linux 的高性能磁盘 IO ” PDF 演示文稿中,Julia Suvorova 展示了该io_uring后端在一个随机 16K 块的工作负载上的表现优于threads和后端。aio

  • Samba 于 2020 年 2 月合并了一个io_uringVFS 后端,并成为Samba 4.12 版本的一部分。在“Linux io_uring VFS 后端” Samba 邮件列表线程中,Stefan Metzmacher(提交作者)表示,该io_uring模块在综合测试中能够将吞吐量提高大约 19%(与某些未指定的后端相比)。您还可以阅读Stefan 的“Async VFS Future” PDF 演示文稿,了解这些变化背后的一些动机。

  • Facebook 的实验性 C++ libunifex使用它(但您还需要 5.6+ 内核)

  • Rust 社区一直在编写包装器,以便更io_uring轻松地访问纯 Rust。rio是一个被讨论过的库,作者说,与使用包装在线程中的同步调用相比,它们实现了更高的吞吐量。作者在 FOSDEM 2020 上介绍了他的数据库和库,其中包括一个赞扬优点的部分。io_uring

  • Glommio专门使用rust库io_uring。作者 (Glauber Costa) 发表了一篇名为《现代存储速度很快》的文档。API 很糟糕,表明经过仔细调整后,Glommio 在 Optane 设备上执行顺序 I/O 时可以获得比常规(非io_uring)系统调用高 2.5 倍的性能。

  • Gluster于 2020 年 10 月合并了 io_uring posix xlator ,并成为Gluster 9.0 版本的一部分。提交作者提到性能“并不比常规的 pwrite/pread 系统调用差”。

软件调查使用io_uring

  • PostgreSQL 开发人员 Andres Freund 是改进的推动力之一io_uring(例如,减少文件系统 inode 争用的解决方法)。有一个演示文稿“PostgreSQL 的异步 IO”(请注意,视频在 5 分钟前就坏了)(PDF)激发了对 PostgreSQL 进行更改的需要,并展示了一些实验结果。他表示希望将他的可选io_uring支持纳入 PostgreSQL 14,并且似乎敏锐地意识到甚至在内核级别什么有效,什么无效。2020 年 12 月,Andres 在“阻塞 I/O、异步 I/O 和 io_uring”pgsql-hackers 邮件列表线程中进一步讨论了他的 PostgreSQLio_uring工作,并提到正在进行的工作可以在https://github.com/anarazel/postgres/tree/aio中看到。

  • Netty项目有一个孵化器仓库,正在io_uring支持需要 5.9 内核

  • libuv 有一个针对其添加io_uring支持的拉取请求,但其在项目中的进展缓慢

  • SwiftNIO 在 2020 年 4 月增加了io_uring对事件的支持(但不包括系统调用),Linux:完整的 io_uring I/O问题概述了进一步整合它的计划

  • Tokio Rust 项目已经开发出一个概念验证tokio-uring

Linux 发行版支持io_uring

  • (2020 年末)Ubuntu 18.04 的最新 HWE 支持内核是 5.4,因此io_uring可以使用系统调用。此发行版未预先打包liburing帮助程序库,但您可以自行构建。

  • Ubuntu 20.04 的初始内核是 5.4,因此io_uring可以使用系统调用。如上所述,该发行版没有预先打包liburing

  • Fedora 32 的初始内核是 5.6 ,并且已经打包liburingio_uring可以使用。

  • SLES 15 SP2 有一个 5.3 内核,因此io_uring可以使用系统调用。此发行版未预先打包liburing帮助程序库,但您可以自行构建。

  • (2021 年中)RHEL 8 的默认内核支持io_uring(此答案的先前版本错误地表示它支持)。有一篇添加 io_uring 支持 Red Hat 知识库文章(内容位于订阅者付费墙后面)正在“进行中”。

  • (2022 年中)RHEL 9 的默认内核支持io_uring。内核足够新(5.14),但io_uring明确禁用了对的支持。


希望io_uring能够为 Linux 带来更好的异步文件 I/O 故事。

(为了给这个答案增加一点可信度,在过去的某个时候,Jens Axboe(Linux 内核块层维护者和发明者io_uring)认为这个答案可能值得投票:-)

解决方案 2:

真正的答案,由 Peter Teoh 间接指出,基于 io_setup() 和 io_submit()。具体来说,Peter 指出的“aio_”函数是基于线程的 glibc 用户级仿真的一部分,这不是一种有效的实现。真正的答案是:

io_submit(2)
io_setup(2)
io_cancel(2)
io_destroy(2)
io_getevents(2)

请注意,2012-08 的手册页指出,此实现尚未成熟到可以取代 glibc 用户空间模拟的程度:

http://man7.org/linux/man-pages/man7/aio.7.html

该实现尚未成熟到可以使用内核系统调用完全重新实现 POSIX AIO 实现的程度。

因此,根据我能找到的最新内核文档,Linux 尚未拥有成熟的基于内核的异步 I/O 模型。而且,如果我假设文档中的模型实际上已经成熟,它仍然不支持 recv() 与 read() 意义上的部分 I/O。

解决方案 3:

详见:

http://code.google.com/p/kernel/wiki/AIOUserGuide

还有这里:

http://www.ibm.com/developerworks/library/l-async/

Linux 确实在内核级别提供了异步块 I/O,API 如下:

aio_read    Request an asynchronous read operation
aio_error   Check the status of an asynchronous request
aio_return  Get the return status of a completed asynchronous request
aio_write   Request an asynchronous operation
aio_suspend Suspend the calling process until one or more asynchronous requests have completed (or failed)
aio_cancel  Cancel an asynchronous I/O request
lio_listio  Initiate a list of I/O operations

如果你问这些 API 的用户是谁,答案是内核本身——这里只显示了一小部分:

./drivers/net/tun.c (for network tunnelling):
static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,

./drivers/usb/gadget/inode.c:
ep_aio_read(struct kiocb *iocb, const struct iovec *iov,

./net/socket.c (general socket programming):
static ssize_t sock_aio_read(struct kiocb *iocb, const struct iovec *iov,

./mm/filemap.c (mmap of files):
generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov,

./mm/shmem.c:
static ssize_t shmem_file_aio_read(struct kiocb *iocb,

ETC。

在用户空间级别,还有 io_submit() 等 API(来自 glibc),但以下文章提供了使用 glibc 的替代方法:

http://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt

它直接将 io_setup() 等函数的 API 实现为直接系统调用(绕过 glibc 依赖项),应该存在通过相同“__NR_io_setup”签名的内核映射。在以下位置搜索内核源代码:

http://lxr.free-electrons.com/source/include/linux/syscalls.h#L474 (此 URL 适用于最新版本 3.13)你会看到内核中这些 io_*() API 的直接实现:

474 asmlinkage long sys_io_setup(unsigned nr_reqs, aio_context_t __user *ctx);
475 asmlinkage long sys_io_destroy(aio_context_t ctx);
476 asmlinkage long sys_io_getevents(aio_context_t ctx_id,
481 asmlinkage long sys_io_submit(aio_context_t, long,
483 asmlinkage long sys_io_cancel(aio_context_t ctx_id, struct iocb __user *iocb,

glibc 的后续版本应该使得使用“syscall()”来调用 sys_io_setup() 变得没有必要,但如果没有最新版本的 glibc,如果您使用的是具有“sys_io_setup()”功能的后续内核,您总是可以自己进行这些调用。

当然,还有其他用于异步 I/O 的用户空间选项(例如,使用信号?):

http://personal.denison.edu/~bressoud/cs375-s13/supplements/linux_altIO.pdf

或者可能:

POSIX 异步 I/O(AIO)的状态如何?

“io_submit” 和相关函数在 glibc 中仍然不可用(参见 io_submit 手册页),我已在我的 Ubuntu 14.04 中进行了验证,但此 API 是 Linux 特有的。

其他如 libuv、libev 和 libevent 也是异步 API:

http://nikhilm.github.io/uvbook/filesystem.html#reading-writing-files

http://software.schmorp.de/pkg/libev.html

http://libevent.org/

所有这些 API 都旨在实现在 BSD、Linux、MacOSX 甚至 Windows 之间的移植。

在性能方面,我没有看到任何数字,但怀疑 libuv 可能是最快的,因为它的轻量级?

https://ghc.haskell.org/trac/ghc/ticket/8400

解决方案 4:

对于网络套接字 i/o,当它处于“就绪”状态时,它不会阻塞。这就是O_NONBLOCK“就绪”的含义。

对于磁盘 i/o,我们有posix aio、linux aio、sendfile和朋友。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用