IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

libeio源码分析 – 主流程

淘宝核心系统团队博客 2012-09-18 23:41:26 累计浏览 2,286 次
本机暂存

简介

    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

同分类推荐文章

  1. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  2. Go 实验特性详解 (2026-06-21 10:05:27)
  3. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)

查看更多 后端 文章 →

建议继续学习

  1. Google怎么用linux (累计阅读 12,582)
  2. 如何学好C++语言 (累计阅读 10,449)
  3. Linux操作系统的内存使用方法详细解析 (累计阅读 10,151)
  4. 浅析linux kernel network之socket创建 (累计阅读 6,741)
  5. C 语言的前世今生 (累计阅读 6,195)
  6. osx平台上lol英雄联盟launcher启动器的分析实现 (累计阅读 5,870)
  7. GLIBC内存分配机制引发的“内存泄露” (累计阅读 5,242)
  8. do{...}while(0)的意义和用法 (累计阅读 5,119)
  9. 如何保证一个程序在单台服务器上只有唯一实例(linux) (累计阅读 4,517)
  10. 接口设计规则一:让你的接口会说话 (累计阅读 4,506)