3.6   Asynchronous Input/Output

Asynchronous Input/Output (AIO) is the ability to perform input and output operations concurrently with ordinary internal processing. AIO enables you to decouple I/O operations from the activities of a particular task when these are logically independent.

The benefit of AIO is greater processing efficiency: it permits I/O operations to take place whenever resources are available, rather than making them await arbitrary events such as the completion of independent operations. AIO eliminates some of the unnecessary blocking of tasks that is caused by ordinary synchronous I/O; this decreases contention for resources between input/output and internal processing, and expedites throughput.

The VxWorks AIO implementation meets the specification in the POSIX 1003.1b standard. To include AIO in your VxWorks configuration, select INCLUDE_POSIX_AIO and INCLUDE_POSIX_AIO_SYSDRV in the project facility VxWorks view; see Tornado User's Guide: Projects for information on configuring VxWorks. The second configuration constant enables the auxiliary AIO system driver, required for asynchronous I/O on all current VxWorks devices.

3.6.1   The POSIX AIO Routines

The VxWorks library aioPxLib provides the POSIX AIO routines. To access a file asynchronously, open it with the open( ) routine, like any other file. Thereafter, use the file descriptor returned by open( ) in calls to the AIO routines. The POSIX AIO routines (and two associated non-POSIX routines) are listed in Table 3-4.

Table 3-4:  Asynchronous Input/Output Routines


Function
Description

aioPxLibInit( ) 
Initialize the AIO library (non-POSIX). 
aioShow( ) 
Display the outstanding AIO requests (non-POSIX).1  
aio_read( ) 
Initiate an asynchronous read operation. 
aio_write( ) 
Initiate an asynchronous write operation. 
aio_listio( ) 
Initiate a list of up to LIO_MAX asynchronous I/O requests. 
aio_error( ) 
Retrieve the error status of an AIO operation. 
aio_return( ) 
Retrieve the return status of a completed AIO operation. 
aio_cancel( ) 
Cancel a previously submitted AIO operation. 
aio_suspend( ) 
Wait until an AIO operation is done, interrupted, or timed out. 

1:  This function is not built into the Tornado shell. To use it from the Tornado shell, you must select INCLUDE_POSIX_AIO_SHOW for inclusion in the project facility VxWorks view. When you invoke the function, its output is sent to the standard output device.

The default VxWorks initialization code calls aioPxLibInit( ) automatically when INCLUDE_POSIX_AIO is selected for inclusion in the project facility VxWorks view. This routine takes one parameter, the maximum number of lio_listio( )calls that can be outstanding at one time. By default this parameter is MAX_LIO_CALLS (which can be seen on the Params tab of the properties window to be 0 by default). When the parameter is 0, the value is taken from AIO_CLUST_MAX (defined in installDir/target/h/private/aioPxLibP.h).

The AIO system driver, aioSysDrv, is initialized by default with the routine aioSysInit( ) when both INCLUDE_POSIX_AIO and INCLUDE_POSIX_AIO_SYSDRV are included. The purpose of aioSysDrv is to provide request queues independent of any particular device driver, so that you can use any VxWorks device driver with AIO.

The routine aioSysInit( ) takes three parameters: the number of AIO system tasks to spawn, and the priority and stack size for these system tasks. The number of AIO system tasks spawned equals the number of AIO requests that can be handled in parallel. The default initialization call( )uses three constants, all defined in configAll.h:

aioSysInit( MAX_AIO_SYS_TASKS, AIO_TASK_PRIORITY, AIO_TASK_STACK_SIZE )

When any of the parameters passed to aioSysInit( )is 0, the corresponding value is taken from AIO_IO_TASKS_DFLT, AIO_IO_PRIO_DFLT, and AIO_IO_STACK_DFLT (all defined in installDir/target/h/aioSysDrv.h).

Table 3-5 lists the names of the constants called from usrConfig.c and their default values (which can be seen on the Params tab of the properties window). It also shows the constants used within initialization routines when the parameters are left at their default values of 0, and where these constants are defined.

Table 3-5:  AIO Initialization Functions and Related Constants


