aio
ddatsh
宏观与微观角度看epoll+nonblock
宏观角度可以叫做全异步
微观角度还是同步 IO(数据到达后得到系统通知,然后同步执行 recv 取回数据,没有 iowait)
真正的异步 IO(AIO)应像 Windows IOCP 一样,传入文件句柄,缓存区,尺寸等参数和一个函数指针,当os真正完成了 IO 操作,再执行对应的函数
-
实际上 socket ,epoll 已经是最高效的模型了,虽然比 AIO 多一次 recv 系统调用,但总体来看没有任何 IO 等待,效率很高
-
epoll 天然 reactor 模型,程序实现更容易。AIO 如 windows 的 IOCP,异步回调的方式,开发难度很高
为什么还是需要 AIO
文件句柄跟 socket 完全不同,它总是处于可读状态
,不能 epoll+nonblock 实现异步化
在 epoll 的全异步 Server 中,读写文件必须得用 AIO
AIO 的几种实现方案
gcc AIO
gcc 遵循 posix 标准实现了 AIO
头文件为 <aio.h>
,支持 FreeBSD/Linux
通过阻塞 IO + 用户线程池实现
主要函数 aio_read/aio_write/aio_return
优点 | 缺点 |
---|---|
支持平台多,兼容性好,无需依赖第三方库,阻塞 IO 可以利用到操作系统的 PageCache | 一些 bug 和陷阱,一直未解决 |
Linux Native Aio
os kernel 提供的 AIO
头文件 <linux/aio_abi.h>
真正的 AIO,完全非阻塞异步,而不是用阻塞 IO 和线程池模拟
主要系统调用 io_submit/io_setup/io_getevents
优点 | 缺点 |
---|---|
os提供,读写操作可以直接投递到硬件,不会浪费 CPU | 仅支持 Linux,必须使用 DirectIO,无法利os的 PageCache 对于写文件来说 native aio 作用不大,应为本身写文件就是先写到 PageCache 上,直接返回,没有 IO 等待 |
Libeio
libev 作者开发的 AIO 实现,与 gcc aio 类似也是使用阻塞 IO + 线程池实现的
与 gcc aio 不同之处,代码更简洁 三方库,代码需要依赖 libeio
总结
程序读写文件很大,随机性强,PageCache 命中率低,可以选择 Native AIO,降低 CPU 使用率
读写文件很小,而且是固定的一些文件,PageCache 的命中率高,可以选择 gcc aio 或 libeio
跑 mysql,CPU 的 iowait 很大非常常见
对于以 IO 为最大瓶颈的db,native AIO 几乎不二的选择,仅仅依靠多线程,显然无法解决磁盘和网络的问题
AIO Ring 应用户态进程内存缓存区,内核共享,并报告 IO完成情况
用户态进程也可以直接从 AIO Ring 检查异步 IO 完成情况,避免系统调用开销
AIO 与 epoll
用 AIO 时,需阻塞的系统调用 io_getevents 获取已经完成的 IO 事件,所以有 2 种方式:
- 多线程,用专门的线程调用 io_getevents(MySQL5.5)
- 单线程程序,可以通过 epoll 来使用 AIO(需要系统调用 eventfd ,kernel 2.6.22)
eventfd 是 Linux-native aio 其中的一个 API,用来生成 file descriptors,可为应用程序提供更高效 “等待/通知” 的事件机制
和 pipe 作用相似,但比 pipe 更好,一是只用一个 file descriptor(pipe 要用两个),节省内核资源; 二是eventfd 缓冲区管理要简单得多,pipe 需要不定长的缓冲区,而 eventfd 全部缓冲只有定长 8 bytes
nginx 0.8 加入native aio 支持
linux 下有两种 aio,glibc 实现的 aio比较烂,在用户空间用 pthread 进行模拟。还有内核实现的 aio,nginx 用的后一种,native aio 只支持 direct io
native aio 的优点就是能够同时提交多个 io 请求给内核,然后直接由内核的 io 调度算法去处理这些请求 (direct io),这样的话,内核就有可能执行一些合并,优化
io_setup、io_cancel、io_destroy、io_getevents、 io_submit
libaio简单的封装了上面的几个系统调用,而 nginx直接使用 syscall 来调用系统调用