POSIX异步I/O(AIO)的状态是什么?

101
在网络上有一些零散的页面介绍 POSIX AIO 设施,内容详细程度不尽相同。它们当中没有一个是非常新的。它们在描述什么并不清晰。例如,“官方”(?)Linux 内核异步 I/O 支持的网站在此处表示套接字无法工作,但我 Ubuntu 8.04.1 工作站上的“aio.h”手册页似乎都暗示它可以用于任意文件描述符。还有另一个项目似乎在库层工作,但文档更少。
我想知道:
  • POSIX AIO 的目的是什么?考虑到我能找到的最明显的实现例子都说它不支持套接字,整个东西对我来说似乎很奇怪。它只是为异步磁盘 I/O 而设计的吗?如果是这样,为什么要使用如此超级通用的 API?如果不是这样,为什么磁盘 I/O 是首先被攻击的对象?
  • 有什么完整的 POSIX AIO 程序样例供我查看吗?
  • 是否有人真正使用它?
  • 哪些平台支持 POSIX AIO?它们支持其中的哪些部分?是否有人真正支持似乎承诺的“任何 FD 的任何 I/O”?
我可以使用其他可用的多路复用机制,但那些零散的信息片段让我很好奇。
4个回答

75

使用kqueue、epoll、IO完成端口等机制可以高效地进行套接字I/O。异步文件I/O算是后来者(除了Windows的重叠I/O和Solaris对POSIX AIO的早期支持)。

如果您想进行套接字I/O,最好使用上述机制之一。

因此,AIO的主要目的是解决异步磁盘I/O的问题。这很可能是为什么Mac OS X仅支持常规文件的AIO,而不支持套接字(因为kqueue在这方面做得更好)。

写操作通常由内核缓存,并在稍后刷新。例如,当驱动器的读取头恰好经过要写入块的位置时。

但是,对于读操作,如果您希望内核优先处理和排序读取操作,则AIO确实是唯一的选择。以下是内核可以(理论上)比任何用户级应用程序更好地执行此操作的原因:

  • 内核可以看到所有磁盘I/O操作,不仅仅是你的应用程序的磁盘作业,并且可以在全局层面上对它们进行排序。
  • 内核(可能)知道磁盘读取头的位置,并且可以按最佳顺序选择你传递给它的读取作业,以使磁头移动的距离最短。
  • 内核可以利用本地命令队列进一步优化你的读取操作。
  • 使用lio_listio()可能比readv()每个系统调用发出更多的读取操作,特别是如果你的读取不是(逻辑上)连续的,则可以节省一点系统调用开销。
  • AIO使你的程序可能会稍微简单一些,因为你不需要额外的线程来阻塞在读取或写入调用中。

话虽如此,posix AIO具有相当笨拙的接口,例如:

  • 唯一有效且得到良好支持的事件回调方式是通过信号(signal)实现的,这使得在库中使用它变得困难,因为这意味着要从进程全局信号命名空间中使用信号编号。如果您的操作系统不支持实时信号,那么这也意味着您必须循环遍历所有未完成的请求,以找出哪一个实际上已经完成(例如Mac OS X,而不是Linux)。在多线程环境下捕获信号也会带来一些棘手的限制。通常情况下,您不能在信号处理程序内部对事件做出反应,而是必须引发一个信号、写入管道或使用signalfd()(在Linux上)。
  • lio_suspend()存在与select()相同的问题,随着作业数的增加,其效率并不高。
  • 实现的lio_listio()传递的作业数量相当有限,而且以可移植方式找到此限制并不容易。您必须调用sysconf(_SC_AIO_LISTIO_MAX),可能会失败,在这种情况下,您可以使用AIO_LISTIO_MAX定义,但这些定义不一定被定义,然后您可以使用2,这被保证支持。

至于使用posix AIO的实际应用程序,您可以查看lighttpd(lighty),在引入支持时还发布了性能测量

大多数posix平台现在都支持posix AIO(Linux、BSD、Solaris、AIX、tru64)。Windows通过其重叠文件I/O支持它。我的理解是,只有Solaris、Windows和Linux真正支持异步。文件I/O一直到驱动程序,而其他操作系统则使用内核线程模拟异步。Linux是个例外,它的glibc posix AIO实现使用用户级线程模拟异步操作,而其本地异步I/O接口(io_submit()等)从驱动程序一直到底层都是真正的异步,假设驱动程序支持它。
我认为,在操作系统中普遍存在不支持任何fd的posix AIO,而是将其限制为常规文件。

