7    Asynchronous Input and Output

I/O operations on a file can be either synchronous or asynchronous. For synchronous I/O operations, the process calling the I/O request is blocked until the I/O operation is complete and regains control of execution only when the request is completely satisfied or fails. For asynchronous I/O operations, the process calling the I/O request immediately regains control of execution once the I/O operation is queued to the device. When the I/O operation is completed (either successfully or unsuccessfully), the calling process can be notified of the event by a signal passed through the aiocb structure for the asynchronous I/O function. Alternatively, the calling process can poll the aiocb structure for completion status.

This chapter includes the following sections:

Asynchronous I/O is most commonly used in realtime applications requiring high-speed or high-volume data collection and/or low-priority journaling functions. Compute-intensive processes can use asynchronous I/O instead of blocking. For example, an application may collect intermittent data from multiple channels. Because the data arrives asynchronously, that is, when it is available rather than according to a set schedule, the receiving process must queue up the request to read data from one channel and immediately be free to receive the next data transmission from another channel. Another application may require such a high volume of reads, writes, and computations that it becomes practical to queue up a list of I/O operation requests and continue processing while the I/O requests are being serviced. Applications can perform multiple I/O operations to multiple devices while making a minimum number of function calls. The P1003.1b asynchronous I/O functions are designed to help meet these realtime needs.

You can perform asynchronous I/O operations using any open file descriptor.


7.1    Data Structures Associated with Asynchronous I/O

The P1003.1b asynchronous I/O functions use the asynchronous I/O control block aiocb. This control block contains asynchronous operation information such as the initial point for the read operation, the number of bytes to be read, and the file descriptor on which the asynchronous I/O operation will be performed. The control block contains information similar to that required for a read or write function, but additionally contains members specific to asynchronous I/O operations. The aiocb structure contains the following members:

     int             aio_fildes;    /* File descriptor                  */
     off_t           aio_offset;    /* File offset                      */
     volatile void   *aio_buf;      /* Pointer to buffer                */
     size_t          aio_nbytes;    /* Number of bytes to transfer      */
     int             aio_reqprio;   /* Request priority offset          */
     struct sigevent aio_sigevent;  /* Signal structure                 */
     int             aio_lio_opcode;/* Specifies type of I/O operation  */

Note that you cannot reuse the aiocb structure while an asynchronous I/O request is pending. To determine whether the aiocb is in use, use the aio_error function.


7.1.1    Identifying the Location

When you call either the aio_read or aio_write function, you must specify how to locate the data to be read or to position the data to be written.

The aio_offset and aio_nbytes members of the aiocb structure provide information about the starting point and length of the data to be read or written. The aio_buf member provides information about where the information should be read or written in memory.

When you use the aio_write function to write to a new file, data is written to the end of a zero-length file. On additional write operations, if the O_APPEND flag is set, write operations are appended to the file in the same order as the calls to the aio_write function were made. If the O_APPEND flag is not set, write operations take place at the absolute position in the file as given by the aio_offset as if the lseek function were called immediately prior to the operation with an offset equal to aio_offset and a whence equal to SEEK_SET.

On a call to the aio_read function, the read operation takes place at the absolute position in the file as given by aio_offset as if the lseek function were called immediately prior to the operation with an offset equal to aio_offset and a whence equal to SEEK_SET.

After a successful call to queue an asynchronous write operation with O_APPEND or to queue an asynchronous read, you must update the value of the offset with the value returned from the read or write operation. The file offset is not dynamically updated, and failure to update the value of the offset can produce incorrect results.

To determine whether the read or write operation was successful, call the aio_error function. If the operation was successful, call the aio_return function to update the value of the aio_offset member after each successful read or write operation. See Section 7.2.3 for an example of using these functions to determine status.


7.1.2    Specifying a Signal

You can send a signal on completion of every read and write operation, regardless of whether the operation is issued from a call to the aio_read, aio_write, or lio_listio function. In addition, you can send a signal on completion of the lio_listio function. See Chapter 5 for more information on signals and signal handling.

