Linux异步IO实现方案总结

一、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很可能就一统天下了。

参考链接:

《linux AIO (异步IO) 那点事儿》

《异步I/O – posix aio 从入门到放弃的吐血实践》

《linux异步IO编程实例分析》

《linux异步IO的两种方式》


————————————————
版权声明:本文为CSDN博主「百里杨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zyhse/article/details/109188990

猜你喜欢

转载自blog.csdn.net/smilejiasmile/article/details/122469921