Linux® 中最常用的输入/输出(I/O)模型是同步 I/O。在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止。这是很好的一种解决方案,因为调用应用程序在等待 I/O 请求完成时不需要使用任何中央处理单元(CPU)。但是在某些情况中,I/O 请求可能需要与其他进程产生交叠。可移植操作系统接口(POSIX)异步 I/OAIO)应用程序接口(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 中,我们发起了一次传输操作,当传输操作完成或发生错误时,系统调用就会返回。
Linux 上的write的返回值 AIO
AIO 2.5 版本的内核中首次出现,现在已经是 2.6 版本的产品内核的一个标准特性了。
    在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO 中,这是一个 aiocbAIO I/O Control Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O 操作。这个 API 的展示显示了如何使用它。
AIO API
  AIO 接口的 API 非常简单,但是它为数据传输提供了必需的功能,并给出了两个不同的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。

1. AIO 接口 API
API 函数
说明
aio_read
请求异步读操作
aio_error
检查异步请求的状态
aio_return
获得完成的异步请求的返回状态
aio_write
请求异步写操作
aio_suspend
挂起调用进程,直到一个或多个异步请求已经完成(或失败)
aio_cancel
取消异步 I/O 请求
lio_listio
发起一系列 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( "", 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,就一直忙碌等待,直到状态发生变化为止。现在,请求可能成功,也可能失败。
使用 AIO 接口来编译程序
我们可以在 aio.h 头文件中到函数原型和其他需要的符号。在编译使用这种接口的程序时,我们必须使用 POSIX 实时扩展库(librt)。
    注意使用这个 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

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。