The aio_sigevent member refers to a sigevent structure that contains the signal number of the signal to be sent upon completion of the asynchronous I/O request. The sigevent structure is defined in the signal.h header file and contains the following members:

union sigval   sigev_value;   /* Application-defined value */
int            sigev_signo;   /* Signal to raise */
int            sigev_notify   /* Notification type */

The sigev_notify member specifies the notification mechanism to use when an asynchronous event occurs. There are two values defined for sigev_notify in P1003.1b: SIGEV_NONE and SIGEV_SIGNAL. SIGEV_NONE indicates that no asynchronous notification is delivered when an event occurs. SIGEV_SIGNAL indicates that the signal number specified in sigev_signo and the application-defined value specified in sigev_value are queued when an event occurs. When the signal is queued to the process, the value of aio_sigevent.sigev_value will be the si_value component of the generated signal. See Chapter 5 for more information.

The sigev_signo member specifies the signal number to be sent on completion of the asynchronous I/O operation. Setting the sigev_signo member to a legal signal value will cause that signal to be posted when the operation is complete, if sigev_notify equals SIGEV_SIGNAL. Setting the value to NULL means that no signal is sent, but the error status and return value for the operation are set appropriately and can be retrieved using the aio_error and aio_return functions.

Instead of specifying a signal, you can poll for I/O completion when you expect the I/O operation to be complete.


7.2    Asynchronous I/O Functions

The asynchronous I/O functions combine a number of tasks normally performed by the user during synchronous I/O operations. With synchronous I/O, the application typically calls the lseek function, performs the I/O operation, and then waits to receive the return status.

Asynchronous I/O functions provide the following capabilities:

The functions for performing and managing asynchronous I/O operations are as follows:

Function Description
aio_cancel Cancels one or more requests pending against a file descriptor
aio_error Returns the error status of a specified operation
aio_fsync Asynchronously writes system buffers containing a file's modified data to permanent storage
aio_read Initiates a read request on the specified file descriptor
aio_return Returns the status of a completed operation
aio_suspend Suspends the calling process until at least one of the specified requests has completed
aio_write Initiates a write request to the specified file descriptor
lio_listio Initiates a list of requests

Refer to the online reference pages for a complete description of these functions.


7.2.1    Reading and Writing

Asynchronous and synchronous I/O operations are logically parallel operations. The asynchronous functions aio_read and aio_write perform the same I/O operations as the read and write functions. However, the aio_read and aio_write functions return control to the calling process once the I/O is initiated, rather than after the I/O operation is complete. For example, when reading data from a file synchronously, the application regains control only after all the data is read. Execution of the calling process is delayed until the read operation is complete.

In contrast, when reading data from a file asynchronously, the calling process regains control right after the call is issued, before the read-and-return cycle is complete. The aio_read function returns once the read request is initiated or queued for delivery, even if delivery could be delayed. The calling process can use the time normally required to transfer data to execute some other task.

A typical application using asynchronous I/O includes the following steps:

  1. Create and fill the asynchronous I/O control block (aiocb).

  2. Call the open function to open a specified file and get a file descriptor for that file. After a call to the open function, the file pointer is set to the beginning of the file. Select flags as appropriate.

    Do not use the select system call with asynchronous I/O; the results are undefined.

  3. If you use signals, establish a signal handler to catch the signal returned on completion of the asynchronous I/O operation.

  4. Call the aio_read, aio_write, or aio_fsync function to request asynchronous I/O operations.

  5. Call aio_suspend if your application needs to wait for the I/O operations to complete; or continue execution and poll for completion with aio_error; or continue execution until the signal arrives.

  6. After completion, call the aio_return function to retrieve completion value.

  7. Call the close function to close the file. The close function waits for all asynchronous I/O to complete before closing the file.

On a call to either the _exit or fork function, the status of outstanding asynchronous I/O operations is undefined. If you plan to use asynchronous I/O operations in a child process, call the exec function before you call the I/O functions.


