47

考虑一个受 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——它将第 1 步提升到第 4 步之后,如果您已经确切知道自己需要什么,那么这是“倒退”。

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

4

4 回答 4

58

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

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

  • 在执行缓冲 I/O 时保留异步行为(而不仅仅是在执行直接 I/O 时)
  • 更易于使用(尤其是在使用liburing辅助库时)
  • 可以选择以轮询方式工作(但您需要更高的权限才能启用此模式)
  • 减少每个 I/O 的簿记空间开销
  • 由于更少的用户空间/内核系统调用模式切换(由于幽灵/熔毁缓解的影响,这些天很重要) ,从而降低了 CPU 开销
  • 可以预先注册文件描述符和缓冲区以节省映射/取消映射时间
  • 更快(可以实现更高的总吞吐量,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具有以下优点:

Efficient IO with io_uring文档更详细地介绍了IO的io_uring好处和用法。What's new with io_uring文档描述了io_uring自成立以来添加的新功能,而io_uring LWN 的快速增长文章描述了 5.1 - 5.5 内核中的每个内核中可用的功能,并展望了 5.6 中的内容(也请参阅LWN 的 io_uring文章列表)。还有作者 Jens Axboe从 2019 年底开始的“通过 io_uring 实现更快的 IO”视频演示幻灯片)。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)。因此,您也可以从中获得recv()风格 - “短” I/O 行为io_uring

软件/项目使用io_uring

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

软件调查使用io_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 它有一个打包好的liburing可用io_uring的。
  • SLES 15 SP2 具有 5.3 内核,因此io_uring可以使用系统调用。此发行版没有预先打包liburing帮助程序库,但您可以自己构建它。
  • (2021 年中)RHEL 8 的默认内核支持io_uring(此答案的先前版本错误地表示支持)。根据添加 io_uring 支持 Red Hat 知识库文章(内容在订阅者付费墙后面)正在向后移植 io_uring 到默认的 RHEL 8 内核。

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

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

于 2019-08-11T16:04:07.737 回答
18

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。

于 2014-02-20T17:39:41.023 回答
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,

等等

在用户空间级别,还有 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

于 2014-02-17T04:24:53.933 回答
1

对于网络套接字 i/o,当它“准备好”时,它不会阻塞。这就是O_NONBLOCK“准备好”的意思。

对于磁盘 i/o,我们有posix aiolinux aiosendfile和朋友。

于 2012-11-19T01:41:28.293 回答