全部博文(92)
分类: 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。
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
提供了超时,而超时情况的确发生了,那么它就会返回-1
,errno
中会包含EAGAIN
。
aio_cancel
aio_cancel
函数允许我们取消对某个文件描述符执行的一个或所有 I/O 请求。其原型如下:
int aio_cancel( int fd, struct aiocb *aiocbp );
|
要取消一个请求,我们需要提供文件描述符和aiocb
引用。如果这个请求被成功取消了,那么这个函数就会返回AIO_CANCELED
。如果请求完成了,这个函数就会返回AIO_NOTCANCELED
。
要取消对某个给定文件描述符的所有请求,我们需要提供这个文件的描述符,以及一个对aiocbp
的NULL
引用。如果所有的请求都取消了,这个函数就会返回AIO_CANCELED
;如果至少有一个请求没有被取消,那么这个函数就会返回AIO_NOT_CANCELED
;如果没有一个请求可以被取消,那么这个函数就会返回AIO_ALLDONE
。我们然后可以使用aio_error
来验证每个 AIO 请求。如果这个请求已经被取消了,那么aio_error
就会返回-1
,并且errno
会被设置为ECANCELED
。
lio_listio
最后,AIO 提供了一种方法使用lio_listio
API 函数同时发起多个传输。这个函数非常重要,因为这意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,这非常重要,因此值得我们花点时间探索一下。lio_listio
API 函数的原型如下:
int lio_listio( int mode, struct aiocb *list[], int nent,
struct sigevent *sig );
|
mode
参数可以是LIO_WAIT
或LIO_NOWAIT
。LIO_WAIT
会阻塞这个调用,直到所有的 I/O 都完成为止。在操作进行排队之后,LIO_NOWAIT
就会返回。list
是一个aiocb
引用的列表,最大元素的个数是由nent
定义的。注意list
的元素可以为NULL
,lio_listio
会将其忽略。sigevent
引用定义了在所有 I/O 操作都完成时产生信号的方法。
对于lio_listio
的请求与传统的read
或write
请求在必须指定的操作方面稍有不同,如清单 4 所示。
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 展示了这种通知方法。
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。
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 的性能进行优化:
|
结束语
使用异步 I/O 可以帮助我们构建 I/O 速度更快、效率更高的应用程序。如果我们的应用程序可以对处理和 I/O 操作重叠进行,那么 AIO 就可以帮助我们构建可以更高效地使用可用 CPU 资源的应用程序。尽管这种 I/O 模型与在大部分 Linux 应用程序中使用的传统阻塞模式都不同,但是异步通知模型在概念上来说却非常简单,可以简化我们的设计。
参考资料
学习关于作者
Tim Jones 是一名嵌入式软件工程师,他是 GNU/Linux Application Programming、AI Application Programming 以及 BSD Sockets Programming from a Multilanguage Perspective 等书的作者。他的工程背景非常广泛,从同步宇宙飞船的内核开发到嵌入式架构设计,再到网络协议的开发。Tim 是 Emulex Corp. 的一名资深软件工程师。 |