libeio源码分析 – 主流程

fmms 13年前
     <p>        <strong>简介</strong></p>    <blockquote>     <p>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.</p>    </blockquote>    <p>        <strong>缘起</strong></p>    <p>        相信上面这段话已经将 libeio 的 feature 讲的足够清楚:提供全套异步文件操作的接口,让使用者能写出完全非阻塞的程序。阻塞意味着低效,但非阻塞一定要有很好的通知机制才能做到高效。</p>    <p>        其实 linux 下的 AIO(异步 IO)并不是没有解决方案:在用户态,多线程同步来模拟的异步 IO,如 Glibc 的 AIO;以及在内核态实现异步通知,如 linux 内核2.6.22之后实现的 Kernel Native AIO。但两者都存在让使用者望而祛步的问题。</p>    <p>        Glibc 的 AIO bug 太多,而且 IO 发起者并不是最后的 IO 终结者(callback 是在单独的线程执行的);而 kernel Native AIO 只支持O_DIRECT 方式,无法利用 Page cache。</p>    <p>        正是由于上述原因,Marc Alexander Lehmann 大佬决定自己开发一个 AIO 库,及 libeio。libeio 也是在用户态用多线程同步来模拟异步 IO,但实现更高效,代码也更可靠,目前虽然是 beta 版,但已经可以上生产了(node.js 底层就是用 libev 和 libeio 来驱动的)。还要强调一点:libeio 里 IO 的终结者正是当初 IO 的发起者(这一点非常重要,因为 IO 都是由用户的 request 而发起,而 IO 完成后返回给用户的 response 也能在处理 request 的线程中完成)。</p>    <p>        <strong>实现</strong></p>    <p>        一次异步 IO 操作可以分为三个阶段:</p>    <blockquote>     <p>初始化         –>  提交 IO        –>  通知 worker 线程      (主线程);</p>     <p>取 request     –>  执行 IO       –>  通知主线程            (worker 线程);</p>     <p>取 response  –>  callback      –>  结束 IO                 (主线程)。</p>    </blockquote>    <p>        下面我们通过一张流程图来剖析一下 libeio 的源码实现。</p>    <p>        1. 主线程调用 eio_init 函数,主要是初始化 req_queue,res_queue 以及对应的 mutex 和 cond;</p>    <p>        2-3. 所有的 IO 操作其实都是对 eio_sumbit 的调用,而 eio_sumbit 的职能是将 IO 操作封装为 request 并插入到 req_queue;并调用 cond_signal 向 worker 线程发出 reqwait 已经 OK 的信号;</p>    <p style="text-align:center;"><a><img style="width:562px;height:429px;" alt="libeio源码分析 – 主流程" src="https://simg.open-open.com/show/62ae2d6790b1384e30643ef86452714a.jpg" width="726" height="555" /></a></p>    <p align="center">libeio 处理流程图</p>    <p>        4. worker 线程被创建后执行的函数为 etp_proc,etp_proc 启动后会一直等待 reqwait 条件的出现;</p>    <p>        5-6. 当 reqwait 条件变量满足时,etp_proc 从 req_queue 中取得一个待处理的 request;并调用 eio_execute 来同步执行该 IO 操作;</p>    <p>        7-8. eio_execute 完成后,将 response 插入到 res_queue 队列中;同时调用 want_poll 来通知主线程 request 已经处理完毕;</p>    <p>        9. 这里 worker 线程通知主线程的机制是通过向 pipe[1]写一个 byte 数据;</p>    <p>        10. 当主线程发现 pipe[0]可读时,就调用 eio_poll;</p>    <p>        11. eio_poll 从 res_queue 里取 response,并调用该 IO 操作在 init 时设置的 callback 函数完成后续处理;</p>    <p>        12. 在 res_queue 中没有待处理 response 时,调用 done_poll;</p>    <p>        13-14. done_poll 从 pipe[0]读出一个 byte 数据,该 IO 操作完成。</p>    <p>        <strong>后记</strong></p>    <p>        libeio 的实现就是这么简洁,这里需要说明两点:</p>    <p>        1. 在 worker 线程完成 IO 请求,通知主线程的机制是需要使用者自定义的,wait_poll 和 done_poll 就是 libeio 提供给使用者的接口(pipe 是一种常用的线程通知机制)。</p>    <p>        2. worker 线程并不是为每个请求都创建一个,而是维护了一个 worker 线程池,关于这部分将会在下面的文章中单独讲到。</p>    <p>        <strong>参考</strong></p>    <p>        《libeio 接口文档》</p>    <p>        <a href="/misc/goto?guid=4958338583386478177">http://pod.tst.eu/http://cvs.schmorp.de/libeio/eio.pod</a></p>    <p>        《linux 异步 IO 浅析》</p>    <p>        <a href="/misc/goto?guid=4958338584200646322">http://hi.baidu.com/_kouu/blog/item/e225f67b337841f42f73b341.html</a></p>    <p>        《linux AIO (异步 IO) 那点事儿》</p>    <p>        <a href="/misc/goto?guid=4958338584995311418">http://club.cnodejs.org/topic/4f16442ccae1f4aa270010a7</a></p>    <div id="come_from">     来自:     <a id="link_source2" href="/misc/goto?guid=4958338585794651093" target="_blank">rdc.taobao.com</a>    </div>