POSIX asynchronous I/O

    The POSIX asynchronous I/O interface provides a consistent set of methods for asynchronous I/O to different types of files. These interfaces use AIO control blocks to describe I/O operations. The aiocb structure defines the AIO control block and includes at least the following fields:
#include <aio.h>
struct aiocb{
    int aio_fildes; // file descriptor
    off_t           aio_offset;      // file offset for I/O
    volatile  void *aio_buf;         // buffer for I/O
    size_t          aio_nbytes;      // number of bytes to transfer
    int             aio_reqprio;     // priority
    struct sigevent aio_sigevent;    // signal information
    int             aio_lio_opcode;  // operation for list I/O
};
struct sigevent{
    int             sigev_notify;    // notify type
    int             sigev_signo;     // signal number
    union sigval    sigev_value;     // notify argument
    void (*sigev_notify_function)(union sigval);    // notify function
    pthread_attr_t *sigev_notify_attributes;        // notify attrs
};

    In the aiocb structure, the aio_fildes field represents the file descriptor that is opened for reading or writing. A read or write operation starts at the offset specified by aio_offset (note that asynchronous I/O operations must explicitly specify the offset. As long as you do not mix asynchronous and traditional I/O functions in the same process On the same file, the asynchronous I/O interface does not affect the file offset maintained by the operating system. In addition, if the asynchronous I/O interface is used to write data to a file opened in append mode, the aio_offset field will be neglect). The operations of reading and writing data are performed in the buffer specified by aio_buf, and the aio_nbytes field contains the number of bytes to be read and written. aio_reqprio hints the order for asynchronous I/O requests (but the system has limited control over this order, so it doesn't necessarily follow). The aio_lio_opcode field can only be used for list-based asynchronous I/O (see below). aio_sigevent uses the sigevent structure to control how the application is notified when an I/O event is complete.
    In the sigevent structure, the sigev_notify field controls the type of notification, and its value may be one of the following three.
    (1) SIGEV_NONE: After the asynchronous I/O request is completed, the process is not notified.
    (2) SIGEV_SIGNAL: After the asynchronous I/O request is completed, the signal specified by the sigev_signo field is generated. If the application has chosen to catch the signal and specified the SA_SIGINFO flag when establishing the signal handler, the signal will be enqueued (if the implementation supports queued signals). The signal handler is passed to a siginfo structure whose si_value field is set to sigev_value (if the SA_SIGINFO flag is used).
    (3) SIGEV_THREAD: When the asynchronous I/O request is completed, the function specified by sigev_notify_function will be called, and the sigev_value field is passed in as its only parameter. Unless the sigev_notify_attributes field is set to the address of a pthread attributes structure that specifies another thread attribute, the function will execute in a separate thread in the detached state.
    The AIO control block needs to be initialized before asynchronous I/O. The aio_read and aio_write functions can be used to perform asynchronous read and asynchronous write operations, respectively.
#include <aio.h>
int aio_read(struct aiocb *aiocb);
int aio_write(struct aiocb *aiocb);
                            /* The return value of the two functions: if successful, return 0; otherwise, return -1 */

    When these functions return successfully, the asynchronous I/O request has been queued by the system for processing. These return values ​​have nothing to do with the results of the actual I/O operation. When an I/O operation is waiting, care must be taken to ensure that the AIO control block and database buffer remain stable, and the corresponding memory under them must always be legal, and cannot be reused unless the I/O operation is completed.
    To force all pending asynchronous operations to write to persistent storage without waiting, call the aio_fsync function. To know the completion status of an asynchronous read, write, or synchronous operation, call the aio_error and aio_return functions.
#include <aio.h>
int aio_fsync(int op, struct aiocb *aiocb);
                                      /* Return value: if successful, return 0; otherwise, return -1 */
int aio_error(const struct aiocb *aiocb);
ssize_t aio_return(const struct aiocb *aiocb);
                                     /* The return values ​​of the two functions: see below respectively*/

    aio_fsync returns after the synchronization is scheduled, and the data will not be persisted until the asynchronous synchronization operation is completed. The aio_fields field in the AIO control block specifies the files whose asynchronous writes are synchronized. If the op parameter is set to O_DSYNC, the operation will behave as if fdatasync had been called. Otherwise, if the op parameter is set to O_SYNC, the operation will behave as if fsync had been called.
    The return value of aio_error is one of the following four cases.
    (1) 0: Indicates that the asynchronous operation is successfully completed, and the aio_return function needs to be called to obtain the return value of the operation.
    (2) -1: Indicates that the call to aio_error failed. You can find out the reason for the failure by checking errno.
    (3) EINPROGRESS: Indicates that an asynchronous read, write or synchronous operation is still waiting.
    (4) Other cases: any other return value is the error code returned by the failure of the related asynchronous operation.
    Care needs to be taken not to call the aio_return function until the asynchronous operation is complete, otherwise the result is undefined. Also, be careful to call aio_return only once per asynchronous operation, because once the function is called, the operating system can free the record containing the return value of the I/O operation. If aio_return itself fails, it returns -1 and sets errno. Otherwise, it will directly return the result when read, write, or fsync was successfully called.
    When performing an I/O operation, you can use asynchronous I/O if there are other transactions to be processed and you do not want to be blocked by the I/O operation. But if the asynchronous operation is not completed after all transactions are completed, the aio_suspend function can be called to block the process until the operation is completed. And when there are still pending asynchronous I/O operations that you don't want to complete, you can try to cancel them with the aio_cancel function.
#include <aio.h>
int aio_suspend(const struct aiocb *const list[], int nent, const struct timespec *timeout);
                                      /* Return value: if successful, return 0; otherwise, return -1 */
int aio_cancel(int fd, struct aiocb *aiocb); /* Return value: see below*/

    The list parameter of aio_suspend is a pointer to the array of AIO control blocks, the nent parameter indicates the number of entries in the array, the null pointer will be skipped, and other entries must point to the AIO control that has been used to initialize asynchronous I/O operations Piece. If the function is interrupted by a signal, it will return -1 and set errno to EINTR, and when the blocking time expires, it will set errno to EAGAIN (no time limit when the timeout parameter is a null pointer).
    The fd parameter of aio_cancel specifies the file descriptor for the outstanding asynchronous I/O operation. If the aiocb parameter is NULL, the system will attempt to cancel all outstanding asynchronous I/O operations on the file. Otherwise, the system will attempt to cancel the single asynchronous I/O operation described by the AIO control block. I say "attempted" cancellation because there is no guarantee that the system will be able to cancel any operation in progress. The function may return one of the following 4 values:
    (1) AIO_ALLDONE: All operations have been completed before attempting to cancel.
    (2) AIO_CANCELED: So the requested operation has been canceled.
    (3) AIO_NOTCANCELED: At least one requested operation has not been canceled.
    (4)-1: The function call fails and errno is set.
    If the asynchronous I/O operation is successfully canceled, calling the aio_error function on the corresponding AIO control block will return ECANCELED; if the operation cannot be canceled, the corresponding AIO control block will not be modified by the call to aio_cancel.
    There is also a function lio_listio that is also included in the asynchronous I/O interface, although it can be used both synchronously and asynchronously. This function submits a series of I/O requests described by a list of AIO control blocks.
#include <aio.h>
int lio_listio(int mode, struct aiocb *restrict const list[restrict],
               int nent, struct sigevent *restrict sigev);
                                      /* Return value: if successful, return 0; otherwise, return -1 */

    The mode parameter determines whether the I/O is really asynchronous: if the parameter is set to LIO_WAIT, the function will return after all the I/O operations specified by the list are completed, and the sigev parameter will be ignored at this time; If it is set to LIO_NOWAIT, the function will return as soon as the I/O request is enqueued, and the process will be notified asynchronously of the event specified by the sigev parameter after all I/O operations are complete. If you do not want to be notified, you can set sigev to NULL (note that each AIO control block itself may also have asynchronous notification enabled when its respective operation completes, the asynchronous notification specified by the sigev parameter is in addition to this, and Sent only after all I/O operations are complete).
    The list parameter points to a list of AIO control blocks that specify the I/O operations to run, which can contain NULL pointers, those entries are ignored. The nent parameter specifies the number of elements in the array. In each AIO control block, the aio_lio_opcode field specifies whether the operation is a read operation (LIO_READ), a write operation (LIO_WRITE), or a no-op to be ignored (LIO_NOP). The read operation will be passed to the aio_read function according to the corresponding AIO control block, and the write operation will be passed to the aio_write function according to the corresponding AIO control block.
    The number of asynchronous I/O operations is limited by the runtime as shown in the following table.

    The values ​​of AIO_LISTIO_MAX, AIO_MAX, and AIO_PRIO_DELTA_MAX can be set by calling the sysconf function with the name parameter set to _SC_IO_LISTIO_MAX, _SC_AIO_MAX, and _SC_AIO_PRIO_DELTA_MAX, respectively.
    The following example shows how to use the asynchronous I/O interface to implement a ROT-13 algorithm used in the popular USENET news system of the 1980s, which was originally used to convert offensive or spoiler-containing text in And the article in the joke section is fuzzified, it cyclically shifts the English characters a~z and A~Z in the text by 13 letters to the right, and other characters remain unchanged (in order to avoid bloated code, except for return detection outside of aio_error and aio_return calls).
#include <stdio.h>
#include <stdlib.h>
#include <aio.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

#define BSZ 40 //4096
#define NBUF 8
#define FILE_MODE (S_IRUSR |S_IWUSR |S_IRGRP |S_IROTH)

enum rwop{ UNUSED = 0, READ_PENDING = 1, WRITE_PENDING = 2 };
struct buf{
	enum rwop op;
	int				last;
	struct aiocb	aiocb;
	unsigned char	data[BSZ];
};
struct buf bufs [NBUF];

unsigned char translate(unsigned char c){
	if(isalpha(c)){
		if(c >= 'n') c -= 13;
		else if(c >= 'a') c += 13;
		else if(c >= 'N') c -= 13;
		else c += 13;
	}
	return c;
}

int main(int argc, char *argv[]){
	if(argc != 3){
		printf("usage: %s <infile> <outfile>\n", argv[0]);
		exit(1);
	}
	int ifd = open(argv[1], O_RDONLY);
	int ofd = open(argv[2], O_RDWR |O_CREAT |O_TRUNC, FILE_MODE);
	struct stat	sbuf;
	fstat(ifd, &sbuf);

	const struct aiocb	*aiolist[NBUF];
	int i, j;
	for(i=0; i<NBUF; i++){		// initialize the buffers
		bufs [i] .op = UNUSED;
		bufs [i] .aiocb.aio_buf = bufs [i] .data;
		bufs[i].aiocb.aio_sigevent.sigev_notify = SIGEV_NONE;
		aiolist[i] = NULL;
	}
	int	numop = 0;
	off_t	off = 0;
	for(;;){
		for(i=0; i<NBUF; i++){
			int n, err;
			switch (bufs [i] .op) {
			case UNUSED:
				if(off < sbuf.st_size){
					bufs[i].op = READ_PENDING;
					bufs [i] .aiocb.aio_fildes = ifd;
					bufs [i] .aiocb.aio_offset = off;
					off += BSZ;
					if(off >= sbuf.st_size)
						bufs [i] .last = 1;
					bufs [i] .aiocb.aio_nbytes = BSZ;
					aio_read(&bufs[i].aiocb);
					aiolist[i] = &bufs[i].aiocb;
					numop++;
				}
				break;
			case READ_PENDING:
				if((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS)
					continue;
				if(err != 0){
					if(err == -1)
						printf("aio_error failed\n");
					else
						printf("read failed, errno = %d\n", err);
					continue;
				}
				if((n = aio_return(&bufs[i].aiocb)) < 0){
					printf("aio_return failed\n");
					continue;
				}
				if(n != BSZ && !bufs[i].last)
					printf("short read (%d/%d)\n", n, BSZ);
				for(j=0; j<n; j++)	// translate the buffer
					bufs [i] .data [j] = translate (bufs [i] .data [j]);
				bufs[i].op = WRITE_PENDING;
				bufs [i] .aiocb.aio_fildes = ofd;
				bufs [i] .aiocb.aio_nbytes = n;
				aio_write(&bufs[i].aiocb);
				break;					// retain our spot in aiolist
			case WRITE_PENDING:
				if((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS)
					continue;
				if(err != 0){
					if(err == -1)
						printf("aio_error failed\n");
					else
						printf("read failed, errno = %d\n", err);
					continue;
				}
				if((n = aio_return(&bufs[i].aiocb)) < 0){
					printf("aio_return failed\n");
					continue;
				}
				if (n! = bufs [i] .aiocb.aio_nbytes)
					printf("short write (%d/%d)\n", n, BSZ);
				aiolist[i] = NULL;
				bufs [i] .op = UNUSED;
				numop--;
				break;
			}
		}
		if(numop == 0)
			if(off >= sbuf.st_size)
				break;
		else
			aio_suspend(aiolist, NBUF, NULL);
	}
	bufs [0] .aiocb.aio_fildes = ofd;
	aio_fsync(O_SYNC, &bufs[0].aiocb);
	exit(0);
}

    Here 8 buffers are used, so up to 8 asynchronous I/O requests can be pending. Surprisingly though, this may degrade performance, because if reads are committed to the filesystem in an out-of-order manner, the OS's read-ahead algorithm will fail. This example does not use asynchronous notifications, if there is something else to do while the I/O operation is in progress, the extra work can be included in the for loop. And if you want to prevent this extra work from delaying the task of translating files, you should consider using asynchronous notifications. In the case of multitasking, the priority of each task may also be considered.
    If you run the program with this code file as the input file, you get the following results.
$ gcc -lrt aio_rot13.c -o aio_rot13.out # Compile the file
$ ./aio_rot13.out aio_rot13.c aio.test # Execute the program
$ head -n 5 aio.test # View the first five lines of the translated file
#vapyhqr <fgqvb.u>
#vapyhqr <fgqyvo.u>
#vapyhqr <nvb.u>
#vapyhqr <pglcr.u>
#vapyhqr <spagy.u>
$

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326178224&siteId=291194637