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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。