7.2.2    Using List-Directed Input/Output

To submit list-directed asynchronous read or write operations, use the lio_listio function. As with other asynchronous I/O functions, you must first establish the control block structures for the individual read and write operations. The information contained in this structure is used during the operations. The lio_listio function takes as an argument an array of pointers to I/O control block structures, which allows the calling process to initiate a list of I/O requests. Therefore, you can submit multiple operations as a single function call.

You can control whether the lio_listio function returns immediately after the list of operations has been queued or waits until all the operations have been completed. The mode argument controls when the lio_listio function returns and can have one of the following values:

Value Description
LIO_NOWAIT Queues the operation, returns, and can signal when the operation is complete.
LIO_WAIT Queues the operation, suspends the calling process until the operation is complete, and does not signal when the lio_listio operation is complete.

Completion means that all the individual operations in the list have completed, either successfully or unsuccessfully. In either case, the return value indicates only the success or failure of the lio_listio function call, not the status of individual I/O requests. In some cases one or more of the I/O requests contained in the list may fail. Failure of an individual request does not prevent completion of any other individual request. To determine the outcome of each I/O request, examine the error status associated with each lio_aiocb control block.

The list argument to the lio_listio function is a pointer to an array of aiocb structures.

The aio_lio_opcode member of the aiocb structure defines the I/O operation to be performed and the aio_fildes member identifies the file descriptor. The combination of these members makes it possible to specify individual read and write operations as if they had been submitted individually. Each read or write operation in list-directed asynchronous I/O has its own status, return value, and sigevent structure for signal delivery.

To use list-directed asynchronous I/O in your application, use the following steps:

  1. Create and fill the aiocb control blocks.

  2. Call the open function to open the specified files and get file descriptors for the files. After a call to the open function, the file pointer is set to the beginning of the file. Select flags as appropriate.

  3. If you use signals, establish signal handlers to catch the signals returned on completion of individual operations after the lio_listio function completes, or to catch a signal returned on completion of the entire list of I/O operations in the lio_listio request.

  4. Call the lio_listio function.

  5. Call the close function to close the files. The close function waits for all I/O to complete before closing the file.

As with other asynchronous I/O operations, any open function that returns a file descriptor is appropriate. On a call to either the _exit or fork function, the status of outstanding asynchronous I/O operations is undefined.


7.2.3    Determining Status

Asynchronous I/O functions provide status values when the operation is successfully queued for servicing and provides both error and return values when the operation is complete. The status requirements for asynchronous I/O are more complex than the functionality provided by the errno function, so status retrieval for asynchronous I/O is accomplished through using the aio_error and aio_return functions in combination with each other.

The aiocbp argument to the aio_error or aio_return function provides the address of an aiocb structure, unique for each asynchronous I/O operation. The aio_error function returns the error status associated with the specified aiocbp. The error status is the errno value that is set by the corresponding asynchronous I/O read or write operation.

The aio_error function returns EINPROGRESS if the operation is ongoing. Once the asynchronous I/O operation is complete, EINPROGRESS is not returned. A subsequent call to the aio_return function will show if the operation is successful.

Once you call the aio_return function, the system resources associated with the aiocb for the duration of the I/O operation are returned to the system. If the aio_return function is called for an aiocb with incomplete I/O, the result of the operation is undefined. To avoid losing data, use the aio_error function to ensure completion before you call the aio_return function. Then use the aio_return function to retrieve the number of bytes read or written during the asynchronous I/O operation.

If you do not call the aio_return function, the number of asynchronous I/O resources available for use in your application is reduced by one for every completed asynchronous I/O operation that does not return data through a call to the aio_return function.

The following example shows how to use the aio_error and aio_return functions to track the progress of asynchronous write operations.

     
.
.
.
return_value = aio_error(aiocbp); if (return_value != EINPROGRESS) { total = aio_return(aiocbp); if (total == -1) { errno = return_value; perror("aio_read"); } }
.
.
.

In this example, the variable total receives the number of bytes read in the operation. This variable is then used to update the offset for the next read operation.

