Chinaunix首页 | 论坛 | 博客
  • 博客访问: 555302
  • 博文数量: 92
  • 博客积分: 2511
  • 博客等级: 少校
  • 技术积分: 932
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-19 10:10
文章分类
文章存档

2011年(6)

2010年(27)

2009年(37)

2008年(22)

我的朋友

分类: LINUX

2011-04-10 15:02:21

aio_suspend

我们可以使用aio_suspend函数来挂起(或阻塞)调用进程,直到异步请求完成为止,此时会产生一个信号,或者发生其他超时操作。调用者提供了一个aiocb引用列表,其中任何一个完成都会导致aio_suspend返回。aio_suspend的函数原型如下:

int aio_suspend( const struct aiocb *const cblist[],
                  int n, const struct timespec *timeout );

aio_suspend的使用非常简单。我们要提供一个aiocb引用列表。如果任何一个完成了,这个调用就会返回0。否则就会返回-1,说明发生了错误。请参看清单 3。


清单 3. 使用 aio_suspend 函数阻塞异步 I/O 
  
struct aioct *cblist[MAX_LIST]

/* Clear the list. */
bzero( (char *)cblist, sizeof(cblist) );

/* Load one or more references into the list */
cblist[0] = &my_aiocb;

ret = aio_read( &my_aiocb );

ret = aio_suspend( cblist, MAX_LIST, NULL );

注意,aio_suspend的第二个参数是cblist中元素的个数,而不是aiocb引用的个数。cblist中任何NULL元素都会被aio_suspend忽略。

如果为aio_suspend提供了超时,而超时情况的确发生了,那么它就会返回-1errno中会包含EAGAIN

aio_cancel

aio_cancel函数允许我们取消对某个文件描述符执行的一个或所有 I/O 请求。其原型如下:

int aio_cancel( int fd, struct aiocb *aiocbp );

要取消一个请求,我们需要提供文件描述符和aiocb引用。如果这个请求被成功取消了,那么这个函数就会返回AIO_CANCELED。如果请求完成了,这个函数就会返回AIO_NOTCANCELED

要取消对某个给定文件描述符的所有请求,我们需要提供这个文件的描述符,以及一个对aiocbpNULL引用。如果所有的请求都取消了,这个函数就会返回AIO_CANCELED;如果至少有一个请求没有被取消,那么这个函数就会返回AIO_NOT_CANCELED;如果没有一个请求可以被取消,那么这个函数就会返回AIO_ALLDONE。我们然后可以使用aio_error来验证每个 AIO 请求。如果这个请求已经被取消了,那么aio_error就会返回-1,并且errno会被设置为ECANCELED

lio_listio

最后,AIO 提供了一种方法使用lio_listioAPI 函数同时发起多个传输。这个函数非常重要,因为这意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,这非常重要,因此值得我们花点时间探索一下。lio_listioAPI 函数的原型如下:

int lio_listio( int mode, struct aiocb *list[], int nent,
                   struct sigevent *sig );

mode参数可以是LIO_WAITLIO_NOWAITLIO_WAIT会阻塞这个调用,直到所有的 I/O 都完成为止。在操作进行排队之后,LIO_NOWAIT就会返回。list是一个aiocb引用的列表,最大元素的个数是由nent定义的。注意list的元素可以为NULLlio_listio会将其忽略。sigevent引用定义了在所有 I/O 操作都完成时产生信号的方法。

对于lio_listio的请求与传统的readwrite请求在必须指定的操作方面稍有不同,如清单 4 所示。


清单 4. 使用 lio_listio 函数发起一系列请求 
  

struct aiocb aiocb1, aiocb2;
struct aiocb *list[MAX_LIST];

...

/* Prepare the first aiocb */
aiocb1.aio_fildes = fd;
aiocb1.aio_buf = malloc( BUFSIZE+1 );
aiocb1.aio_nbytes = BUFSIZE;
aiocb1.aio_offset = next_offset;
aiocb1.aio_lio_opcode = LIO_READ;

...

bzero( (char *)list, sizeof(list) );
list[0] = &aiocb1;
list[1] = &aiocb2;

ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );

对于读操作来说,aio_lio_opcode域的值为LIO_READ。对于写操作来说,我们要使用LIO_WRITE,不过LIO_NOP对于不执行操作来说也是有效的。


点击查看原始尺寸


回页首


AIO 通知

现在我们已经看过了可用的 AIO 函数,本节将深入介绍对异步通知可以使用的方法。我们将通过信号和函数回调来探索异步函数的通知机制。

使用信号进行异步通知

使用信号进行进程间通信(IPC)是 UNIX 中的一种传统机制,AIO 也可以支持这种机制。在这种范例中,应用程序需要定义信号处理程序,在产生指定的信号时就会调用这个处理程序。应用程序然后配置一个异步请求将在请求完成时产生一个信号。作为信号上下文的一部分,特定的aiocb请求被提供用来记录多个可能会出现的请求。清单 5 展示了这种通知方法。


清单 5. 使用信号作为 AIO 请求的通知 
  
void setup_io( ... )
{
  int fd;
  struct sigaction sig_act;
  struct aiocb my_aiocb;

  ...

  /* Set up the signal handler */
  sigemptyset(&sig_act.sa_mask);
  sig_act.sa_flags = SA_SIGINFO;
  sig_act.sa_sigaction = aio_completion_handler;


  /* Set up the AIO request */
  bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
  my_aiocb.aio_fildes = fd;
  my_aiocb.aio_buf = malloc(BUF_SIZE+1);
  my_aiocb.aio_nbytes = BUF_SIZE;
  my_aiocb.aio_offset = next_offset;

  /* Link the AIO request with the Signal Handler */
  my_aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
  my_aiocb.aio_sigevent.sigev_signo = SIGIO;
  my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;

  /* Map the Signal to the Signal Handler */
  ret = sigaction( SIGIO, &sig_act, NULL );

  ...

  ret = aio_read( &my_aiocb );

}


