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提供了超时,而超时情况的确发生了,那么它就会返回-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_listioAPI 函数同时发起多个传输。这个函数非常重要,因为这意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,这非常重要,因此值得我们花点时间探索一下。lio_listioAPI 函数的原型如下:
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 所示。
清单 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 上直接下载。
讨论
- 通过参与 developerWorks blogs 加入 developerWorks 社区。
关于作者
| Tim Jones 是一名嵌入式软件工程师,他是 GNU/Linux Application Programming、AI Application Programming 以及 BSD Sockets Programming from a Multilanguage Perspective 等书的作者。他的工程背景非常广泛,从同步宇宙飞船的内核开发到嵌入式架构设计,再到网络协议的开发。Tim 是 Emulex Corp. 的一名资深软件工程师。 | |
