Linux aio,linux不支持aio

  Linux aio,linux不支持aio

  linux AIO(异步IO)-CNode

  linux AIO(异步IO)的事情

  在高性能服务器编程中,IO模型是重中之重,需要慎重选择。对于网络套接字,我们可以使用epoll进行轮询。epoll虽然有一些缺陷,但总体来说还是很高效的,尤其是在大量套接字的情况下;但是对于常规文件,我们不能使用poll/epoll,也就是O_NOBLOCK方法对于传统的文件句柄是无效的,也就是说我们的open、read、mkdir等常规文件操作肯定会导致阻塞。在多线程多进程模型中,我们可以选择通过同步阻塞来执行IO操作。任务调度的公平性由操作系统保证,但在单进程/线程模型下,以nodejs为例,假设我们需要处理一个用户请求中的10个文件:fun() {

  fs . read file sync();

  fs . read file sync();

  }

  此时,进程将被阻塞至少10次,这可能导致成千上万的其他用户请求得不到处理,这当然是不可接受的。

  Linux AIO已经提上日程很久了。目前,著名的Glibc的AIO和内核土著AIO。

  http://www.ibm.com/developerworks/linux/library/l-async/AIO环球银行

  内核原生地:http://lse.sourceforge.net/io/aio.html AIO

  让我们用Glibc的AIO做一个小实验,写一个简单的程序:异步读取一个文件,注册异步回调函数:

  int main()

  struct aiocb my _ aiocb

  fd=open(file.txt ,O _ RDONLY);

  my _ aiocb . AIO _ sigevent . sigev _ notify _ function=AIO _ completion _ handler;

  ret=AIO _ read(my _ aiocb);

  write(1,调用者线程\n ,14);

  睡眠(5);

  }

  我们使用strace跟踪调用,得到如下结果(只保留主语句):

  23908 open(file.txt ,O_RDONLY)=3

  23908克隆(.)=23909

  23908 write(1,调用者线程\n ,14)=14

  23908毫微秒睡眠({5,0},

  .

  23909 pread(3,你好,世界\n ,1024,0)=13

  23909克隆(.)=23910

  23909 futex(0x3d3a4082a4,FUTEX_WAIT_PRIVATE,1,{0,999942000}

  .

  23910 write(1,回调\n ,9)=9

  23910 write(1, data: hello,world\n ,19)=19

  23910 write(1, \n ,1)=1

  23910 _exit(0)=?

  23909 .futex resumed )=-1 ETIMEDOUT(连接超时)

  23909 futex(0x3d3a408200,FUTEX_WAKE_PRIVATE,1)=0

  23909 _exit(0)=?

  23908 .nanosleep resumed {5,0})=0

  23908 exit_group(0)=?

  在Glibc AIO的实现中,采用多线程同步来模拟异步IO。以上面的代码为例,它涉及三个线程。

  主线程(23908)创建一个新线程(23909)来调用被阻塞的pread函数。当pread返回时,会创建另一个线程(23910)来执行我们预设的异步回调函数。23909等待23910结束返回,然后23909也结束执行。

  实际上,为了避免频繁地创建和销毁线程,Glibc AIO会在有多个请求时使用线程池,但上述原则不会改变。特别要注意的是,我们的回调函数是在单线程中执行的。

  Gliaio广受诟病,存在一些难以忍受的缺陷和bug。备受诟病,不推荐。

  详情见http://davmac.org/davpage/linux/async-io.html。

  在Linux 2.6.22系统上,还有一个内核AIO的实现,不同于Glibc的多线程模拟。真正实现了内核的异步通知。例如,新版本的Nginx服务器增加了AIO模式。

  http://wiki.nginx.org/HttpCoreModule

  免疫球蛋白源的淀粉样蛋白

  语法:aio [onoffsendfile]

  默认:关闭

  上下文:http,服务器,位置

  该指令从Linux内核2.6.22开始可用。对于Linux,需要使用directio,这将自动禁用发送文件支持。

  位置/视频{

  aio开启;

  directio 512

  output _ buffers 1 128k

  }

  听起来,内核原生AIO提供了近乎完美的异步模式,但如果你对它期望过高,你将再次失望。

  目前内核AIO只支持O_DIRECT模式读写磁盘,也就是说你不能使用系统的缓存,而且要求读写的大小和偏移量要分块对齐。请参考《nginx:http://forum.nginx.org/read.php? 2,113524,113587#msg-113587》作者伊戈尔塞索耶夫的评论

  nginx仅在0.8.11中支持文件AIO,但是文件AIO是可用的

  仅在FreeBSD上。在Linux上,只有kerenl上的nginx支持AIO

  2.6.22(虽然CentOS 5.5已经反向移植了所需的AIO功能)。

  无论如何,在Linux上,AIO只有在文件偏移量和大小对齐的情况下才能工作

  到磁盘块大小(通常为512字节),并且该数据不能被缓存

  在操作系统虚拟机缓存中(Linux AIO需要绕过操作系统虚拟机缓存的指令)。

  我认为AIO实现如此奇怪的原因是Linux中的AIO

  主要是由Oracle和IBM为数据库开发的。

  同时注意上面橙色的字。启用AIO将关闭发送文件-这是显而易见的。当你使用Nginx作为静态服务器时,你要么选择用AIO将文件读入用户缓冲区然后发送给windows sockets,要么直接调用sendfile发送给windows sockets。虽然sendfile会造成短期阻塞,但是打开AIO并不能充分利用缓存,也失去了零拷贝的特性。当你使用Nginx作为动态服务器时,比如fastcgi php,php脚本中文件的读写是由php的文件接口操作的,这是一种多进程同步阻塞模式,与文件异步模式无关。

  所以现在,在Linux上,没有完美的异步文件IO方案。这个时候,努力的程序员的价值就充分体现出来了。老板Marc Alexander Lehmann,libev的作者,重新实现了一个AIO图书馆:

  http://software.schmorp.de/pkg/libeio.html

  实际上是通过线程池同步来模拟的,类似于Glibc的AIO。用作者的话来说,这个库和Glibc的实现相比,开销更少,bug更少(不然的话,再造一个轮子有什么意义?反正我是信的),但是这个轮子的代码可读性真的不敢恭维。老板Marc自己说:目前在测试中!它的代码、文档、集成和可移植性质量目前低于libev,但应该很快就可以在生产环境中使用。

  (其实libev代码和文档的可读性并不是那么好。好像驱动内核就这么多?)好了,腹诽完了,我们来看看它的源代码,稍微分析一下它的原理:

  (本文流程图挺靠谱的:http://cnodejs.org/blog/? P=244,这里有更详细的补充)

  int eio _ init(void(* want _ poll)(void),void (*done_poll)(void))

  初始化时,设置两个回调函数,它们有两个全局数据结构:req存储请求队列,res存储完成队列。当我,当你提交一个异步请求(eio_submit)的时候,实际上是把它放到req队列里,然后给线程池里等待信号的线程发一个信号量(如果线程池里没有线程就创建一个)。获得信号的线程将执行以下代码:

  ETP_EXECUTE (self,req);

  x _ LOCK(reslock);

  nppending;

  如果(!reqq_push ( res_queue,req) want_poll_cb

  want _ poll _ CB();

  x _ UNLOCK(reslock);

  ETP _执行是实际的阻塞调用,比如read,open,sendfile等等。当函数返回时,表示操作完成。此时,锁定方法向完成队列添加一个项目,然后调用want_pool。这个函数是在我们eio_init时设置的,然后释放锁。

  注意:每次完成一个任务都会调用want_poll,所以这个函数应该是线程安全的,尽量简短。事实上,为了避免陷入多线程,我们经常使用eio的事件轮询机制。例如,我们创建一对管道,并在epoll监控结构的“读取”端添加管道。want_poll函数在“写入”端向管道写入一个字节或一个字的长度。

  void r_pipe_cb(){

  eio _ poll();

  }

  eio_poll中有一个类似于下面的代码:

  for(;){

  x _ LOCK(reslock);

  req=reqq _ shift(RES _ queue);

  如果(请求){

  如果(!res_queue.size done_poll_cb)

  done _ poll _ CB();

  x _ UNLOCK(reslock);

  RES=ETP _完成(req);

  如果(空)断;

  }

  eio_poll函数是从完成队列res转移到依次执行我们的回调函数(ETP_FINISH是执行用户回调)。在取出完成队列的最后一项但还没有执行用户回调之前,调用我们设置的done_poll,res队列的操作当然是锁定的。注意,我们的自定义异步回调函数此时是在我们的主线程中执行的!这是我们的终极目标!

  在eio线程池中,默认最多4个线程。在高性能程序中,过多的进程/线程通常是一个瓶颈。

  寄存器进出栈,后面是进程虚拟内存地址切换和各级缓存未命中,这是开销最大的。因此,理想的情况是,当有几个CPU时,有相同数量的活动线程/进程。但是,由于io线程往往会进入睡眠模式,因此仍然需要切换额外的线程。根据经验,线程池的数量最好是CPU的数量X ^ 2(参见windows核心编程的IOCP卷)。

  Libeio并不完美,但暂时会用到。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: