I/O 模型

最后修改于

I / O 模型#

Blocking IO#

BIO 模型,是完全的同步模型,最符合人类直觉。简单来说就是发起一个 IO 系统调用,然后会导致当前线程出让 CPU 控制权,线程被挂起,直到系统调用完成,在合适的时机再被 OS 唤醒。
对应于系统调用的openfcntl

loading...

这很简单也很有用,但还可以做得更好。

Non-Blocking IO#

NIO 模型,非阻塞 IO 模型,这是相对 BIO 的一种改进。在 Linux 上,也就是对于openfnctl 这两个系统调用增加O_NONBLOCK 选项。

如果可能,文件会以非阻塞模式打开。 open 操作或是对其返回的 FD 执行的任何后续 I / O 操作都不会导致调用进程阻塞。
如果当前需要做的 I/O 操作对应的文件/设备没有准备好(例如被占用),会返回一个错误EAGAIN
表明当前 I / O 操作没有准备好。
当无法继续进行 I / O 时(如 Buffer 不足),也会返回一个错误EAGAIN,以及此次 I/O 完成的字节数。
等下一次,程序再次进行 IO 操作的时候,继续询问,当对应资源准备就绪时,再由程序进行实际上的读写操作,发起 I / O 系统调用。
loading...

Note

值得注意的是,O_NONBLOCK 对常规文件或者块设备无效。当对这样的文件进行 IO 操作时,仍然是阻塞的(即使当前文件被其他进程占用)。但是在 Server 场景下,谈论的多半是 socket 进行通信,此时 O_NONBLOCK 是有效的。
使用如下命令可以查看所有文件类型

man 2 stat

通常你会得到如下的结果,每种对应一个文件类型。

#define        S_IFIFO  0010000  /* named pipe (fifo) */
#define        S_IFCHR  0020000  /* character special */
#define        S_IFDIR  0040000  /* directory */
#define        S_IFBLK  0060000  /* block special */
#define        S_IFREG  0100000  /* regular */
#define        S_IFLNK  0120000  /* symbolic link */
#define        S_IFSOCK 0140000  /* socket */
#define        S_IFWHT  0160000  /* whiteout */

TODO
use jslinux show an io example JSLinux (bellard.org)

NIO 参考#

open(2) - Linux manual page (man7.org)
fcntl(2) - Linux manual page (man7.org)
read(2) - Linux manual page (man7.org)
write(2) - Linux manual page (man7.org)

I/O Multiplexing#

IO 复用模型,与前两个模型不太一样,前两个模型。是针对单文件 I / O 时采用的模型。
而 IO 复用模型则是基于事件系统的多文件的 I / O。它并非描述单个文件进行 IO 操作时发生的事情。而是多个文件需要 IO 时,要进行的协调操作。在 Linux 中对应着select,poll,epoll 这三个 API。
其思路在于,同时监听多个目标文件的事件,当其中任何一个目标文件准备好时,可以对准备好的文件进行进行 I / O 处理。
其中select,poll,epoll 并不进行实际上的 I/O 操作,而是帮助线程监听多个文件的状态,当有文件准备好 I/O 时,可以进行读取。因此,可以它可以与 BIONIO 结合使用。一般而言,结合NIO 是更好的选择。
loading...

Note

值得注意的是,尽管可以通过等待 select 这三个 api 意图是拿到准备好的进行 I/O 的事件,但是并不能保证 ready 队列中 FD,一定可以正常读取/写入,仍然有可能会为了等待数据而阻塞。具体可参见 select(2) - Linux manual page (man7.org)BUGS部分。
可能会将 socket 文件视为 “已准备好读取”,但程序随后实际读取时仍然被阻塞。例如,数据可能在到达后因错误检查和校验而被丢弃,可能会发生这种情况。也可能还存在其他 FD 被错误地报告为就绪的情况。因此,在不应阻塞的套接字上使用 O_NONBLOCK 可能更安全。

c - Non blocking sockets when using I/O multiplexing - Stack Overflow

Asynchronous IO#

异步 IO 模型, IO 相比于 BIO 和 NIO,是完全异步的。从数据的准备,以及数据在内核及进程缓冲区间传送的行为,都由内核代为执行。
loading...
尽管这一模型看起来很简单但有多种不同 AIO 实现。

posix aio#

作为 GLIBC 的一部分进行实现,提供了一个内部使用线程池的实现(线程池 + BIO / NIO 模拟),效率低下。

linux aio#

最初在 linux 2.5 内核中引入。 最初是用于异步磁盘 I/O,且会直接操作磁盘 I/O(绕过缓冲区),因此不适用于非常规文件,如管道、socket 通信(会成为 BIO)Linux AIO status & todo [LWN.net]
后续加入补丁中增加了对 socket 的文件的支持(通过轮询 ring bufferA new kernel polling interface [LWN.net]

littledan/linux-aio: How to use the Linux AIO feature (github.com)
io_submit: The epoll alternative you've never heard about (cloudflare.com)
How io_uring and eBPF Will Revolutionize Programming in Linux - The New Stack

io_uring#

linux 5.1 内核引入的新异步 IO api。有 SQ(Submission Queue)和 CQ(Completion Queue)两个队列,用户程序可以通过 mmap 将此数据结构映射到用户程序空间。当用户提交 I / O 任务时(io_uring_submit),可以减少切换到内核态到次数。简单来说,在轮询模式下,当用户线程修改 SQ,提交了新 IO 任务,如果 SQ 内核线程是活跃的,那么就可以立即获取到该提交并进行处理,此时可以避免系统调用,如果 SQ 非活跃,则进行相应的系统调用,通知内核新的 I / O 任务到达。

reference#

kernel_new_features/io_uring/文章/浅析开源项目之 io_uring.md at main・0voice/kernel_new_features (github.com)

» asynchronous disk I/O (libtorrent.org)

[译] Linux 异步 I / O 框架 io_uring:基本原理、程序示例与性能压测(2020) (arthurchiao.art)

Learn about different I/O Access Methods and what we chose for ScyllaDB

io stack | 系统技术非业余研究 (yufeng.info)
Linux 下异步 IO (libaio) 的使用以及性能 | 系统技术非业余研究 (yufeng.info)

Blocking I/O, Nonblocking I/O, And Epoll (eklitzke.org)