3.2 Linux AIO
Linux Kernel Asynchronous I/O
于 2003 年进入内核,libaio
是其用户态接口的库封装。
时至今日看,linux aio
存在一系列的批评声音1。笔者很少见到新的项目选择使用 linux aio
了,大部分转而去拥抱 io_uring
。但不可否认的是,AIO 切切实实为 Linux 磁盘异步 IO 曾经的生产实践。
1 异步 I/O 基础
Linux AIO 的基本使用,体现了异步 IO 的基本范式: 提交-执行-收割。
graph LR A[提交队列 Submit] --> B[内核执行] B --> C[完成队列 Completion]
一般开发者不会直接调用 syscall,而是使用为用户封装的库 libaio
。具体来讲,libaio
的关键调用如下:
阶段 | 系统调用 | 功能 |
---|---|---|
上下文初始化 | io_setup() | 创建事件队列(最大深度 128-4096) |
准备异步请求 | io_prep_pread/pwrite() | 准备异步 io 请求结构体 |
I/O 提交 | io_submit() | 批量提交请求(iocb 结构体) |
事件获取 | io_getevents() | 阻塞/非阻塞获取完成事件 |
其余关键控制函数还有 prepare、cancel 等操作,可参考 libaio
文档。
2 AIO 的缺陷
LWN.net 2017 年的一篇博客1精辟地总结了开发者们对 linux aio 的批评,大致包含以下几点。
2.1 某些条件下意料外的阻塞
虽然我们以异步非阻塞的方式提交了 io,但还要验证其在任何文件系统、内核版本都是以异步方式运行的。
Seastar 甚至推出一个专门的工具2,通过测量上下文切换次数判断是否内核进行了阻塞 IO。
这是很多开发者不太能接受的,尤其是用户应用代码在不同的内核、文件系统/块设备、磁盘负载下,表现出不一致的阻塞性。一个纯 async 的进程可能突然在某种条件下 hang 住一段时间,造成性能大幅下降。
2.2 只支持 Direct I/O
Linux AIO 被设计时主要聚焦于避免阻塞的磁盘 I/O,且强制要求使用 O_DIRECT
标志。这其实限制了其范围、设计思路、接口定义。
一方面,从应用开发者:只有部分需要使用 AIO 的,比如数据库、存储引擎应用才会去重度使用3。使用 O_DIRECT
需要自己处理一系列的内存对齐、自己构建上层的 buffer 系统3。
另一方面,从内核开发者:网络读写、甚至任何 syscall 是不是都可以有一套异步接口?Linux AIO 一直也被持续改进,但是被认为还是需要有一个通用的、异步调用 syscall 的机制,而不是扩展已有的 AIO 接口4。
3 ScyllaDB/Seastar 与 AIO
ScyllaDB/Seastar 重度使用了 linux aio。博客有一系列关于磁盘 io 的文章,非常适合阅读。尤其是 io 调度主题。
- Database Internals: Working with I/O - ScyllaDB Blog
- Top Mistakes with ScyllaDB Storage - ScyllaDB Blog
- On-Demand Webinar: Different I/O Access Methods for Linux - What We Chose for ScyllaDB and Why - ScyllaDB Blog
- What We’ve Learned After 6 Years of I/O Scheduling - ScyllaDB Blog
- How io_uring and eBPF Will Revolutionize Programming in Linux - ScyllaDB Blog
- Scylla’s New I/O Scheduler - ScyllaDB Blog
- I/O Access Methods in Scylla - ScyllaDB Blog