在高性能的服务器编程中,IO 模型理所当然的是重中之重,需要谨慎选型。对于网络套接字,我们可以采用epoll 的方式来轮询,尽管epoll也有一些缺陷,但总体来说还是很高效的,尤其来大量套接字的场景下;但对于Regular File 来说,是不能够用采用 poll/epoll 的,即O_NOBLOCK 方式对于传统文件句柄是无效的,也就是说我们的 open ,read, mkdir 之类的Regular File操作必定会导致阻塞。在多线程、多进程模型中,可以选择以同步阻塞的方式来进行IO操作,任务调度由操作系统来保证公平性,但在单进程/线程模型中,以nodejs 为例 ,假如 我们需要在一个用户请求中处理10个文件:

function fun() {fs.readFileSync();fs.readFileSync();…}

这时候进程至少会阻塞10次,而这可能会导致其他的上千个用户请求得不到处理,这当然是不能接受的.

Linux AIO 早就被提上议程,目前比较知名的有 Glibc 的 AIO   与 Kernel Native AIO 
Glibc AIO: http://www.ibm.com/developerworks/linux/library/l-async/  
Kernel Native AIO:  http://lse.sourceforge.net/io/aio.html

我们用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, "caller thread\n", 14);sleep(5);}void aio_completion_handler(sigval_t sigval){write(1, "callback\n", 9);struct aiocb *req;...req = (struct aiocb *)sigval.sival_ptr;printf("data: %s\n" ,req->aio_buf);return;}

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