void aio_completion_handler( int signo, siginfo_t *info, void *context )
{
  struct aiocb *req;


  /* Ensure it's our signal */
  if (info->si_signo == SIGIO) {

    req = (struct aiocb *)info->si_value.sival_ptr;

    /* Did the request complete? */
    if (aio_error( req ) == 0) {

      /* Request completed successfully, get the return status */
      ret = aio_return( req );

    }

  }

  return;
}

在清单 5 中,我们在aio_completion_handler函数中设置信号处理程序来捕获SIGIO信号。然后初始化aio_sigevent结构产生SIGIO信号来进行通知(这是通过sigev_notify中的SIGEV_SIGNAL定义来指定的)。当读操作完成时,信号处理程序就从该信号的si_value结构中提取出aiocb,并检查错误状态和返回状态来确定 I/O 操作是否完成。

对于性能来说,这个处理程序也是通过请求下一次异步传输而继续进行 I/O 操作的理想地方。采用这种方式,在一次数据传输完成时,我们就可以立即开始下一次数据传输操作。

使用回调函数进行异步通知

另外一种通知方式是系统回调函数。这种机制不会为通知而产生一个信号,而是会调用用户空间的一个函数来实现通知功能。我们在sigevent结构中设置了对aiocb的引用,从而可以惟一标识正在完成的特定请求。请参看清单 6。


清单 6. 对 AIO 请求使用线程回调通知 
  
void setup_io( ... )
{
  int fd;
  struct aiocb my_aiocb;

  ...

  /* Set up the AIO request */
  bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
  my_aiocb.aio_fildes = fd;
  my_aiocb.aio_buf = malloc(BUF_SIZE+1);
  my_aiocb.aio_nbytes = BUF_SIZE;
  my_aiocb.aio_offset = next_offset;

  /* Link the AIO request with a thread callback */
  my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
  my_aiocb.aio_sigevent.notify_function = aio_completion_handler;
  my_aiocb.aio_sigevent.notify_attributes = NULL;
  my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;

  ...

  ret = aio_read( &my_aiocb );

}


void aio_completion_handler( sigval_t sigval )
{
  struct aiocb *req;

  req = (struct aiocb *)sigval.sival_ptr;

  /* Did the request complete? */
  if (aio_error( req ) == 0) {

    /* Request completed successfully, get the return status */
    ret = aio_return( req );

  }

  return;
}

在清单 6 中,在创建自己的aiocb请求之后,我们使用SIGEV_THREAD请求了一个线程回调函数来作为通知方法。然后我们将指定特定的通知处理程序,并将要传输的上下文加载到处理程序中(在这种情况中,是个对aiocb请求自己的引用)。在这个处理程序中,我们简单地引用到达的sigval指针并使用 AIO 函数来验证请求已经完成。


点击查看原始尺寸


回页首


对 AIO 进行系统优化

proc 文件系统包含了两个虚拟文件,它们可以用来对异步 I/O 的性能进行优化:

  • /proc/sys/fs/aio-nr 文件提供了系统范围异步 I/O 请求现在的数目。
  • /proc/sys/fs/aio-max-nr 文件是所允许的并发请求的最大个数。最大个数通常是 64KB,这对于大部分应用程序来说都已经足够了。

点击查看原始尺寸


回页首


结束语

使用异步 I/O 可以帮助我们构建 I/O 速度更快、效率更高的应用程序。如果我们的应用程序可以对处理和 I/O 操作重叠进行,那么 AIO 就可以帮助我们构建可以更高效地使用可用 CPU 资源的应用程序。尽管这种 I/O 模型与在大部分 Linux 应用程序中使用的传统阻塞模式都不同,但是异步通知模型在概念上来说却非常简单,可以简化我们的设计。



参考资料

学习
  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。

  •  从 GNU Library 的角度介绍了 AIO 的详细内幕。 

  •  解释了更多有关 AIO 和很多实时扩展的信息,内容从调度、POSIX I/O 到 POSIX 线程和高分辨率的定时器(HRT)。 

  • 在为 2.5 版本内核集成而编写的  中,我们可以学习有关 Linux 中 AIO 的设计和实现的知识。 

  • 在 developerWorks Linux 专区 中可以找到为 Linux 开发人员准备的更多资源。 

  • 随时关注 developerWorks 技术事件和网络广播

获得产品和技术
  • 订购免费的 SEK for Linux,这有两张 DVD,包括最新的 IBM for Linux 的试用软件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。 

  • 在您的下一个开发项目中采用 IBM 试用软件,这可以从 developerWorks 上直接下载。 


讨论


关于作者

点击查看原始尺寸

M. Tim Jones

Tim Jones 是一名嵌入式软件工程师,他是 GNU/Linux Application ProgrammingAI Application Programming 以及 BSD Sockets Programming from a Multilanguage Perspective 等书的作者。他的工程背景非常广泛,从同步宇宙飞船的内核开发到嵌入式架构设计,再到网络协议的开发。Tim 是 Emulex Corp. 的一名资深软件工程师。


阅读(2639) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~