一、glibc aio
1、名称
由于是glibc提供的aio函数库,所以称为glibc aio。
glibc是GNU发布的libc库,即c运行库。
另外网上还有其他叫法posix aio,都是指glibc提供的这套aio实现方案。
2、主要接口
glibc aio主要包含如下接口:
函数 | 功能 |
---|---|
int aio_read(struct aiocb *aiocbp); | 提交一个异步读 |
int aio_write(struct aiocb *aiocbp); | 提交一个异步写 |
int aio_cancel(int fildes, struct aiocb *aiocbp); | 取消一个异步请求(或基于一个fd的所有异步请求,aiocbp==NULL) |
int aio_error(const struct aiocb *aiocbp); | 查看一个异步请求的状态(进行中EINPROGRESS?还是已经结束或出错?) |
ssize_t aio_return(struct aiocb *aiocbp); | 查看一个异步请求的返回值(跟同步读写定义的一样) |
int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec *timeout); | 阻塞等待请求完成 |
glibc aio提供函数API,是比较通俗易懂的。
3、实现原理
在glibc aio的实现原理是,用多线程同步来模拟异步IO。实际上,为了避免线程的频繁创建、销毁,当有多个请求时,glibc aio会使用线程池,但以上原理是不会变的,尤其要注意的是:我们的回调函数是在一个单独线程中执行的。
缺点: glibc aio 广受非议,存在一些难以忍受的缺陷和bug,饱受诟病,是极不推荐使用的。详见:http://davmac.org/davpage/linux/async-io.html
二、libaio
1、名称
libaio是由linux内核提供的aio实现方案,类似于windows api。
由于是linux kernel提供的api,故也叫linux kernel aio,或者原生aio,
native aio。
由于linux下aio实现方式较多,网上叫法很乱,所以这里特意总结下,方便大家区分。
2、主要接口
它主要包含如下系统调用接口:
函数 | 功能 |
---|---|
int io_setup(int maxevents, io_context_t *ctxp); | 创建一个异步IO上下文(io_context_t是一个句柄) |
int io_destroy(io_context_t ctx); | 销毁一个异步IO上下文(如果有正在进行的异步IO,取消并等待它们完成) |
long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp); | 提交异步IO请求 |
long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result); | 取消一个异步IO请求 |
long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout) | 等待并获取异步IO请求的事件(也就是异步请求的处理结果) |
其中,struct iocb主要包含以下字段:
struct iocb {
void *data; /* Return in the io completion event */
unsigned key; /*r use in identifying io requests */
short aio_lio_opcode;
short aio_reqprio;
int aio_fildes;
union {
struct io_iocb_common c;
struct io_iocb_vector v;
struct io_iocb_poll poll;
struct io_iocb_sockaddr saddr;
} u;
};
struct io_iocb_common {
void *buf;
unsigned long nbytes;
long long offset;
unsigned flags;
unsigned resfd;
};
iocb是提交IO任务时用到的,可以完整地描述一个IO请求:
- data是留给用来自定义的指针:可以设置为IO完成后的callback函数;
- aio_lio_opcode表示操作的类型:IO_CMD_PWRITE | IO_CMD_PREAD;
- aio_fildes是要操作的文件:fd;
- io_iocb_common中的buf, nbytes, offset分别记录的IO请求的mem buffer,大小和偏移。
struct io_event {
void *data;
struct iocb *obj;
unsigned long res;
unsigned long res2;
};
io_event是用来描述返回结果的:
- obj就是之前提交IO任务时的iocb;
- res和res2来表示IO任务完成的状态。
3、实现原理
libaio与Glibc的多线程模拟不同 ,它是真正做到内核的异步通知,是真正意义上的异步IO。
听起来Kernel Native AIO几乎提供了近乎完美的异步方式,但如果你对它抱有太高期望的话,你会再一次感到失望。
使用限制: 目前libaio仅支持O_DIRECT标志,即仅支持Direct I/O。
Direct I/O可以简单理解为直接读写IO,读写期间无缓存。
Linux中直接I/O机制介绍:
https://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html
缺点: 目前libaio仅支持Direct I/O方式来对磁盘读写,这意味着,你无法利用系统的缓存,同时它要求读写的的大小和偏移要以区块的方式对齐。
三、libeio
在当年,linux下已有的AIO (异步IO)解决方案:
- Glibc的AIO,在用户态,多线程同步来模拟的异步IO;
- libaio,需要linux内核2.6.22以上,且仅支持Direct I/O。
但两者都存在让使用者望而却步的问题:
- Glibc的AIO bug太多,而且IO发起者并不是最后的IO终结者(callbak是在单独的线程执行的);
- libaio只支持O_DIRECT方式,无法利用Page cache。
正是由于上述原因,Marc Alexander Lehmann大佬决定自己开发一个AIO库,即libeio。
libeio也是在用户态用多线程同步来模拟异步IO,但实现更高效,代码也更可靠,目前虽然是beta版,但已经可以上生产了(node.js底层就是用libev和libeio来驱动的)。
还要强调点:libeio里IO的终结者正是当初IO的发起者(这一点非常重要,因为IO都是由用户的request而发起,而IO完成后返回给用户的response也能在处理request的线程中完成)。
libeio提供全套异步文件操作的接口,让使用者能写出完全非阻塞的程序。
缺点: 严格来讲,libeio也不属于真正的异步IO,仍然是通过用户态多线程来模拟的,性能上与真正的异步IO有差距。
github地址:https://github.com/kindy/libeio
代码量不大,几千行,感兴趣可以研究下。
四、io_uring
在过去的数年间,针对上述缺陷,限制的很多改进努力都未果,如Glibc AIO、libaio、libeio。
虽然在使用和性能上提升了很多,但是,在Linux 上,依然没有比较完美的异步文件IO方案。
直到,Linux 5.1合入了一个新的异步IO框架和实现:io_uring,由block IO大神Jens Axboe开发。
这对当前异步IO领域无疑是一个喜大普奔的消息,这意味着,libaio的时代即将成为过去,io_uring的时代即将开启。
为了方便使用,Jens Axboe还开发了一套liburing库,同时在fio中提供了ioengine=io_uring的支持。通过liburing库,应用不必了解诸多io_uring的细节就可以简单地使用起来。例如,无需担心memory barrier,或者是ring buffer管理之类等。
一句话总结 io_uring 就是:一套全新的 syscall,一套全新的 async API,更高的性能,更好的兼容性,来迎接高 IOPS,高吞吐量的未来。
这个特性,才出来,需要5.1以上内核才能支持,具体好不好用,后续才知道,现在似乎搜索到的内容较少。
io_uring使用参考:
《原生的 Linux 异步文件操作,io_uring 尝鲜体验》
《Linux 5.1内核AIO 的新归宿:io_uring》
五、总结
linux异步IO实际上是利用了CPU和IO设备可以异步工作的特性(IO请求提交的过程主要还是在调用者线程上同步完成的,请求提交后由于CPU与IO设备可以并行工作,所以调用流程可以返回,调用者可以继续做其他事情)。
linux的异步IO发展之路,还是比较曲折的,没有一个完美的实现。不像windows下异步IO,IOCP就是标杆,各种吊打。
在目前情况下,libeio就是比较不错的方案了。
但是如果你的程序中只用到Direct I/O,那么推荐使用libaio。
在将来,随着Linux 5.1以上版本的更新,如果io_uring给力的话,linux异步IO很可能就一统天下了。
参考链接:
《异步I/O – posix aio 从入门到放弃的吐血实践》
若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!
同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。