If you use list-directed asynchronous I/O, each asynchronous I/O operation in the list has an aiocb structure and a unique aiocbp.


7.2.4    Canceling I/O

Sometimes there is a need to cancel an asynchronous I/O operation after it has been issued. For example, there may be outstanding requests when a process exits, particularly if the application uses slow devices, such as terminals.

The aio_cancel function cancels one or more outstanding I/O requests against a specified file descriptor. The aiocbp argument points to an aiocb control block for a specified file descriptor. If the operation is successfully canceled, the error status indicates success. If, for some reason, the operation cannot be canceled, normal completion and notification take place.

The aio_cancel function can return one of the following values:

Value Description
AIO_ALLDONE Indicates that none of the requested operations could be canceled because they had already completed when the call to the aio_cancel function was made.
AIO_CANCELED Indicates that all requested operations were canceled.
AIO_NOTCANCELED Indicates that some of the requested operations could not be canceled because they were in progress when the call to the aio_cancel function was made.

If the value of AIO_NOTCANCELED is returned, call the aio_error function and check the status of the individual operations to determine which ones were canceled and which ones could not be canceled.


7.2.5    Blocking to Completion

The aio_suspend function lets you suspend the calling process until at least one of the asynchronous I/O operations referenced by the aiocbp argument has completed or until a signal interrupts the function. If the operation had completed when the call to the aio_suspend function was made, the function returns without suspending the calling process. Before using the aio_suspend function, your application must already have initiated an I/O request with a call to the aio_read, aio_write, aio_fsync, or lio_listio function.


7.2.6    Asynchronous File Synchronization

The aio_fsync function is similar to the fsync function; however, it executes in an asynchronous manner, in the same way that aio_read performs an asynchronous read.

The aio_fsync function requests that all I/O operations queued to the specified file descriptor at the time of the call to aio_fsync be forced to the synchronized I/O completion state. Unlike fsync, aio_fsync returns control to the calling process once the operation has been initiated, rather than after the operation is complete. I/O operations that are subsequently initiated on the file descriptor are not guaranteed to be completed by any previous calls to aio_fsync.

Like the aio_read and aio_write functions, aio_fsync takes an aiocbp value as an argument, which can then be used in subsequent calls to aio_error and aio_return in order to determine the error and return status of the asynchronous operation. In addition, the aio_sigevent member of aiocbp can be used to define the signal to be generated when the operation is complete.

Note that the aio_fsync function will force to completion <EMPHASIS>all I/O operations on the specified file descriptor, whether initiated by synchronous or asynchronous functions.


7.3    Asynchronous I/O to Raw Devices

You may have applications which call for performing asynchronous I/O operations by reading to and writing from raw partitions. DIGITAL UNIX provides the libaio_raw.a library for those applications which will only perform asynchronous I/O operations to raw devices, When using this library, you are not required to link with pthreads, libmach, or libc_r.

If you attempt to perform asynchronous I/O operations to a file when linked with libaio_raw.a, the request fails with an error of ENOSYS.

The syntax for compiling or linking with libaio_raw.a is as follows:

% cc -o binary_name my_program -laio_raw


7.4    Asynchronous I/O Examples

The examples in this section demonstrate the use of the asynchronous I/O functions. Example 7-1 uses the aio functions; Example 7-2 uses the lio_listio function.


7.4.1    Using the aio Functions

In Example 7-1, the input file (read synchronously) is copied to the output file (asynchronously) using the specified transfer size. A signal handler counts the number of completions, but is not required for the functioning of the program. A call to the aio_suspend function is sufficient.

Example 7-1:  Using Asynchronous I/O

/*
 * Command line to build the program:
 * cc -o aio_copy aio_copy.c -laio -pthread
 */

           /* * * *  aio_copy.c  * * * */

#include <unistd.h>
#include <aio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>
#include <signal.h>
#include <errno.h>
#include <malloc.h>

#define BUF_CNT 2     /* number of buffers */

