http://www.ibm.com/developerworks/linux/library/l-async/
Before digging into the AIO API, let's explore the different I/O models that are available under Linux. This isn't intended as an exhaustive review, but rather aims to cover the most common models to illustrate their differences from asynchronous I/O. Figure 1 shows synchronous and asynchronous models, as well as blocking and non-blocking models.
Figure 1. Simplified matrix of basic Linux I/O models
Each of these I/O models has usage patterns that are advantageous for particular applications. This section briefly explores each one.
One of the most common models is the synchronous blocking I/O model. In this model, the user-space application performs a system call that results in the application blocking. This means that the application blocks until the system call is complete (data transferred or error). The calling application is in a state where it consumes no CPU and simply awaits the response, so it is efficient from a processing perspective.
Figure 2 illustrates the traditional blocking I/O model, which is also the most common model used in applications today. Its behaviors are well understood, and its usage is efficient for typical applications. When the read
system call is invoked, the application blocks and the context switches to the kernel. The read is then initiated, and when the response returns (from the device from which you're reading), the data is moved to the user-space buffer. Then the application is unblocked (and the read
call returns).
Figure 2. Typical flow of the synchronous blocking I/O model
From the application's perspective, the read
call spans a long duration. But, in fact, the application is actually blocked while the read is multiplexed with other work in the kernel.
2 Synchronous non-blocking I/O
A less efficient variant of synchronous blocking is synchronous non-blocking I/O. In this model, a device is opened as non-blocking. This means that instead of completing an I/O immediately, a read
may return an error code indicating that the command could not be immediately satisfied (EAGAIN
or EWOULDBLOCK
), as shown in Figure 3.
Figure 3. Typical flow of the synchronous non-blocking I/O model
The implication of non-blocking is that an I/O command may not be satisfied immediately, requiring that the application make numerous calls to await completion. This can be extremely inefficient because in many cases the application must busy-wait until the data is available or attempt to do other work while the command is performed in the kernel. As also shown in Figure 3, this method can introduce latency in the I/O because any gap between the data becoming available in the kernel and the user calling read
to return it can reduce the overall data throughput.
Another blocking paradigm is non-blocking I/O with blocking notifications. In this model, non-blocking I/O is configured, and then the blocking select
system call is used to determine when there's any activity for an I/O descriptor. What makes the select
call interesting is that it can be used to provide notification for not just one descriptor, but many. For each descriptor, you can request notification of the descriptor's ability to write data, availability of read data, and also whether an error has occurred.
Figure 4. Typical flow of the asynchronous blocking I/O model (select)
The primary issue with the select
call is that it's not very efficient. While it's a convenient model for asynchronous notification, its use for high-performance I/O is not advised.
4 Asynchronous non-blocking I/O (AIO)
Finally, the asynchronous non-blocking I/O model is one of overlapping processing with I/O. The read request returns immediately, indicating that the read
was successfully initiated. The application can then perform other processing while the background read operation completes. When the read
response arrives, a signal or a thread-based callback can be generated to complete the I/O transaction.
Figure 5. Typical flow of the asynchronous non-blocking I/O model
The ability to overlap computation and I/O processing in a single process for potentially multiple I/O requests exploits the gap between processing speed and I/O speed. While one or more slow I/O requests are pending, the CPU can perform other tasks or, more commonly, operate on already completed I/Os while other I/Os are initiated.
The next section examines this model further, explores the API, and then demonstrates a number of the commands.