23908 open("file.txt", O_RDONLY)        = 3 
23908 clone(...) = 23909 
23908 write(1, "caller thread\n", 14)   = 14 
23908 nanosleep({5, 0},   
... 
23909 pread(3, "hello, world\n", 1024, 0) = 13 
23909 clone(..)= 23910 
23909 futex(0x3d3a4082a4, FUTEX_WAIT_PRIVATE, 1, {0, 999942000} 
... 
23910 write(1, "callback\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 (Connection timed out) 
23909 futex(0x3d3a408200, FUTEX_WAKE_PRIVATE, 1) = 0 
23909 _exit(0)                          = ? 
23908 <... nanosleep resumed> {5, 0})   = 0 
23908 exit_group(0)                     = ?

在Glibc AIO 的实现中, 用多线程同步来模拟 异步IO ,以上述代码为例,它牵涉了3个线程, 
主线程(23908)新建 一个线程(23909)来调用 阻塞的pread函数,当pread返回时,又创建了一个线程(23910)来执行我们预设的异步回调函数, 23909 等待23910结束返回,然后23909也结束执行..

实际上,为了避免线程的频繁创建、销毁,当有多个请求时,Glibc AIO 会使用线程池,但以上原理是不会变的,尤其要注意的是:我们的回调函数是在一个单独线程中执行的. 
Glibc AIO 广受非议,存在一些难以忍受的缺陷和bug,饱受诟病,是极不推荐使用的. 
详见: http://davmac.org/davpage/linux/async-io.html

在Linux 2.6.22+ 系统上,还有一种 Kernel AIO 的实现,与Glibc 的多线程模拟不同 ,它是真正的做到内核的异步通知,比如在较新版本的Nginx 服务器上,已经添加了AIO方式 的支持.

http://wiki.nginx.org/HttpCoreModule 
aio 
syntax: aio [on|off|sendfile] 
default: off 
context: http, server, location 
This directive is usable as of Linux kernel 2.6.22. For Linux it is required to use directio,  this automatically disables sendfile support.

location /video { 
aio on; 
directio 512; 
output_buffers 1 128k; 
}

听起来Kernel Native AIO 几乎提供了近乎完美的异步方式,但如果你对它抱有太高期望的话,你会再一次感到失望.

目前的Kernel AIO 仅支持 O_DIRECT 方式来对磁盘读写,这意味着,你无法利用系统的缓存,同时它要求读写的的大小和偏移要以区块的方式对齐,参考nginx 的作者 Igor Sysoev 的评论:  http://forum.nginx.org/read.php?2,113524,113587#msg-113587

nginx supports file AIO only in 0.8.11+, but the file AIO is functional 
on FreeBSD only. On Linux AIO is supported by nginx only on kerenl 
2.6.22+ (although, CentOS 5.5 has backported the required AIO features). 
Anyway, on Linux AIO works only if file offset and size are aligned 
to a disk block size (usually 512 bytes) and this data can not be cached 
in OS VM cache (Linux AIO requires DIRECTIO that bypass OS VM cache). 
I believe a cause of so strange AIO implementaion is that AIO in Linux 
was developed mainly for databases by Oracle and IBM.

同时注意上面的橙色字部分,启用AIO 就会关闭sendfile -这是显而易见的,当你用Nginx作为静态服务器,你要么选择以AIO 读取文件到用户缓冲区,然后发送到套接口,要么直接调用sendfile发送到套接口,sendfile 虽然会导致短暂的阻塞,但开启AIO 却无法充分的利用缓存,也丧失了零拷贝的特征 ;当你用Nginx作为动态服务器,比如 fastcgi + php 时,这时php脚本中文件的读写是由php 的 文件接口来操作的,这时候是多进程+同步阻塞模型,和文件异步模式扯不上关系的.

所以现在Linux 上,没有比较完美的异步文件IO 方案,这时候苦逼程序员的价值就充分体现出来了,libev 的作者 Marc Alexander Lehmann 老大就重新实现了一个AIO library :

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

其实它还是采用线程池+同步模拟出来的,和Glibc 的 AIO 比较像,用作者的话说,这个库相比与Glibc 的实现,开销更小,bug更少(不然重新造个轮子还有毛意义呢?反正我是信了) ,不过这个轮子的代码可读性实在不敢恭维,Marc 老大自己也说了:Currently in BETA! Its code, documentation, integration and portability quality is currently below that of libev, but should soon be ready for use in production environments.

(其实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);++npending;if (!reqq_push (&res_queue, req) && want_poll_cb)want_poll_cb ();X_UNLOCK (reslock);

ETP_EXECUTE 就是实际的阻塞调用,比如read,open,,sendfile之类的,当函数返回时,表明操作完成,此时加锁方式向完成队列添加一项 ,然后调用 want_pool ,这个函数是我们eio_init时候设置的,然后释放锁。

注意:每次完成任务时,都要调用want_poll ,所以这个函数应该是线程安全且尽量短促,实际上我们为了避免陷入多线程的泥淖,我们往往配合eio使用事件轮询机制,比如:我们创建一对管道,我们把“读”端的管道加入 epoll 监控结构中,want_poll 函向“写”端管道写数入一个字节或字长 ,所以当下次epoll_wait 返回时,我们会执行 “读” 端管道的 回调函数,类似如下:

void r_pipe_cb(){...eio_poll();}

在eio_poll 中 有类似以下代码:

for(;;){X_LOCK (reslock);req = reqq_shift (&res_queue);if (req){if (!res_queue.size && done_poll_cb)done_poll_cb ();}X_UNLOCK (reslock);res = ETP_FINISH (req);...if(empty) break;}

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

在eio 线程池中,默认最多4个线程,在高性能的程序中,过多的进程/线程往往也是一个瓶颈, 
寄存器的进出栈还是其次,进程虚存地址切换、各级cache 的miss ,这才是最昂贵的,所以,最理想的情形就是:有几个cpu ,就有同样数目的active  线程/进程,但因为io线程往往会陷入sleep模式,所以,还是需要额外的待切换的线程的,作为经验法则,线程池的数量最好是cpu 的数目 X  2(参见windows 核心编程 IOCP卷).

libeio 虽不完美,但目前还是将就着用用吧 ... 
http://www.360doc.com/content/13/0128/15/7982302_262867240.shtml

linux AIO (异步IO) 那点事儿相关推荐

  1. Linux 原生异步 IO 原理与使用

    目录 什么是异步 IO? Linux 原生 AIO 原理 Linux 原生 AIO 使用 什么是异步 IO? 异步 IO:当应用程序发起一个 IO 操作后,调用者不能立刻得到结果,而是在内核完成 IO ...

  2. Linux下异步IO(libaio)的使用以及性能

    Linux下异步IO是比较新的内核里面才有的,异步io的好处可以参考这里. 但是文章中aio_*系列的调用是glibc提供的,是glibc用线程+阻塞调用来模拟的,性能很差,千万千万不要用. 我们今天 ...

  3. linux支持异步io吗,Linux 异步IO

    io_submit.io_setup和io_getevents示例 [摘要:注:本宣布正在 io_submit.io_setup战io_getevents战LINUX上的AIO体系挪用.那有一个特别很 ...

  4. linux下异步IO的简单例子

    首先,贴一下异步IO中用的的一些结构体,因为平常很少用,整理起来方便查看. aio.h中的struct aiocb struct aiocb{int aio_fildes; /* File desri ...

  5. python aiompq集群_国内首款基于AIO(异步IO)支持集群的高性能开源WebSocket服务器 宝贝鱼 CshBBrain V4.0 发布...

    国内首款基于AIO的开源WebSocket服务器 宝贝鱼 (CshBBrainAIO)正式发布.基于AIO的开源WebSocket服务器 宝贝鱼 依然采用分层的体系结构,协议层和业务层 与 基于NIO ...

  6. Linux异步IO实现方案总结

    一.glibc aio 1.名称 由于是glibc提供的aio函数库,所以称为glibc aio. glibc是GNU发布的libc库,即c运行库. 另外网上还有其他叫法posix aio,都是指gl ...

  7. 操作系统与存储:解析Linux内核全新异步IO引擎io_uring设计与实现

    作者:draculaqian,腾讯后台开发工程师 引言 存储场景中,我们对性能的要求非常高.在存储引擎底层的IO技术选型时,可能会有如下讨论关于IO的讨论. http://davmac.org/dav ...

  8. 详解Linux 五种IO模型

    原文:https://www.jianshu.com/p/486b0965c296 上一篇 同步.异步.阻塞.非阻塞 已经通俗的讲解了,要理解同步.异步.阻塞与非阻塞重要的两个概念点了,没有看过的,建 ...

  9. 高级IO--1 ---(五种典型IO,阻塞IO,非阻塞IO,信号驱动IO,异步IO, IO多路转接)

    高级IO: 五种典型IO: 阻塞IO/非阻塞IO/信号驱动IO/异步IO/IO多路转接 IO多路转接模型:select/poll/epoll 五种典型IO 阻塞IO IO操作的流程:等待IO操作条件具 ...

  10. Linux 五种IO模型

    想快速了解,看文末总结. 1 概念说明# 在进行解释之前,首先要说明几个概念: 用户空间和内核空间 进程切换 进程的阻塞 文件描述符 缓存 IO 1.1 用户空间与内核空间## 现在操作系统都是采用虚 ...

最新文章

  1. 2019计算机科学论文研讨大会,2019年中华口腔医学会口腔医学计算机专业委员会第十七次全国口腔医学数字化学术会议第一轮会议通知...
  2. java 判断数的位数_Java判断数字位数的两种方法
  3. java 变量 动态类型_Java:如何将变量从一种类型动态转换为另一种类型?
  4. 解决QT无法调试问题-----the cdb process terminated
  5. 别魔改网络了,Google研究员:模型精度不高,是因为你的Resize方法不够好
  6. 不等双十一,ChemDraw降价活动已经打开!
  7. 海量数据库解决方案2011030101
  8. Python 元组tuple - Python零基础入门教程
  9. Node.js「二」—— fs 模块 / async 与 await
  10. resultmap的写法_Mybatis的ResultMap的使用
  11. hsv封装好的调试工具类
  12. Element的使用
  13. PHP数据跨行跨列处理
  14. adt-bundle-mac android 模拟器 sd卡增加文件
  15. 5G手机占比逼近四成,华为和小米将加速5G普及
  16. 快递是否签收,分享小技巧查询物流查看已签收件
  17. 《过目不忘的读书方法》读书摘要
  18. 总体样本与样本均值X拔的一个重要公式推导
  19. 【Leetcode】1612. Check If Two Expression Trees are Equivalent
  20. ubuntu20.04 server 无图形命令行安装

热门文章

  1. 小鑫の日常系列故事(十)——排名次_JAVA
  2. 学习android开发中遇到的一些小问题
  3. 7.测试hadoop安装成功与否,并跑mapreduce实例
  4. JQuery 总结(8)Ajax 无刷新技术
  5. git恢复删除文件之ls-files
  6. 深入理解Java对象序列化
  7. 一个IEEE论文LaTeX模板,可能较旧
  8. Python-OpenCV 杂项(一):图像绘制
  9. 线性代数:第五章 相似矩阵及二次型(1)向量的内积 方阵的特征值与特征向量 相似矩阵
  10. 鸟哥的Linux私房菜(服务器)- 第十四章、账号控管: NIS 服务器