/* To run completion code in the signal handler, define the following: */
#define COMPLETION_IN_HANDLER

struct sigaction sig_act;
volatile int sigcnt = 0;
volatile int total = 0;

           /* * * *  Signal handler * * * */

/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
void sig_action(signo,info,context)
int signo;
siginfo_t *info;
void *context;
{
        printf("Entered sig_action\n");
        printf("  signo = %d \n",signo);
        printf("  si_code  = %d \n",info->si_code);

#ifndef COMPLETION_IN_HANDLER
        printf("  si_value.sival_int = %d decimal\n",
               info->si_value.sival_int);

#else
        printf("  si_value.sival_ptr = %lx hex \n",info->si_value.sival_ptr);

        /* Call aio_error and aio_return from the signal handler.
         * Note that si_value is the address of the write aiocb.
         */
        while (aio_error((struct aiocb *)info->si_value.sival_ptr) ==
               EINPROGRESS);

        /* * * * Update total bytes written to set new file offset * * * */
        total += aio_return((struct aiocb *)info->si_value.sival_ptr);
#endif

/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
        sigcnt++;
        return;
}

void sig_handler(signo)
int signo;
{
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
        printf("Entered sig_handler, signo = %d \n",signo);
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
        sigcnt++;
        return;
}

           /* * * * Main Routine * * * */

main(int argc, char **argv)
{
        int             in_file, out_file, rec_cnt = 0;
        typedef char   *buf_p;
        buf_p           buf[BUF_CNT];
        aiocb_t         a_write;
        size_t          xfer_size;
        int             buf_index, ret;

        /* * * * Check number of input arguments * * * */

        if (argc < 4) {
         fprintf(stderr, "Usage: %s input-file output-file buf-size-in-Kb\n",
               argv[0]);
          exit(0);
        }

           /* * * * Open input file * * * */

        if ((in_file = open(argv[1], O_RDONLY)) == -1) {
                perror(argv[1]);
                exit(errno);
        }
        printf("Opened Input File\n");

           /* * * * Open output file * * * */

        /* If O_APPEND is added to flags, all writes will appear at end */
        if ((out_file = open(argv[2], O_WRONLY|O_CREAT, 0777)) == -1) {
                perror(argv[2]);
                exit(errno);
        }
        printf("Opened Output File \n");

           /* * * * Calculate transfer size (# bufs * 1024) * * * */

        xfer_size = atol(argv[3]) * 1024;

           /* * * * Allocate buffers for file copy * * * */

        for (buf_index = 0; buf_index < BUF_CNT; buf_index++)
                buf[buf_index] = (buf_p) malloc(xfer_size);

        buf_index = 0;

           /* * * *  Init. signal action structure for SIGUSR1  * * * */

/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
        sigemptyset(&sig_act.sa_mask);  /* block only current signal */

        /*  If the SA_SIGINFO flag is set in the sa_flags field then
         *  the sa_sigaction field of sig_act structure specifies the
         *  signal catching function:
         */
        sig_act.sa_flags = SA_SIGINFO;
        sig_act.sa_sigaction = sig_action;

        /*  If the SA_SIGINFO flag is NOT set in the sa_flags field
         *  then the the sa_handler field of sig_act structure specifies
         *  the signal catching function, and the signal handler will be
         *  invoked with 3 arguments instead of 1:
         *     sig_act.sa_flags = 0;
         *     sig_act.sa_handler = sig_handler;
         */

           /* * * *  Estab. signal handler for SIGUSR1 signal * * * */

        printf("Establish Signal Handler for SIGUSR1\n");
        if (ret = sigaction (SIGUSR1, /* Set action for SIGUSR1       */
            &sig_act,                 /* Action to take on signal     */
            0))                       /* Don't care about old actions */
            perror("sigaction");
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/

           /* * * * Init. aio control block (aiocb) * * * */

        a_write.aio_fildes = out_file;
        a_write.aio_offset = 0;                   /* write from current */
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
        a_write.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
        a_write.aio_sigevent.sigev_signo  = SIGUSR1; /* completion signal */

#ifdef COMPLETION_IN_HANDLER
        /* Fill in a user-specified value which will be the si_value
         * component of the generated signal.  sigev_value is a union
         * of either an int (sival_int) or a void * (sival_ptr).
         * In this example, we use the sival_ptr field, and pass
         * the address of the aiocbp into the signal handler, so that
         * the signal handler can call aio_error and aio_return directly:
         */
        a_write.aio_sigevent.sigev_value.sival_ptr = &a_write;
#else
        /* Pass an integer value into the signal handler: */
        a_write.aio_sigevent.sigev_value.sival_int = 1;
#endif
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/

           /* * * * Copy from in_file to out_file * * * */

        while (in_file != -1) {
                int buf_len;

           /* * * * Read next buffer of information * * * */

                buf_len = read( in_file, buf[buf_index], xfer_size);

#ifdef COMPLETION_IN_HANDLER
                if (rec_cnt) {  /* will be >1 on all but first write... */
                        aiocb_t *wait_list = &a_write;

                        /* Wait until previous write completes */
                                aio_suspend(&wait_list,1,NULL);

                }  /* if (rec_cnt) */

#else

                if (rec_cnt) {  /* will be >1 on all but first write... */

                        /* previous write completed? If not, wait */
                        while (aio_error(&a_write) == EINPROGRESS) {
                                aiocb_t *wait_list = &a_write;
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
                                /* No timeout specified */
                                aio_suspend(&wait_list,1,NULL);
                                /* aio_suspend(1, &wait_list); */
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
                        }

           /* * * * Update total bytes written to set new file offset * * * */

                        total += aio_return(&a_write);
                }  /* if (rec_cnt) */
#endif

           /* * * * Check for end-of-file (won't have filled buffer) * * */

                if (buf_len <= 0)
                        break;

           /* * * * Set buffer up for next write * * * */

                a_write.aio_nbytes = buf_len;
                a_write.aio_buf = buf[buf_index];

                /* if file is opened for append, can ignore offset field */

                a_write.aio_offset = total;
                ret = aio_write(&a_write);
                if (ret) { perror ("aio_write"); exit(ret); }

           /* * * Update record count, and position to next buffer * * */

                rec_cnt++;
                buf_index ^= 1;
        }

        printf("total number of bytes written to output file = %d\n",total);

           /* * * * Close files * * * */

        close(in_file);
        printf("Closed Input File\n");
        close(out_file);
        printf("Closed Output File\n");
        printf("Copied: %d records, %d signals taken\n", rec_cnt, sigcnt);
}