自Win32首次推出以来,Windows就支持磁盘文件的OVERLAPPED I/O。这一点并不新奇。而在POSIX中,信号命名空间不是进程全局的,而是每个线程的。信号会传递到特定的线程(或者aio是否有例外,我记不太清楚了?)。 - Ben Voigt
1
无法指定AIO将其信号传递给哪个线程。在Linux上,它似乎大多数情况下将其传递给发出aio_*()命令的线程,但并非总是如此(我找到的唯一解决方案是创建多个signalfd)。几年前,在内核邮件列表上有一个Linux补丁,可以添加此功能,但它从未被采纳,并且它将成为POSIX的扩展。在Mac OS X上,信号似乎主要传递给主线程(根据我的经验)。我不认为POSIX需要特定的行为,如果确实需要,我很想看到规范的部分。 - Arvid
6
glibc 实现 aio_read/write 使用用户空间线程,因此这里甚至没有使用内核线程。 - Marenz
“always typically” 是什么意思?对于任何方法或使用 AIO 时,写入都会被内核缓存吗?似乎必须有一种方法让软件确信写入已成功完成;否则,完整性和事务目标无法实现。 - MikeB
另一个可以使用AIO的实时示例是nginx。支持所有模式。如果您喜欢将负载卸载到用户空间线程,则通常会发现它比直接IO要差得多,但Linux本地AIO与直接IO相当。 AIO可以显着有益的情况是严重的页面缓存压力。异步和直接IO之间的概念差异可以在此处查看http://ftp.dei.uc.pt/pub/linux/kernel/people/suparna/aio-linux.pdf。 - wick
显示剩余4条评论

29

由于所有编写 POSIX 网络服务器的人都使用基于事件、非阻塞的方法,因此网络 I/O 对于 AIO 不是优先级。旧式的 Java "数十亿个阻塞线程" 方法非常糟糕。

磁盘写入 I/O 已经进行了缓冲,磁盘读取 I/O 可以使用像 posix_fadvise 这样的函数预取到缓冲区中。这就使得直接、无缓存的磁盘 I/O 成为 AIO 的唯一有用目的。

直接、无缓存 I/O 只对事务性数据库真正有用,而那些数据库往往会写自己的线程或进程来管理它们的磁盘 I/O。

所以,最终 POSIX AIO 没有提供 任何 有用的作用。不要使用它。


9
从网络文件系统(NFS、Samba)读写有何不同? - Alex B
40
我不同意。磁盘I/O往往是有缓冲的,但它也可能会被阻塞。当使用poll()来检测文件FD时,它总是报告FD可读,即使它将被阻塞。这使得在事件驱动方式下无法以非阻塞操作方式处理磁盘文件,除非使用线程或AIO。 - Hongli
2
@Matt:对于数据报套接字,顺序并不重要。 @Zan:异步I/O非常适用于预缓冲实时流媒体数据,例如媒体播放器。 - Ben Voigt
15
在事件驱动系统中,AIO并非无用。通过正确使用AIO,实际上可以实现零拷贝网络,而使用基于事件的recv()通知则做不到这一点。尽管其他因素可能导致这在大多数情况下只是理论限制,但我认为在Linux中缺乏像Windows上的OVERLAPPED那样的适当AIO是最后一个重要的空缺。 - Jon Watte
1
答案并未说明哪些平台实际上支持哪些描述符的操作。 - EFraim
显示剩余7条评论

13

6
那将是Arvid,他也在上面回复了 :) - dpn
优秀的报告。非常有启发性。感谢您的分享。 - chmike

2

有aio_write - 在glibc中实现; 第一次调用aio_read或aio_write函数会生成多个用户模式线程,aio_write或aio_read将请求发送到该线程,线程执行pread / pwrite操作,完成后将答案发布回被阻塞的调用线程。

还有“真正的”aio - 受内核级别支持(需要libaio,参见io_submit调用http://linux.die.net/man/2/io_submit); 也需要O_DIRECT(也可能不受所有文件系统支持,但主要文件系统都支持它)

请参见此处:

http://lse.sourceforge.net/io/aio.html

http://linux.die.net/man/2/io_submit

在Linux上POSIX AIO和libaio之间的区别?


aio_write 的许多缺陷在 https://dev59.com/1HVD5IYBdhLWcg3wGXlI#5307557 中已经介绍过了。 - Glyph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接