libeio源码分析 – 主流程


@淘宝千石

简介

This library provides fully asynchronous versions of most POSIX functions dealing with I/O. Unlike most asynchronous libraries, this not only includes read and write, but also open, stat, unlink and similar functions, as well as less rarely ones such as mknod, futime or readlink. It also offers wrappers around sendfile (Solaris, Linux, HP-UX and FreeBSD, with emulation on other platforms) and readahead (Linux, with emulation elsewhere>). The goal is to enable you to write fully non-blocking programs.

缘起

相信上面这段话已经将libeio的feature讲的足够清楚:提供全套异步文件操作的接口,让使用者能写出完全非阻塞的程序。阻塞意味着低效,但非阻塞一定要有很好的通知机制才能做到高效。

其实linux下的AIO(异步IO)并不是没有解决方案:在用户态,多线程同步来模拟的异步IO,如Glibc 的AIO;以及在内核态实现异步通知,如linux内核2.6.22之后实现的Kernel Native AIO。但两者都存在让使用者望而祛步的问题。

Glibc的AIO bug太多,而且IO发起者并不是最后的IO终结者(callback是在单独的线程执行的);而kernel Native AIO只支持O_DIRECT方式,无法利用Page cache。

正是由于上述原因,Marc Alexander Lehmann大佬决定自己开发一个AIO库,及libeio。libeio也是在用户态用多线程同步来模拟异步IO,但实现更高效,代码也更可靠,目前虽然是beta版,但已经可以上生产了(node.js底层就是用libev和libeio来驱动的)。还要强调一点:libeio里IO的终结者正是当初IO的发起者(这一点非常重要,因为IO都是由用户的request而发起,而IO完成后返回给用户的response也能在处理request的线程中完成)。

实现

一次异步IO操作可以分为三个阶段:

初始化         -->  提交IO        -->  通知worker线程      (主线程);

取request     -->  执行IO        -->  通知主线程               (worker线程);

取response  -->  callback        -->  结束IO                      (主线程)。

下面我们通过一张流程图来剖析一下libeio的源码实现。

1. 主线程调用eio_init函数,主要是初始化req_queue,res_queue以及对应的mutex和cond;

2-3. 所有的IO操作其实都是对eio_sumbit的调用,而eio_sumbit的职能是将IO操作封装为request并插入到req_queue;并调用cond_signal向worker线程发出reqwait已经OK的信号;

libeio处理流程图

4. worker线程被创建后执行的函数为etp_proc,etp_proc启动后会一直等待reqwait条件的出现;

5-6. 当reqwait条件变量满足时,etp_proc从req_queue中取得一个待处理的request;并调用eio_execute来同步执行该IO操作;

7-8. eio_execute完成后,将response插入到res_queue队列中;同时调用want_poll来通知主线程request已经处理完毕;

9. 这里worker线程通知主线程的机制是通过向pipe[1]写一个byte数据;

10. 当主线程发现pipe[0]可读时,就调用eio_poll;

11. eio_poll从res_queue里取response,并调用该IO操作在init时设置的callback函数完成后续处理;

12. 在res_queue中没有待处理response时,调用done_poll;

13-14. done_poll从pipe[0]读出一个byte数据,该IO操作完成。

后记

libeio的实现就是这么简洁,这里需要说明两点:

1. 在worker线程完成IO请求,通知主线程的机制是需要使用者自定义的,wait_poll和done_poll就是libeio提供给使用者的接口(pipe是一种常用的线程通知机制)。

2. worker线程并不是为每个请求都创建一个,而是维护了一个worker线程池,关于这部分将会在下面的文章中单独讲到。

参考

《libeio接口文档》

http://pod.tst.eu/http://cvs.schmorp.de/libeio/eio.pod

《linux异步IO浅析》

http://hi.baidu.com/_kouu/blog/item/e225f67b337841f42f73b341.html

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

http://club.cnodejs.org/topic/4f16442ccae1f4aa270010a7

猜你喜欢

转载自nil-zhang.iteye.com/blog/1506491