List I/O
System calls are expensive. Every time you cross the boundary between kernel and userspace, you have to save the state of the userspace stack, switch CPU modes, load the kernel state, and then do the same thing in reverse on the way up. You can save some of this overhead when performing AIO operations by using the lio_listio(2) call, which allows you to batch up to AIO_LISTIO_MAX operations in a single system call. As an added bonus, this approach makes it easier for the underlying operating system to re-order the requests for faster disk accesses.
Unlike the aio_* functions, lio_listio isn’t used exclusively for asynchronous I/O; it can also be used to dispatch a number of read and write requests to the kernel at the same time, like a more generalized form of readv(2)/writev(2). Since a number of AIO operations can be started at the same time, it’s important to specify which operation is required for each control block. This is done by setting the aio_lio_opcode filed to LIO_READ, LIO_WRITE, or LIO_NOP—for reading, writing, or ignoring, respectively.
The following example,aio3.c, shows how to use the synchronous form of lio_listio(2):
#include <aio.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> struct aiocb * cb[2]; int main(void) { struct sigaction action; //Create a buffer to store the read data char * foo = calloc(1,20); FILE * file = fopen("bar", "r+"); //Allocate space for the aio control blocks cb[0] = calloc(1,sizeof(struct aiocb)); cb[1] = calloc(1,sizeof(struct aiocb)); //Somewhere to store the result cb[0]->aio_buf = foo; cb[1]->aio_buf = foo + 10; //The file to read from cb[0]->aio_fildes = fileno(file); cb[1]->aio_fildes = fileno(file); //The number of bytes to read, and the offset cb[0]->aio_nbytes = 10; cb[1]->aio_nbytes = 10; cb[0]->aio_offset = 0; cb[1]->aio_offset = 10; //Specify that these are read operations cb[0]->aio_lio_opcode = LIO_READ; cb[1]->aio_lio_opcode = LIO_READ; lio_listio(LIO_WAIT, cb, 2, NULL); }
The first argument of the function instructs it whether to operate synchronously or asynchronously. If we set it to LIO_NOWAIT, then the call will return immediately, and the control blocks will act as if submitted to aio_read(2) or aio_write(2). If required, a single signal can be sent on completion of all of the code. This is specified by passing a struct sigevent as the last argument to lio_listio(2). This is created in exactly the same way as for an aio_sigevent field as described earlier.