Initialization
Function
configAll.h Constant
Def.
Value
Header File Constant used when arg = 0
Def.
Value
Header File
(all in installDir/target

aioPxLibInit( )  
MAX_LIO_CALLS  
0
AIO_CLUST_MAX  
100
h/private/aioPxLibP.h  
aioSysInit( )  
MAX_AIO_SYS_TASKS  
0
AIO_IO_TASKS_DFLT  
2
h/aioSysDrv.h  
   
AIO_TASK_PRIORITY  
0
AIO_IO_PRIO_DFLT  
50
h/aioSysDrv.h  
   
AIO_TASK_STACK_SIZE  
0
AIO_IO_STACK_DFLT  
0x7000
h/aioSysDrv.h  

3.6.2   AIO Control Block

Each of the AIO calls takes an AIO control block (aiocb) as an argument to describe the AIO operation. The calling routine must allocate space for the control block, which is associated with a single AIO operation. No two concurrent AIO operations can use the same control block; an attempt to do so yields undefined results.

The aiocb and the data buffers it references are used by the system while performing the associated request. Therefore, after you request an AIO operation, you must not modify the corresponding aiocb before calling aio_return( ); this function frees the aiocb for modification or reuse.


*

CAUTION: If a routine allocates stack space for the aiocb, that routine must call aio_return( ) to free the aiocb before returning.

The aiocb structure is defined in aio.h. It contains the following fields:

aio_fildes
file descriptor for I/O

aio_offset
offset from the beginning of the file

aio_buf
address of the buffer from/to which AIO is requested

aio_nbytes
number of bytes to read or write

aio_reqprio
priority reduction for this AIO request

aio_sigevent
signal to return on completion of an operation (optional)

aio_lio_opcode
operation to be performed by a lio_listio( ) call

aio_sys
VxWorks-specific data (non-POSIX)

For full definitions and important additional information, see the reference entry for aioPxLib.

3.6.3   Using AIO

The routines aio_read( ), aio_write( ), or lio_listio( ) initiate AIO operations. The last of these, lio_listio( ), allows you to submit a number of asynchronous requests (read and/or write) at one time. In general, the actual I/O (reads and writes) initiated by these routines does not happen immediately after the AIO request. For that reason, their return values do not reflect the outcome of the actual I/O operation, but only whether a request is successful--that is, whether the AIO routine is able to put the operation on a queue for eventual execution.

After the I/O operations themselves execute, they also generate return values that reflect the success or failure of the I/O. There are two routines that you can use to get information about the success or failure of the I/O operation: aio_error( ) and aio_return( ). You can use aio_error( )to get the status of an AIO operation (success, failure, or in progress), and aio_return( ) to obtain the return values from the individual I/O operations. Until an AIO operation completes, its error status is EINPROGRESS. To cancel an AIO operation, call aio_cancel( ).

AIO with Periodic Checks for Completion

The following code uses a pipe for the asynchronous I/O operations. The example creates the pipe, submits an AIO read request, verifies that the read request is still in progress, and submits an AIO write request. Under normal circumstances, a synchronous read to an empty pipe blocks and the task does not execute the write, but in the case of AIO, we initiate the read request and continue. After the write request is submitted, the example task loops, checking the status of the AIO requests periodically until both the read and write complete. Because the AIO control blocks are on the stack, we must call aio_return( ) before returning from aioExample( ).

Example 3-2:  Asynchronous I/O

/* aioEx.c - example code for using asynchronous I/O */
/* includes */
#include "vxWorks.h" #include "aio.h" #include "errno.h"
/* defines */
#define BUFFER_SIZE 200
/************************************************************************ * aioExample - use AIO library * * This example shows the basic functions of the AIO library. * * RETURNS: OK if successful, otherwise ERROR. */
STATUS aioExample (void) { int fd; static char exFile [] = "/pipe/1stPipe"; struct aiocb aiocb_read; /* read aiocb */ struct aiocb aiocb_write; /* write aiocb */ static char * test_string = "testing 1 2 3"; char buffer [BUFFER_SIZE]; /* buffer for read aiocb */
pipeDevCreate (exFile, 50, 100);
if ((fd = open (exFile, O_CREAT | O_TRUNC | O_RDWR, 0666)) == ERROR) { printf ("aioExample: cannot open %s errno 0x%x\n", exFile, errno); return (ERROR); }
printf ("aioExample: Example file = %s\tFile descriptor = %d\n", exFile, fd);
/* initialize read and write aiocbs */ bzero ((char *) &aiocb_read, sizeof (struct aiocb)); bzero ((char *) buffer, sizeof (buffer)); aiocb_read.aio_fildes = fd; aiocb_read.aio_buf = buffer; aiocb_read.aio_nbytes = BUFFER_SIZE; aiocb_read.aio_reqprio = 0;
bzero ((char *) &aiocb_write, sizeof (struct aiocb)); aiocb_write.aio_fildes = fd; aiocb_write.aio_buf = test_string; aiocb_write.aio_nbytes = strlen (test_string); aiocb_write.aio_reqprio = 0;
/* initiate the read */ if (aio_read (&aiocb_read) == -1) printf ("aioExample: aio_read failed\n");
/* verify that it is in progress */ if (aio_error (&aiocb_read) == EINPROGRESS) printf ("aioExample: read is still in progress\n");
/* write to pipe - the read should be able to complete */ printf ("aioExample: getting ready to initiate the write\n"); if (aio_write (&aiocb_write) == -1) printf ("aioExample: aio_write failed\n");
/* wait til both read and write are complete */ while ((aio_error (&aiocb_read) == EINPROGRESS) || (aio_error (&aiocb_write) == EINPROGRESS)) taskDelay (1);
/* print out what was read */ printf ("aioExample: message = %s\n", buffer);
/* clean up */ if (aio_return (&aiocb_read) == -1) printf ("aioExample: aio_return for aiocb_read failed\n"); if (aio_return (&aiocb_write) == -1) printf ("aioExample: aio_return for aiocb_write failed\n");
close (fd); return (OK); }

Alternatives for Testing AIO Completion

A task can determine whether an AIO request is complete in any of the following ways:

The following example is similar to the preceding aioExample( ), except that it uses signals to be notified when the write is complete. If you test this from the shell, spawn the routine to run at a lower priority than the AIO system tasks to assure that the test routine does not block completion of the AIO request. (For details on the shell, see the Tornado User's Guide: Shell.)

Example 3-3:  Asynchronous I/O with Signals

/* aioExSig.c - example code for using signals with asynchronous I/O */
/* includes */
#include "vxWorks.h" #include "aio.h" #include "errno.h"
/* defines */
#define BUFFER_SIZE 200 #define LIST_SIZE 1 #define EXAMPLE_SIG_NO 25 /* signal number */
/* forward declarations */
void mySigHandler (int sig, struct siginfo * info, void * pContext);
/************************************************************************ * aioExampleSig - use AIO library. * * This example shows the basic functions of the AIO library. * Note if this is run from the shell it must be spawned. Use: * -> sp aioExampleSig * * RETURNS: OK if successful, otherwise ERROR. */
STATUS aioExampleSig (void) { int fd; static char exFile [] = "/pipe/1stPipe"; struct aiocb aiocb_read; /* read aiocb */ static struct aiocb aiocb_write; /* write aiocb */ struct sigaction action; /* signal info */ static char * test_string = "testing 1 2 3"; char buffer [BUFFER_SIZE]; /* aiocb read buffer */
pipeDevCreate (exFile, 50, 100);
if ((fd = open (exFile, O_CREAT | O_TRUNC| O_RDWR, 0666)) == ERROR) { printf ("aioExample: cannot open %s errno 0x%x\n", exFile, errno); return (ERROR); }
printf ("aioExampleSig: Example file = %s\tFile descriptor = %d\n", exFile, fd);
/* set up signal handler for EXAMPLE_SIG_NO */ action.sa_sigaction = mySigHandler; action.sa_flags = SA_SIGINFO; sigemptyset (&action.sa_mask); sigaction (EXAMPLE_SIG_NO, &action, NULL);
/* initialize read and write aiocbs */ bzero ((char *) &aiocb_read, sizeof (struct aiocb)); bzero ((char *) buffer, sizeof (buffer)); aiocb_read.aio_fildes = fd; aiocb_read.aio_buf = buffer; aiocb_read.aio_nbytes = BUFFER_SIZE; aiocb_read.aio_reqprio = 0;
bzero ((char *) &aiocb_write, sizeof (struct aiocb)); aiocb_write.aio_fildes = fd; aiocb_write.aio_buf = test_string; aiocb_write.aio_nbytes = strlen (test_string); aiocb_write.aio_reqprio = 0;
/* set up signal info */ aiocb_write.aio_sigevent.sigev_signo = EXAMPLE_SIG_NO; aiocb_write.aio_sigevent.sigev_notify = SIGEV_SIGNAL; aiocb_write.aio_sigevent.sigev_value.sival_ptr = (void *) &aiocb_write;
/* initiate the read */ if (aio_read (&aiocb_read) == -1) printf ("aioExampleSig: aio_read failed\n");
/* verify that it is in progress */ if (aio_error (&aiocb_read) == EINPROGRESS) printf ("aioExampleSig: read is still in progress\n");
/* write to pipe - the read should be able to complete */ printf ("aioExampleSig: getting ready to initiate the write\n"); if (aio_write (&aiocb_write) == -1) printf ("aioExampleSig: aio_write failed\n");
/* clean up */ if (aio_return (&aiocb_read) == -1) printf ("aioExampleSig: aio_return for aiocb_read failed\n"); else printf ("aioExampleSig: aio read message = %s\n", aiocb_read.aio_buf);
close (fd); return (OK); }
void mySigHandler ( int sig, struct siginfo * info, void * pContext )
{ /* print out what was read */ printf ("mySigHandler: Got signal for aio write\n");
/* write is complete so let's do cleanup for it here */ if (aio_return (info->si_value.sival_ptr) == -1) { printf ("mySigHandler: aio_return for aiocb_write failed\n"); printErrno (0); } }