7.4.2    Using the lio_listio Function

In Example 7-2 the input file is read synchronously to a specified number of output files (asynchronously) using the specified transfer size from the lio_listio function. After the list-directed I/O completes, it checks the return status and value for the write to each file and continues in a loop until the copy is complete.

Example 7-2:  Using lio_listio in Asynchronous I/O

/*
 * Command line to build the program:
 * cc -o lio_copy lio_copy.c -non_shared -O0 -L/usr/ccs/lib \
 *       -laio -pthread
 */

/* * * *  lio_copy.c  * * * */

#include <unistd.h>
#include <aio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>
#include <signal.h>
#include <errno.h>
#include <malloc.h>

#define FOR_EACH_FILE   for (i = 0; i < out_cnt; i++)

#define BUF_CNT 2                     /* number of buffers */

/* * * * ------------------  Main Routine ------------------- * * * */

main(int argc, char **argv)
{
        register int    i, rec_cnt = 0, out_cnt = 0;
        char            outname[128], temp[8];
        int             in_file, out_file[AIO_LISTIO_MAX], len;
        typedef char   *buf_p;
        buf_p           buf[BUF_CNT];
        aiocb_t         a_write[AIO_LISTIO_MAX], *wait_list[AIO_LISTIO_MAX];
        size_t          xfer_size;
        int             buf_index, total[AIO_LISTIO_MAX], ret;
        struct sigevent lio_sigevent = {0,0};

        /* * * * Check the number of input arguments * * * */

        if (argc < 5) {
                fprintf(stderr, "Usage: %s in_file out_file buffsz-in-kb
                        #-out-files\n", argv[0]);
                exit(0);
        }

        /* * * * Open the input file * * * */

        if ((in_file = open(argv[1], O_RDONLY)) == -1) {
                perror(argv[1]);
                exit(errno);
        }
        printf("\tOpened Input File %s\n", argv[1]);

        /* * * * Open the output files * * * */

        out_cnt = atoi(argv[4]);
        if ((out_cnt <=  0) || (out_cnt > AIO_LISTIO_MAX)) {
                fprintf(stderr, "Number of output files must be 1-%d.\n",
                        AIO_LISTIO_MAX);
                exit(EINVAL);
        }

        outname[0] = '\0';
        len = strlen(argv[2]);
        strcpy(outname, argv[2]);

        FOR_EACH_FILE {
                sprintf(&outname[len], "%d", i);
                /*
                 * If O_APPEND is added to flags, all writes will appear at
                 * end
                 */
                if ((out_file[i] = open(outname, O_WRONLY|O_CREAT, 0777))
                                == -1) {
                        perror(outname);
                        exit(errno);
                }
                printf("\tOpened output file %s\n", outname);
        }

        /* * * * Calculate the transfer size (# bufs * 1024) * * * */

        xfer_size = atol(argv[3]) * 1024;

        /* * * * Allocate buffers for file copy * * * */

        for (buf_index = 0; buf_index < BUF_CNT; buf_index++) {
                buf[buf_index] = (buf_p) malloc(xfer_size);
                if (buf[buf_index] == NULL) {
                        perror("malloc");
                        exit(1);
                }
        }

        buf_index = 0;

        /* * * * Init the aio control blocks and wait list * * * */

        FOR_EACH_FILE {
                a_write[i].aio_fildes = out_file[i];
                a_write[i].aio_lio_opcode = LIO_WRITE;
                a_write[i].aio_sigevent.sigev_signo = 0;
                wait_list[i] = &a_write[i];
                total[i] = 0;
        }

        /* * * * Copy from in_file to out_file * * * */

        while (in_file != -1) {
                int buf_len;

        /* * * * Read the next buffer of information * * * */

                buf_len = read(in_file, buf[buf_index], xfer_size);

                if (rec_cnt) {  /* will be >1 on all but the first write... */

        /* * * * Update the bytes written to set new offset * * * */

                        FOR_EACH_FILE {
                                errno = aio_error(&a_write[i]);
                                ret = aio_return(&a_write[i]);
                                if (ret == -1) {
                                        perror("Write error");
                                        exit(1);
                                } else {
                                        total[i] += ret;
                                }
                        }
                }

      /* * * * Check for end-of-file (won't have filled buffer) * * */

                if (buf_len <= 0)
                        break;

      /* * * * Set the buffer up for the next write * * * */

                FOR_EACH_FILE {
                        a_write[i].aio_nbytes = buf_len;
                        a_write[i].aio_buf = buf[buf_index];
                        /* if opened for append, ignore offset field */
                        a_write[i].aio_offset = total[i];
                }

                ret = lio_listio(LIO_WAIT, wait_list, out_cnt, &lio_sigevent);
                if (ret) /* report failure status, but don't exit yet */
                        perror("lio_listio");

      /* * * Update record count, and position to next buffer * * */

                buf_index ^= 1;
                rec_cnt++;
        }

        /* * * * Close the files * * * */

        close(in_file);
        printf("\tClosed input file\n");
        FOR_EACH_FILE {
                close(out_file[i]);
        }
        printf("\tClosed output files\n");
        printf("Copied %d records to %d files\n", rec_cnt * out_cnt, out_cnt);
}

Note

Use of the printf function in this example is for illustrative purposes only. You should avoid using printf and any similar functions in signal handlers because they can affect scheduling characteristics.