I/O 模型#
Blocking IO#
BIO 模型,是完全的同步模型,最符合人类直觉。简单来说就是发起一个 IO 系统调用,然后会导致当前线程出让 CPU 控制权,线程被挂起,直到系统调用完成,在合适的时机再被 OS 唤醒。
对应于系统调用的open
和fcntl
。
loading...
这很简单也很有用,但还可以做得更好。
Non-Blocking IO#
NIO 模型,非阻塞 IO 模型,这是相对 BIO 的一种改进。在 Linux 上,也就是对于open
和fnctl
这两个系统调用增加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 时,可以进行读取。因此,可以它可以与 BIO
或 NIO
结合使用。一般而言,结合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 buffer
)A 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#
» 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)