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

Jon*_*tte 25 linux posix asynchronous overlapped-io

考虑一个受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核心.

我把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发生 - 它会在步骤4之后提升第1步,如果你确切知道你需要的话,那就是"向后".

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

Jon*_*tte 16

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

io_submit(2)
io_setup(2)
io_cancel(2)
io_destroy(2)
io_getevents(2)
Run Code Online (Sandbox Code Playgroud)

请注意,2012 - 08年的手册页说,这个实现尚未成熟到可以替换glibc用户空间模拟的程度:

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

这个实现还没有成熟到可以使用内核系统调用完全重新实现POSIX AIO实现的程度.

因此,根据我发现的最新内核文档,Linux还没有一个成熟的,基于内核的异步I/O模型.而且,如果我假设所记录的模型实际上已经成熟,那么它仍然不支持recv()vs read()意义上的部分I/O.

  • 我对你的回答的问题是你推荐"aio_xxx()"函数API作为"异步I/O API".但是,该API实际上并不受Linux系统调用的支持,而是使用glibc中的用户空间线程实现.至少,根据手册页.因此,"aio_xxx()"函数不是问题的答案 - 它们是问题的原因.正确的答案是以"io_setup()"开头的函数. (2认同)

Pet*_*eoh 6

如下所述:

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
Run Code Online (Sandbox Code Playgroud)

如果您询问这些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,
Run Code Online (Sandbox Code Playgroud)

等等

在用户空间级别,还有io_submit()etc 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,
Run Code Online (Sandbox Code Playgroud)

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

  • 实际上,阅读更多内容并不是一个正确的答案:“当前的Linux POSIX AIO实现由glibc在用户空间中提供。” 正确的答案基于io_setup()和io_submit()。 (2认同)

Ano*_*non 6

(2019)如果您使用的是5.1或更高版本的内核,则可以将该io_uring接口用于类似文件的I / O并获得出色的异步操作。

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

  • 与缓冲的直接I / O一起使用
  • 更容易使用
  • 可以选择以轮询方式工作
  • 减少每个I / O的簿记空间开销
  • 由于较少的用户空间/内核syscall上下文切换而降低了CPU开销(由于Spectre / meltdown缓解措施的影响,近来这很重要)
  • “链接模式”可用于表达I / O组之间的依赖关系(> = 5.3内核)
  • 不会在每次星星排列不完全时都变得阻塞

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

关于ioio_uring的好处和用法,“ 带有io_uring的高效IO”文档更加详细。还有一个拍录像,演示文稿“通过io_uring更快的IO”io_uring作者延斯·克斯博。

重新“以recv()vs的方式支持部分I / O read()”:5.3内核中io_uring有一个补丁,它将自动重试短读取。一个尚待解决的补丁(我猜它将在5.4内核中出现)进一步调整了行为,并且在未请求时处理“常规”文件时仅自动处理短读REQ_F_NOWAIT(看起来您可以请求)REQ_F_NOWAIT通过IOCB_NOWAIT或通过打开文件O_NONBLOCK)。因此,看起来您也可以从中获得recv()样式“短” I / O行为io_uring

显然,在编写该io_uring接口时,该接口是一个非常新的接口,但希望它将为Linux带来更好的基于异步文件的I / O故事。