异步 I/O 的理想:提交请求 → 不等待 → 完成时通知。Linux 有两套实现,都不完美。
一、先看图
flowchart TD
subgraph POSIX_AIO["POSIX AIO(glibc)"]
AIO_READ[aio_read] --> POOL[线程池<br/>clone/futex]
POOL --> SYNC[同步 pread]
end
subgraph LIBAIO["libaio(内核)"]
IO_SUBMIT[io_submit] --> CTX[io_context<br/>内核 AIO 环]
CTX --> BIO_SUB[直接 bio 提交<br/>需 O_DIRECT]
BIO_SUB --> IO_GET[io_getevents<br/>轮询完成]
end
subgraph IOURING["io_uring(替代)"]
SQ[SQ ring] --> CQ[CQ ring<br/>无需 syscall]
end
classDef old fill:#f0883e22,stroke:#f0883e,color:#adbac7;
classDef kern fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef new fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class AIO_READ,POOL,SYNC old
class IO_SUBMIT,CTX,BIO_SUB,IO_GET kern
class SQ,CQ new
二、POSIX AIO(glibc 实现)
2.1 API
struct aiocb cb = {
.aio_fildes = fd,
.aio_buf = buf,
.aio_nbytes = 4096,
.aio_offset = 0,
};
aio_read(&cb);
// ... 做其他工作 ...
aio_suspend(&cb_list, 1, NULL); // 等待完成
ssize_t ret = aio_return(&cb);2.2 真相
glibc 内部用 线程池
模拟异步——clone() 创建工作线程,每线程做同步
pread/pwrite。
问题:
- 线程创建/调度开销
- 无法利用内核异步能力
- 信号通知(
aio_sigevent)不可靠
2.3 结论
不要在新代码中使用 POSIX AIO。它是 POSIX 标准的产物,Linux 实现是假异步。
三、libaio(内核 AIO)
3.1 API
io_context_t ctx = 0;
io_setup(128, &ctx);
struct iocb cb;
io_prep_pread(&cb, fd, buf, 4096, 0);
struct iocb *cbs[1] = {&cb};
io_submit(ctx, 1, cbs);
struct io_event events[1];
io_getevents(ctx, 1, 1, events, NULL);
io_destroy(ctx);3.2 限制
- 必须 O_DIRECT:buffered I/O 会退化为同步
- io_cancel 几乎不工作:大多数驱动不支持取消
- 不能与 epoll
组合:
io_getevents是独立等待点 - 固定大小
ring:
io_setup(nr_events)分配的 ring 不能动态扩展 - 每次 io_submit 一个 syscall
3.3 使用场景
数据库引擎(MySQL InnoDB、RocksDB)在 O_DIRECT 场景下用 libaio。
四、libaio 的 eventfd 集成
io_set_eventfd(&cb, efd); // 完成时 write(efd, 1)可以把 eventfd 加入 epoll → 间接实现 epoll + AIO 组合。但还是需要 io_getevents 收割。
五、为什么 libaio 不支持 buffered I/O
buffered I/O 需要页缓存交互 → 可能触发 page fault → 内核不愿在 AIO 上下文处理阻塞操作 → 退化为同步。
io_uring 解决了这个问题:用独立的 io-wq 工作线程处理可能阻塞的请求。
六、io_cancel 的问题
int io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *result);返回 -EINVAL 的概率极高——NVMe、SCSI
驱动大多不实现 cancel。已提交到设备的 I/O 无法取消。
七、性能对比
| 方案 | 4KB 随机读 IOPS | syscall/IO |
|---|---|---|
| 同步 pread | ~100K | 1 |
| POSIX AIO | ~80K | 1 + 线程开销 |
| libaio O_DIRECT | ~300K | ~0.5(批量) |
| io_uring | ~400K+ | ~0(SQPOLL) |
libaio 在 O_DIRECT 批量场景有优势,但 io_uring 全面超越。
八、迁移建议
POSIX AIO → 直接删除,用 io_uring 或线程池 + pread
libaio → 新项目用 io_uring;老项目评估迁移成本
io_getevents → 可先加 eventfd 桥接,再逐步迁移
九、观察
# 查看进程的 AIO 上下文
cat /proc/<pid>/io # rchar/wchar/syscr/syscw
# libaio ring buffer
cat /proc/<pid>/maps | grep aio
# 系统级
cat /proc/sys/fs/aio-max-nr # 最大 AIO 上下文数
cat /proc/sys/fs/aio-nr # 当前使用十、小结
- POSIX AIO 是线程池伪装 → 不要用
- libaio 是内核异步,但限制多(O_DIRECT、不可取消、不组合 epoll)
- io_uring 是 libaio 的全面替代
- 老系统的 libaio 代码仍在运行,需要理解其语义
参考文献
man 7 aiofs/aio.c(内核 AIO 实现)- Suparna Bhattacharya, “Asynchronous I/O support in Linux 2.5.” OLS 2003
- 存储百科-11-linux-async-io
- Jens Axboe, “Efficient IO with io_uring.” 2019
工具
libaio-dev(io_setup/io_submit 头文件)strace -e io_submit,io_geteventsperf trace -e aio:*
上一篇:VFS I/O 路径 下一篇:io_uring 内核内部
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】io_uring 内核内部
io_uring 用共享内存 ring buffer 实现零 syscall 异步 I/O——SQ/CQ、SQPOLL、IOPOLL、注册 fd/buffer、multishot、安全模型演化。本文深入内核实现与工程实践。
【操作系统百科】异步 I/O 模型 benchmark
epoll、io_uring、libaio、阻塞线程池——四种异步模型的真实性能对比。本文用统一 workload 量化 echo server、静态文件服务、数据库 I/O 场景下的吞吐、延迟与 CPU 开销。
【存储工程】Linux 异步 I/O:从 POSIX AIO 到 io_uring
在高吞吐存储系统中,同步 I/O 是性能的天花板。本文系统梳理 Linux 下三代异步 I/O 方案——POSIX AIO、Linux Native AIO(libaio)和 io_uring——的设计原理、编程接口、性能特征与工程实践,帮助你在实际项目中做出正确的技术选型。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。