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

vh0rcniy  于 2023-11-17  发布在  Linux
关注(0)|答案(4)|浏览(153)

考虑一个CPU受限的应用程序,但也有高性能I/O需求。
我正在比较Linux和Windows的文件I/O,我看不出epoll对Linux程序有什么帮助。内核会告诉我文件描述符“准备好阅读了”,但我仍然需要调用blocking read()来获取数据,如果我想读取兆字节,很明显这会阻塞。
在Windows上,我可以创建一个设置为OVERLAPPED的文件句柄,然后使用非阻塞I/O,并在I/O完成时收到通知,并使用该完成函数中的数据。我不需要花费应用程序级别的挂钟时间等待数据,这意味着我可以精确地将线程数量调整为核心数量,并获得100%的CPU利用率。
如果我必须在Linux上模拟异步I/O,那么我必须分配一定数量的线程来做这件事,这些线程将花费一点时间做CPU的事情,并且大量的时间阻塞I/O,再加上与这些线程之间的消息传递会产生开销。因此,我将过度订阅或未充分利用我的CPU核心。
我把mmap()+ madvise()(WILLNEED)看作是一个“穷人的madvise I/O”,但它仍然没有完全实现,因为当它完成时我不能得到通知--我必须“猜测”,如果我猜错了,我将最终阻塞内存访问,等待数据来自磁盘。
Linux似乎在io_submit中开始了POSIX I/O,它似乎也有一个用户空间POSIX aio实现,但它已经这样一段时间了,我知道没有人会为这些系统的关键,高性能应用程序担保。
Windows模型的工作原理大致如下:
1.发出异步操作。
1.将异步操作绑定到特定的I/O完成端口。
1.等待该端口上的操作完成
1.当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级别上剩余的真实的缺点之一。

4uqofj5v

4uqofj5v1#

(2020)如果您使用的是5.1或更高版本的Linux内核,则可以将io_uring interface用于类似文件的I/O,并获得出色的异步操作。
与现有的libaio/KAIO接口相比,io_uring具有以下优点:

  • 在执行缓冲I/O时保留异步行为(而不仅仅是在执行直接I/O时)
  • 要使用的插件(特别是在使用liburing助手库时)
  • 可以选择以轮询方式工作(但您需要更高的权限才能启用此模式)
  • 减少每个I/O的簿记空间开销
  • 更低的CPU开销,因为用户空间/内核系统调用模式切换更少(由于impact of spectre/meltdown mitigations,这是一个大问题)
  • 可以预先注册文件描述符和缓冲区,以保存Map/取消Map时间
  • 更快(可以实现更高的聚合吞吐量,I/O具有更低的延迟)
  • “链接模式”可以表示I/O之间的依赖关系(>=5.3内核)
  • 可以使用基于套接字的I/O(recvmsg()/sendmsg()从>=5.3开始支持,请参阅在io_uring.c's git history中提到单词support的消息)
  • 支持尝试取消排队的I/O(>=5.5)
  • 可以请求I/O * 总是 * 从异步上下文中执行,而不是默认的,当内联提交路径触发阻塞时,只回退到将I/O输出到异步上下文中(>=5.6内核)
  • 对执行超过read/write的异步操作的支持不断增加(例如fsync(>=5.1)、fallocate(>=5.6)、splice(>=5.7)等)
  • 更高的发展势头
  • Doesn't become blocking each time the stars aren't perfectly aligned

与glibc的POSIX AIO相比,io_uring具有以下优点:

*更快更高效(上述更低的开销优势在这里更加适用)

  • 接口是内核支持的,并且不使用用户空间线程池
  • 执行缓冲I/O时,数据拷贝较少
  • 不要纠结于信号
  • Glibc的POSIX AIO在一个文件描述符上不能有多个I/O,而io_uring肯定可以!

Efficient IO with io_uring文档详细介绍了io_uring的优点和用法。What's new with io_uring文档描述了在5. 2 - 5. 5内核之间添加到io_uring的新功能,虽然The rapid growth of io_uring LWN article描述了5.1 - 5.5内核中的每个内核都有哪些功能,(另请参阅LWN的io_uring文章列表)。还有io_uring作者Jens Axboe从2019年底开始的Faster IO through io_uring Kernel Recipes videoed presentationslides)和从2022年中期开始的What’s new with io_uring Kernel Recipes videoed presentationslides)。最后,Lord of the io_uring tutorial介绍了io_uring的用法。
io_uring社区可以通过io_uring邮件列表和io_uring mailing list archives显示2021年初的每日流量。
关于“在recv()read()的意义上支持部分I/O“:一个patch went into the 5.3 kernel that will automatically retry io_uring short reads和一个进一步的提交进入了5.4内核,将行为调整为only automatically take care of short reads when working with "regular" files on requests that haven't set the REQ_F_NOWAIT flag。(看起来你可以通过IOCB_NOWAIT请求REQ_F_NOWAIT,或者用O_NONBLOCK打开文件)。因此,你可以得到recv()样式-“短”I/io_uring的O行为。

使用io_uring的软件/项目

虽然这个接口还很年轻(它的第一个版本于2019年5月发布),但一些开源软件正在“野外”使用io_uring

使用io_uring进行软件调查

io_uring Linux发行版支持

  • (2020年底)Ubuntu 18.04的最新HWE启用内核是5.4,因此可以使用io_uring系统调用。此发行版没有预打包liburing助手库,但您可以自行构建它。
  • Ubuntu 20.04的初始内核是5.4,所以可以使用io_uring系统调用。如上所述,发行版没有预先打包liburing
  • Fedora 32的初始内核是5.6**,它有一个打包的liburing,所以io_uring是可用的。
  • SLES 15 SP2 has a 5.3 kernel所以可以使用io_uring系统调用。这个发行版没有预先打包liburing助手库,但是你可以自己构建它。
  • (2021年中期)RHEL 8的默认内核支持io_uring(此答案的先前版本错误地说它支持)。有一个Add io_uring support Red Hat knowledge base article(内容位于订阅者付费墙后面)正在“进行中”。
  • (Mid 2022)RHEL 9's default kernel does not support io_uring.内核足够新(5.14),但对io_uring的支持被显式禁用。

希望io_uring能为Linux带来更好的异步文件式I/O。
(To在过去的某个时候,Jens Axboe(Linux内核块层维护者和io_uring的发明者)thought this answer might be worth upvoting:-)

dffbzjpn

dffbzjpn2#

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。

qlckcl4x

qlckcl4x3#

正如解释:
http://code.google.com/p/kernel/wiki/AIOUserGuide
这里:
http://www.ibm.com/developerworks/library/l-async/
Linux在内核级别提供了Java数据块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,



在用户空间级别,还有io_submit()等API(来自glibc),但以下文章提供了使用glibc的替代方案:
http://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt
它直接将io_setup()等函数的API实现为直接syscall(绕过glibc依赖),通过相同的“_NR_io_setup”签名的内核Map应该存在。
http://lxr.free-electrons.com/source/include/linux/syscalls.h#L474适用于最新版本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
或者说:
What is the status of POSIX asynchronous I/O (AIO)?
“io_submit”和friends在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

ql3eal8s

ql3eal8s4#

对于网络套接字i/o,当它“就绪”时,它不会阻塞。这就是O_NONBLOCK和“就绪”的意思。
对于磁盘I/O,我们有posix aiolinux aiosendfile和朋友。

相关问题