http://wenku.baidu.com/view/dae7dbfcc8d376eeaeaa31d6.html
Linux® 中最常用的输入/输出(I/O)模型是同步 I/O。在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止。这是很好的一种解决方案,因为调用应用程序在等待 I/O 请求完成时不需要使用任何中央处理单元(CPU)。但是在某些情况中,I/O 请求可能需要与其他进程产生交叠。可移植操作系统接口(POSIX)异步 I/O(AIO)应用程序接口(API)就提供了这种功能。在本文中,我们将对这个 API 概要进行介绍,并来了解一下如何使用它。
AIO 简介
Linux 异步 I/O 是 Linux 内核中提供的一个相当新的增强。它是 2.6 版本内核的一个标准特性,但是我们在 2.4 版本内核的补丁中也可以找到它。AIO 背后的基本思想是允许进程发起很多 I/O 操作,而不用阻塞或等待任何操作完成。稍后或在接收到 I/O 操作完成的通知时,进程就可以检索 I/O 操作的结果。
Linux 上的 AIO 简介
本节将探索 Linux 的异步 I/O 模型,从而帮助我们理解如何在应用程序中使用这种技术。
在传统的 I/O 模型中,有一个使用惟一句柄标识的 I/O 通道。在 UNIX® 中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。在阻塞 I/O 中,我们发起了一次传输操作,当传输操作完成或发生错误时,系统调用就会返回。
|
在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO 中,这是一个 aiocb
(AIO I/O Control Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb
结构就被用来惟一标识所完成的 I/O 操作。这个 API 的展示显示了如何使用它。
AIO API
AIO 接口的 API 非常简单,但是它为数据传输提供了必需的功能,并给出了两个不同的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。
表 1. AIO 接口 API
API 函数 | 说明 |
| 请求异步读操作 |
| 检查异步请求的状态 |
| 获得完成的异步请求的返回状态 |
| 请求异步写操作 |
| 挂起调用进程,直到一个或多个异步请求已经完成(或失败) |
| 取消异步 I/O 请求 |
| 发起一系列 I/O 操作 |
每个 API 函数都使用 aiocb
结构开始或检查。这个结构有很多元素,但是清单 1 仅仅给出了需要(或可以)使用的元素。清单 1. aiocb 结构中相关的域
struct aiocb { int aio_fildes; // File Descriptor int aio_lio_opcode; // Valid only for lio_listio (r/w/nop) volatile void *aio_buf; // Data Buffer size_t aio_nbytes; // Number of Bytes in Data Buffer struct sigevent aio_sigevent; // Notification Structure /* Internal fields */ ... }; |
sigevent
结构告诉 AIO 在 I/O 操作完成时应该执行什么操作。我们将在 AIO 的展示中对这个结构进行探索。现在我们将展示各个 AIO 的 API 函数是如何工作的,以及我们应该如何使用它们。
清单 1. aiocb 结构中相关的域
struct aiocb { int aio_fildes; // File Descriptor int aio_lio_opcode; // Valid only for lio_listio (r/w/nop) volatile void *aio_buf; // Data Buffer size_t aio_nbytes; // Number of Bytes in Data Buffer struct sigevent aio_sigevent; // Notification Structure /* Internal fields */ ... }; |
sigevent
结构告诉 AIO 在 I/O 操作完成时应该执行什么操作。我们将在 AIO 的展示中对这个结构进行探索。现在我们将展示各个 AIO 的 API 函数是如何工作的,以及我们应该如何使用它们。
aio_read
aio_read
函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。aio_read
函数的原型如下:
int aio_read( struct aiocb *aiocbp ); |
aio_read
函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;如果出现错误,返回值就为 -1,并设置 errno
的值。
要执行读操作,应用程序必须对 aiocb
结构进行初始化。下面这个简短的例子就展示了如何填充 aiocb
请求结构,并使用 aio_read
来执行异步读请求(现在暂时忽略通知)操作。它还展示了 aio_error
的用法,不过我们将稍后再作解释。
清单 2. 使用 aio_read 进行异步读操作的例子
#include <aio.h> ... int fd, ret; struct aiocb my_aiocb; fd = open( "file.txt", O_RDONLY ); if (fd < 0) perror("open"); /* Zero out the aiocb structure (recommended) */ bzero( (char *)&my_aiocb, sizeof(struct aiocb) ); /* Allocate a data buffer for the aiocb request */ my_aiocb.aio_buf = malloc(BUFSIZE+1); if (!my_aiocb.aio_buf) perror("malloc"); /* Initialize the necessary fields in the aiocb */ my_aiocb.aio_fildes = fd; my_aiocb.aio_nbytes = BUFSIZE; my_aiocb.aio_offset = 0; ret = aio_read( &my_aiocb ); if (ret < 0) perror("aio_read"); while ( aio_error( &my_aiocb ) == EINPROGRESS ) ; if ((ret = aio_return( &my_iocb )) > 0) { /* got ret bytes on the read */ } else { /* read failed, consult errno */ } |
在清单 2 中,在打开要从中读取数据的文件之后,我们就清空了 aiocb
结构,然后分配一个数据缓冲区。并将对这个数据缓冲区的引用放到 aio_buf
中。然后,我们将 aio_nbytes
初始化成缓冲区的大小。并将 aio_offset
设置成 0(该文件中的第一个偏移量)。我们将 aio_fildes
设置为从中读取数据的文件描述符。在设置这些域之后,就调用 aio_read
请求进行读操作。我们然后可以调用 aio_error
来确定 aio_read
的状态。只要状态是 EINPROGRESS
,就一直忙碌等待,直到状态发生变化为止。现在,请求可能成功,也可能失败。
|
注意使用这个 API 与标准的库函数从文件中读取内容是非常相似的。除了 aio_read
的一些异步特性之外,另外一个区别是读操作偏移量的设置。在传统的 read
调用中,偏移量是在文件描述符上下文中进行维护的。对于每个读操作来说,偏移量都需要进行更新,这样后续的读操作才能对下一块数据进行寻址。对于异步 I/O 操作来说这是不可能的,因为我们可以同时执行很多读请求,因此必须为每个特定的读请求都指定偏移量。
aio_error
aio_error
函数被用来确定请求的状态。其原型如下:
int aio_error( struct aiocb *aiocbp ); |
这个函数可以返回以下内容:
· EINPROGRESS
,说明请求尚未完成
· ECANCELLED
,说明请求被应用程序取消了
·
-1
,说明发生了错误,具体错误原因可以查阅 errno
· 0 说明操作成功
aio_return
异步 I/O 和标准块 I/O 之间的另外一个区别是我们不能立即访问这个函数的返回状态,因为我们并没有阻塞在 read
调用上。在标准的 read
调用中,返回状态是在该函数返回时提供的。但是在异步 I/O 中,我们要使用 aio_return
函数。这个函数的原型如下:
ssize_t aio_return( struct aiocb *aiocbp ); |
只有在 aio_error
调用确定请求已经完成(可能成功,也可能发生了错误)之后,才会调用这个函数。aio_return
的返回值就等价于同步情况中 read
或 write
系统调用的返回值(所传输的字节数,如果发生错误,返回值就为 -1
)。
aio_write
aio_write
函数用来请求一个异步写操作。其函数原型如下:
int aio_write( struct aiocb *aiocbp ); |
aio_write
函数会立即返回,说明请求已经进行排队(成功时返回值为 0
,失败时返回值为 -1
,并相应地设置 errno
)。
这与 read
系统调用类似,但是有一点不一样的行为需要注意。回想一下对于 read
调用来说,要使用的偏移量是非常重要的。然而,对于 write
来说,这个偏移量只有在没有设置 O_APPEND
选项的文件上下文中才会非常重要。如果设置了 O_APPEND
,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。否则,aio_offset
域就确定了数据在要写入的文件中的偏移量。
aio_error(3) - Linux man page
Name
aio_error - get error status of asynchronous I/O operation
Synopsis
#include <aio.h>
int aio_error(const struct aiocb *aiocbp);
Description
The aio_error() function returns the error status for the asynchronous I/O request with control block pointed to by aiocbp.
Return Value
This function returns EINPROGRESS if the request has not been completed yet. It returns ECANCELED if the request was cancelled. It returns 0 if the request completed successfully. Otherwise an error value is returned, the same value that would have been stored in the errno variable in case of a synchronous read, write, fsync, or fdatasync request. On error, the error value is returned.
Errors
EINVAL
aiocbp does not point at a control block for an asynchronous I/O request of which the return status (see aio_return(3)) has not been retrieved yet.