This section explores the asynchronous I/O model for Linux to help you understand how to apply it in your applications.
In a traditional I/O model,there is an I/O channel that is identified by a unique handle. In UNIX?,these are file descriptors (which are the same for files,pipes,sockets,and so on). In blocking I/O,you initiate a transfer and the system call returns when it's complete or an error has occurred.
In asynchronous non-blocking I/O,you have the ability to initiate multiple transfers at the same time. This requires a unique context for each transfer so you can identify it when it completes. In AIO,this is an?aiocb
?(AIO I/O Control Block) structure. This structure contains all of the information about a transfer,including a user buffer for data. When notification for an I/O occurs (called a completion),the?aiocb
?structure is provided to uniquely identify the completed I/O. The API demonstration shows how to do this.
The AIO interface API is quite simple,but it provides the necessary functions for data transfer with a couple of different notification models. Table 1 shows the AIO interface functions,which are further explained later in this section.
API function
Description |
aio_readaio_erroraio_returnaio_writeaio_suspendaio_cancellio_listio
Each of these API functions uses the?
aiocb
?structure for initiating or checking. This structure has a number of elements,but Listing 1 shows only the ones that you'll need to (or can) use.
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 /
...
};
The?sigevent
?structure tells AIO what to do when the I/O completes. You'll explore this structure in the AIO demonstration. Now I'll show you how the individual API functions for AIO work and how you can use them.
The?aio_read
?function requests an asynchronous read operation for a valid file descriptor. The file descriptor can represent a file,a socket,or even a pipe. The?aio_read
?function has the following prototype:
aio_read( struct aiocb *aiocbp );
The?aio_read
?function returns immediately after the request has been queued. The return value is zero on success or -1 on error,where?errno
is defined.
To perform a read,the application must initialize the?aiocb
?structure. The following short example illustrates filling in the?aiocb
?request structure and using?aio_read
?to perform an asynchronous read request (ignore notification for now). It also shows use of the?aio_error
?function,but I'll explain that later.
...
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 /
}
In Listing 2,after the file from which you're reading data is opened,you zero out your?aiocb
?structure,and then allocate a data buffer. The reference to the data buffer is placed into?aio_buf
. Subsequently,you initialize the size of the buffer into?aio_nbytes
. The?aio_offset
?is set to zero (the first offset in the file). You set the file descriptor from which you're reading into?aio_fildes
. After these fields are set,you call?aio_read
to request the read. You can then make a call to?aio_error
?to determine the status of the?aio_read
. As long as the status is?EINPROGRESS
,you busy-wait until the status changes. At this point,your request has either succeeded or failed.
Note the similarities to reading from the file with the standard library functions. In addition to the asynchronous nature of?aio_read
,another difference is setting the offset for the read. In a typical?read
?call,the offset is maintained for you in the file descriptor context. For each read,the offset is updated so that subsequent reads address the next block of data. This isn't possible with asynchronous I/O because you can perform many read requests simultaneously,so you must specify the offset for each particular read request.
The?aio_error
?function is used to determine the status of a request. Its prototype is:
aio_error( struct aiocb *aiocbp );
This function can return the following:
EINPROGRESS
,indicating the request has not yet completed
-
ECANCELLED
,indicating the request was cancelled by the application
-
-1
,indicating that an error occurred for which you can consult?errno
Another difference between asynchronous I/O and standard blocking I/O is that you don't have immediate access to the return status of your function because you're not blocking on the?read
?call. In a standard?read
?call,the return status is provided upon return of the function. With asynchronous I/O,you use the?aio_return
?function. This function has the following prototype:
aio_return( struct aiocb *aiocbp );
This function is called only after the?aio_error
?call has determined that your request has completed (either successfully or in error). The return value of?aio_return
?is identical to that of the?read
?or?write
?system call in a synchronous context (number of bytes transferred or?-1
?for error).
The?aio_write
?function is used to request an asynchronous write. Its function prototype is:
aio_write( struct aiocb *aiocbp );
The?aio_write
?function returns immediately,indicating that the request has been enqueued (with a return of?0
?on success and?-1
?on failure,witherrno
?properly set).
This is similar to the?read
?system call,but one behavior difference is worth noting. Recall that the offset to be used is important with the?read
?call. However,with?write
,the offset is important only if used in a file context where the?O_APPEND
?option is not set. If?O_APPEND
?is set,then the offset is ignored and the data is appended to the end of the file. Otherwise,the?aio_offset
?field determines the offset at which the data is written to the file.
You can use the?aio_suspend
?function to suspend (or block) the calling process until an asynchronous I/O request has completed,a signal is raised,or an optional timeout occurs. The caller provides a list of?aiocb
?references for which the completion of at least one will causeaio_suspend
?to return. The function prototype for?aio_suspend
?is:
aio_suspend( const struct aiocb *const cblist[],int n,const struct timespec *timeout );
Using?aio_suspend
?is quite simple. A list of?aiocb
?references is provided. If any of them complete,the call returns with?0
. Otherwise,?-1
?is returned,indicating an error occurred. See Listing 3.
/
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 );
Note that the second argument of?aio_suspend
?is the number of elements in?cblist
,not the number of?aiocb
?references. Any?NULL
?element in the?cblist
?is ignored by?aio_suspend
.
If a timeout is provided to?aio_suspend
?and the timeout occurs,then?-1
is returned and?errno
?contains?EAGAIN
.
The?aio_cancel
?function allows you to cancel one or all outstanding I/O requests for a given file descriptor. Its prototype is:
aio_cancel( int fd,struct aiocb *aiocbp );
To cancel a single request,provide the file descriptor and the?aiocb
?reference. If the request is successfully cancelled,the function returnsAIO_CANCELED
. If the request completes,the function returns?AIO_NOTCANCELED
.
To cancel all requests for a given file descriptor,provide that file descriptor and a?NULL
?reference for?aiocbp
. The function returns?AIO_CANCELED
if all requests are canceled,?AIO_NOT_CANCELED
?if at least one request couldn't be canceled,and?AIO_ALLDONE
?if none of the requests could be canceled. You can then evaluate each individual AIO request using?aio_error
. If the request was canceled,?aio_error
?returns?-1
,and?errno
?is set to?ECANCELED
.
Finally,AIO provides a way to initiate multiple transfers at the same time using the?lio_listio
?API function. This function is important because it means you can start lots of I/Os in the context of a single system call (meaning one kernel context switch). This is great from a performance perspective,so it's worth exploring. The?lio_listio
?API function has the following prototype:
lio_listio( int mode,struct aiocb *list[],int nent,struct sigevent *sig );
The?mode
?argument can be?LIO_WAIT
?or?LIO_NOWAIT
.?LIO_WAIT
?blocks the call until all I/O has completed.?LIO_NOWAIT
?returns after the operations have been queued. The?list
?is a list of?aiocb
?references,with the maximum number of elements defined by?nent
. Note that elements of?list
?may be?NULL
,which?lio_listio
?ignores. The?sigevent
?reference defines the method for signal notification when all I/O is complete.
The request for?lio_listio
?is slightly different than the typical?read
?or?write
?request in that the operation must be specified. This is illustrated in Listing 4.
...
/ 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,NULL );
The read operation is noted in the?aio_lio_opcode
?field with?LIO_READ
. For a write operation,?LIO_WRITE
?is used,but?LIO_NOP
?is also valid for no operation.
Now that you've seen the AIO functions that are available,this section digs into the methods that you can use for asynchronous notification. I'll explore asynchronous notification through signals and function callbacks.
The use of signals for interprocess communication (IPC) is a traditional mechanism in UNIX and is also supported by AIO. In this paradigm,the application defines a signal handler that is invoked when a specified signal occurs. The application then specifies that an asynchronous request will raise a signal when the request has completed. As part of the signal context,the particular?aiocb
?request is provided to keep track of multiple potentially outstanding requests. Listing 5 demonstrates this notification method.
...
/ 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 (<strong>aio_error</strong>( req ) == 0) {
/* Request completed successfully,get the return status */
ret = <strong>aio_return</strong>( req );
}
}
return;
}
In Listing 5,you set up your signal handler to catch the?SIGIO
?signal in the?aio_completion_handler
?function. You then initialize theaio_sigevent
?structure to raise?SIGIO
?for notification (which is specified via the?SIGEV_SIGNAL
?definition in?sigev_notify
). When your read completes,your signal handler extracts the particular?aiocb
?from the signal's?si_value
?structure and checks the error status and return status to determine I/O completion.
For performance,the completion handler is an ideal spot to continue the I/O by requesting the next asynchronous transfer. In this way,when completion of one transfer has completed,you immediately start the next.
An alternative notification mechanism is the system callback. Instead of raising a signal for notification,this mechanism calls a function in user-space for notification. You initialize the?aiocb
?reference into the?sigevent
?structure to uniquely identify the particular request being completed; see Listing 6.
...
/ 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 = <strong>aio_return</strong>( req );
}
return;
}
In Listing 6,after creating your?aiocb
?request,you request a thread callback using?SIGEV_THREAD
?for the notification method. You then specify the particular notification handler and load the context to be passed to the handler (in this case,a reference to the?aiocb
?request itself). In the handler,you simply cast the incoming?sigval
?pointer and use the AIO functions to validate the completion of the request.
The proc file system contains two virtual files that can be tuned for asynchronous I/O performance:
The /proc/sys/fs/aio-nr file provides the current number of system-wide asynchronous I/O requests.
- The /proc/sys/fs/aio-max-nr file is the maximum number of allowable concurrent requests. The maximum is commonly 64KB,which is adequate for most applications.
Using asynchronous I/O can help you build faster and more efficient I/O applications. If your application can overlap processing and I/O,then AIO can help you build an application that more efficiently uses the CPU resources available to you. While this I/O model differs from the traditional blocking patterns found in most Linux applications,the asynchronous notification model is conceptually simple and can simplify your design.
The??explains the internal details of AIO from the GNU Library perspective.
-
?explains more about AIO and a number of real-time extensions,from scheduling and POSIX I/O to POSIX threads and high resolution timers (HRT).
- In the??for the 2.5 integration,learn about the design and implementation of AIO in Linux.
- In the?,find more resources for Linux developers.
- Stay current with?.
With?,available for download directly from developerWorks,build your next development project on Linux.
Check out??and get involved in the?.
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!