Introduction to Linux Asynchronous I/O (AIO) and Its API
This article explains Linux asynchronous I/O (AIO), compares it with synchronous and non‑blocking I/O models, describes the underlying aiocb structure, and provides detailed examples of the AIO API functions such as aio_read, aio_write, aio_error, aio_return, aio_suspend, aio_cancel and lio_listio.
Linux asynchronous I/O (AIO) is a relatively new enhancement in the Linux kernel, standard since the 2.6 series and also available via patches for the 2.4 kernel. The basic idea behind AIO is to allow a process to issue many I/O operations without blocking or waiting for any of them to complete; the process can later retrieve the results when notified.
Before diving into the AIO API, it is useful to understand the various I/O models available on Linux. The most common models are synchronous blocking I/O, synchronous non‑blocking I/O, asynchronous blocking I/O (using select ), and asynchronous non‑blocking I/O (AIO). Figures illustrate the typical flow of each model.
Synchronous Blocking I/O – The application issues a system call (e.g., read ) and blocks until the call completes. This model is simple and efficient for many applications.
Synchronous Non‑Blocking I/O – The device is opened in non‑blocking mode; a read may return EAGAIN or EWOULDBLOCK if the operation cannot be satisfied immediately, requiring the application to poll or retry.
Asynchronous Blocking I/O – Uses non‑blocking I/O together with select to be notified when a descriptor is ready. While convenient, select is not optimal for high‑performance I/O.
Asynchronous Non‑Blocking I/O (AIO) – Allows multiple I/O requests to be issued simultaneously; each request has a unique context (the aiocb structure) so the kernel can identify which operation completes. Completion can be signaled via signals or thread callbacks.
The motivation for AIO is to avoid the blocking behavior of traditional models, enabling overlap of computation and I/O and improving CPU utilization.
Linux’s AIO became a standard feature in the 2.6 kernel. Each asynchronous request is represented by an aiocb (AIO I/O Control Block) containing fields such as aio_fildes , aio_buf , aio_nbytes , and a sigevent structure for notification.
AIO API Overview
The core AIO functions are listed in Table 1 and include:
aio_read – request an asynchronous read.
aio_write – request an asynchronous write.
aio_error – check the status of a request.
aio_return – obtain the return value after completion.
aio_suspend – block until one or more requests finish.
aio_cancel – cancel pending requests.
lio_listio – submit a list of I/O operations in a single system call.
Example: Using aio_read
#include <aio.h>
int fd, ret;
struct aiocb my_aiocb;
fd = open("file.txt", O_RDONLY);
if (fd < 0) perror("open");
bzero((char *)&my_aiocb, sizeof(struct aiocb));
my_aiocb.aio_buf = malloc(BUFSIZE+1);
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_aiocb)) > 0) {
/* got ret bytes on the read */
} else {
/* read failed, consult errno */
}After initializing the aiocb , the program calls aio_read , then polls aio_error until the request is no longer in progress, and finally retrieves the number of bytes read with aio_return .
Compiling AIO Programs
Programs using the AIO interface must be linked with the POSIX realtime library ( librt ).
Other API Functions
aio_error returns EINPROGRESS , ECANCELLED , or -1 (with errno set) to indicate the request state.
aio_return should be called only after aio_error indicates completion; it returns the same value that a synchronous read or write would.
aio_write works like aio_read but for writes; the offset field matters unless the file was opened with O_APPEND .
aio_suspend blocks the calling process until any request in a provided list completes. The second argument is the number of elements in the list, not the number of references.
aio_cancel can cancel a specific request or all requests for a file descriptor, returning AIO_CANCELED , AIO_NOTCANCELED , or AIO_ALLDONE as appropriate.
lio_listio can launch multiple I/O operations in a single system call, using LIO_WAIT or LIO_NOWAIT modes.
Asynchronous Notification Methods
Two common notification mechanisms are signals and thread callbacks. With signals, the application registers a handler for SIGIO and sets aio_sigevent.sigev_notify = SIGEV_SIGNAL . With thread callbacks, SIGEV_THREAD is used and a user‑defined function is invoked upon completion.
Signal‑Based Notification Example
void setup_io(...){
int fd; struct sigaction sig_act; struct aiocb my_aiocb;
sigemptyset(&sig_act.sa_mask);
sig_act.sa_flags = SA_SIGINFO;
sig_act.sa_sigaction = aio_completion_handler;
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;
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;
sigaction(SIGIO, &sig_act, NULL);
aio_read(&my_aiocb);
}
void aio_completion_handler(int signo, siginfo_t *info, void *context){
struct aiocb *req = (struct aiocb *)info->si_value.sival_ptr;
if (info->si_signo == SIGIO && aio_error(req) == 0) {
int ret = aio_return(req);
/* handle completed read */
}
}Thread‑Callback Notification Example
void setup_io(...){
int fd; struct aiocb my_aiocb;
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;
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;
aio_read(&my_aiocb);
}
void aio_completion_handler(sigval_t sigval){
struct aiocb *req = (struct aiocb *)sigval.sival_ptr;
if (aio_error(req) == 0) {
int ret = aio_return(req);
/* handle completed read */
}
}System‑Level Optimizations
The /proc filesystem provides two virtual files for tuning AIO performance: /proc/sys/fs/aio-nr shows the current number of asynchronous I/O requests, and /proc/sys/fs/aio-max-nr defines the maximum allowed concurrent requests (typically 64 KB).
Conclusion
Using asynchronous I/O can help build faster, more efficient applications by overlapping computation with I/O. Although the model differs from the traditional blocking I/O used by most Linux programs, its notification mechanisms are conceptually simple and can simplify design while improving CPU utilization.
Art of Distributed System Architecture Design
Introductions to large-scale distributed system architectures; insights and knowledge sharing on large-scale internet system architecture; front-end web architecture overviews; practical tips and experiences with PHP, JavaScript, Erlang, C/C++ and other languages in large-scale internet system development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.