Chinaunix首页 | 论坛 | 博客
  • 博客访问: 391091
  • 博文数量: 87
  • 博客积分: 2571
  • 博客等级: 少校
  • 技术积分: 920
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-29 13:10
文章分类

全部博文(87)

文章存档

2012年(49)

2011年(7)

2010年(26)

2009年(5)

分类: LINUX

2012-05-04 15:35:37

摘自:http://hi.baidu.com/_kouu/blog/item/e225f67b337841f42f73b341.html

linux下主要有两套异步IO:
一套是由glibc实现的(以下称之为glibc版本);
一套是由linux内核实现,并由libaio来封装调用接口(以下称之为linux版本);


glibc版本

接口
glibc版本主要包含如下接口:
  1. int aio_read(struct aiocb *aiocbp); /* 提交一个异步读 */
  2. int aio_write(struct aiocb *aiocbp); /* 提交一个异步写 */
  3. int aio_cancel(int fildes, struct aiocb *aiocbp); /* 取消一个异步请求(或基于一个fd的所有异步请求,aiocbp==NULL) */
  4. int aio_error(const struct aiocb *aiocbp); /* 查看一个异步请求的状态(进行中EINPROGRESS?还是已经结束或出错?) */
  5. ssize_t aio_return(struct aiocb *aiocbp); /* 查看一个异步请求的返回值(跟同步读写定义的一样) */
  6. int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec *timeout); /* 阻塞等待请求完成 */
其中,struct aiocb主要包含以下字段:
  1. int aio_fildes; /* 要被读写的fd */
  2. void * aio_buf; /* 读写操作对应的内存buffer */
  3. __off64_t aio_offset; /* 读写操作对应的文件偏移 */
  4. size_t aio_nbytes; /* 需要读写的字节长度 */
  5. int aio_reqprio; /* 请求的优先级 */
  6. struct sigevent aio_sigevent; /* 异步事件,定义异步操作完成时的通知信号或回调函数 */
原理:
  1. 1、异步请求被提交到request_queue中;
  2. 2、request_queue实际上是一个表结构,"行"是fd、"列"是具体的请求。也就是说,同一个fd的请求会被组织在一起;
  3. 3、异步请求有优先级概念,属于同一个fd的请求会按优先级排序,并且最终被按优先级顺序处理;
  4. 4、随着异步请求的提交,一些异步处理线程被动态创建。这些线程要做的事情就是从request_queue中取出请求,然后处理之;
  5. 5、为避免异步处理线程之间的竞争,同一个fd所对应的请求只由一个线程来处理;
  6. 6、异步处理线程同步地处理每一个请求,处理完成后在对应的aiocb中填充结果,然后触发可能的信号通知或回调函数(回调函数是需要创建新线程来调用的);
  7. 7、异步处理线程在完成某个fd的所有请求后,进入闲置状态;
  8. 8、异步处理线程在闲置状态时,如果request_queue中有新的fd加入,则重新投入工作,去处理这个新fd的请求(新fd和它上一次处理的fd可以不是同一个);
  9. 9、异步处理线程处于闲置状态一段时间后(没有新的请求),则会自动退出。等到再有新的请求时,再去动态创建;


linux版本

接口
下面再来看看linux版本的异步IO。它主要包含如下系统调用接口:
  1. int io_setup(int maxevents, io_context_t *ctxp); /* 创建一个异步IO上下文(io_context_t是一个句柄) */
  2. int io_destroy(io_context_t ctx); /* 销毁一个异步IO上下文(如果有正在进行的异步IO,取消并等待它们完成) */
  3. long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp); /* 提交异步IO请求 */
  4. long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result); /* 取消一个异步IO请求 */
  5. long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout) /* 等待并获取异步IO请求的事件(也就是异步请求的处理结果) */
其中,struct iocb主要包含以下字段:
  1. __u16 aio_lio_opcode; /* 请求类型(如:IOCB_CMD_PREAD=读、IOCB_CMD_PWRITE=写、等) */
  2. __u32 aio_fildes; /* 要被操作的fd */
  3. __u64 aio_buf; /* 读写操作对应的内存buffer */
  4. __u64 aio_nbytes; /* 需要读写的字节长度 */
  5. __s64 aio_offset; /* 读写操作对应的文件偏移 */
  6. __u64 aio_data; /* 请求可携带的私有数据(在io_getevents时能够从io_event结果中取得) */
  7. __u32 aio_flags; /* 可选IOCB_FLAG_RESFD标记,表示异步请求处理完成时使用eventfd进行通知(百度一下) */
  8. __u32 aio_resfd; /* 有IOCB_FLAG_RESFD标记时,接收通知的eventfd */
其中,struct io_event主要包含以下字段:
  1. __u64 data; /* 对应iocb的aio_data的值 */
  2. __u64 obj; /* 指向对应iocb的指针 */
  3. __s64 res; /* 对应IO请求的结果(>=0: 相当于对应的同步调用的返回值;<0: -errno) */

比较:
- 从上面的流程可以看出,linux版本的异步IO实际上只是利用了CPU和IO设备可以异步工作的特性(IO请求提交的过程主要还是在调用者线程上同步完成的,请求提交后由于CPU与IO设备可以并行工作,所以调用流程可以返回,调用者可以继续做其他事情)。相比同步IO,并不会占用额外的CPU资源。

- 而glibc版本的异步IO则是利用了线程与线程之间可以异步工作的特性,使用了新的线程来完成IO请求,这种做法会额外占用CPU资源(对线程的创建、销毁、调度都存在CPU开销,并且调用者线程和异步处理线程之间还存在线程间通信的开销)。不过,IO请求提交的过程都由异步处理线程来完成了(而linux版本是调用者来完成的请求提交),调用者线程可以更快地响应其他事情。如果CPU资源很富足,这种实现倒也还不错。

- 还有一点,当调用者连续调用异步IO接口,提交多个异步IO请求时。在glibc版本的异步IO中,同一个fd的读写请求由同一个异步处理线程来完成。而异步处理线程又是同步地、一个一个地去处理这些请求。所以,对于底层的IO调度器来说,它一次只能看到一个请求。处理完这个请求,异步处理线程才会提交下一个。而内核实现的异步IO,则是直接将所有请求都提交给了IO调度器,IO调度器能看到所有的请求。请求多了,IO调度器使用的类电梯算法就能发挥更大的功效。请求少了,极端情况下(比如系统中的IO请求都集中在同一个fd上,并且不使用预读),IO调度器总是只能看到一个请求,那么电梯算法将退化成先来先服务算法,可能会极大的增加碰头移动的开销。

- 最后,glibc版本的异步IO支持非direct-io,可以利用内核提供的page cache来提高效率。而linux版本只支持direct-io,cache的工作就只能靠用户程序来实现了。








阅读(1097) | 评论(0) | 转发(0) |
0

上一篇:[lxc]Cgroups概述

下一篇:linux-文件系统-ramfs

给主人留下些什么吧!~~