Using asynchronous I/O in Linux greatly improves Application Performance

Source: Internet
Author: User
Tags signal handler

The most common input/output (I/O) model in Linux is synchronous I/O. In this model, when a request is sent, the application blocks until the request is met. This is a good solution because no central processing unit (CPU) is required when calling an application waiting for the completion of I/O requests ). However, in some cases, I/O requests may need to overlap with other processes. POSIX asynchronous I/O (AIO) application interface (API) provides this function. In this article, we will introduce the overview of this API and learn how to use it.

AIO Introduction

Linux asynchronous I/O is a new enhancement provided by the Linux kernel. It is a standard feature of the 2.6 kernel, but we can also find it in the patch of the 2.4 kernel. The basic idea behind AIO is to allow a process to initiate many I/O operations without blocking or waiting for any operation to complete. The process can retrieve the results of an I/O operation later or when it receives a notification that the I/O operation is complete.

I/O model

Before going into the aio api, let's first explore the different I/O models that can be used on Linux. This is not a detailed description, but we will try to introduce some of the most common models to explain the differences between them and asynchronous I/O. Figure 1 shows the synchronous and asynchronous models, as well as the blocking and non-blocking Models.

Figure 1. Simple matrix of basic Linux I/O models


Each I/O model has its own usage mode, which has its own advantages for specific applications. This section briefly introduces them one by one.

Synchronous blocking I/O

Comparison between I/O-intensive and CPU-intensive processes

I/O-intensive processes perform more I/O operations than I/O operations. CPU-intensive processes perform more processing operations than I/O operations. In Linux 2.6, schedulers prefer I/O-intensive processes because they usually initiate an I/O operation and then block the process, this means that other work can be effectively staggered between the two.

The most common model is the synchronous blocking I/O model. In this model, the user space application executes a system call, which causes application blocking. This means that the application will be blocked until the system call is completed (data transmission is completed or an error occurs ). Calling an application is in a state that does not consume the CPU, but simply waits for a response. Therefore, this is very effective from the processing point of view.

Figure 2 shows the traditional blocking I/O model, which is also the most commonly used model in applications. Its behavior is very easy to understand, and its usage is very effective for typical applications. Before callingread
When the system is called, the application is blocked and context switches are performed on the kernel. Then, the read operation is triggered. When the response is returned (from the device we are reading from), the data is moved to the user space buffer. Then the application unblocks (read
Call back ).

Figure 2. Typical process of synchronous blocking I/O model


From the application perspective,read
The call takes a long time. In fact, when the Kernel performs read operations and other work, the application is indeed blocked.

Synchronous non-blocking I/O

A slightly less efficient variant of synchronous blocking I/O is synchronous non-blocking I/O. In this model, devices are opened in a non-blocking manner. This means that the I/O operation will not be completed immediately,read
The operation may return an error code, indicating that this command cannot be met immediately (EAGAIN
OrEWOULDBLOCK
), As shown in 3.

Figure 3. Typical process of synchronizing non-blocking I/O models


The non-blocking implementation is that the I/O command may not be immediately satisfied, and the application needs to call it many times to wait for the Operation to complete. This may be inefficient, because in many cases, when the kernel executes this command, the application must wait until the data is available or try to execute other work. As shown in figure 3, this method can introduce the latency of I/O operations, because the data becomes available in the kernel for user calls.read
There is a certain interval between the returned data, which leads to a reduction in the overall data throughput.

Asynchronous blocking I/O

Another blocking solution is non-blocking I/O with blocking notifications. In this model, non-blocking I/O is configured, and then blockselect
A system call is used to determine when an I/O descriptor is operated. Enableselect
The call is very interesting because it can be used to provide notifications for multiple descriptors, not just for one descriptor. For each prompt, we can request that this descriptor can write data, read data is available, and whether an error occurs.

Figure 4. Typical process of asynchronous blocking I/O model (select)


select
The main problem with calling is that it is not very efficient. Although this is a convenient model for asynchronous notifications, it is not recommended for high-performance I/O operations.

Asynchronous non-blocking I/O (AIO)

Finally, the asynchronous non-blocking I/O model is a model that processes overlapping I/O. Read requests are returned immediately.read
The request has been initiated successfully. When the read operation is completed in the background, the application then performs other processing operations. Whenread
When the response arrives, a signal is generated or a thread-based callback function is executed to complete the I/O processing process.

Figure 5. Typical asynchronous non-blocking I/O model process


The overlapping processing capabilities of computing operations and I/O processing to execute multiple I/O requests in a process utilizes the difference between processing speed and I/O speed. When one or more I/O requests are suspended, the CPU can execute other tasks. or, more commonly, when other I/O operations are initiated, the completed I/O operations are performed.

The next section will introduce this model in depth, explore the APIs used by this model, and then display several commands.



Back to Top

Motivation for asynchronous I/O

From the classification of the previous I/O model, we can see the motivation of AIO. This blocking model needs to block the application at the beginning of the I/O operation. This means that it is impossible to overlap processing and I/O operations at the same time. Synchronous non-blocking model allows overlapping processing and I/O operations, but this requires the application to check the status of I/O operations according to the reproducible rules. In this way, asynchronous non-blocking I/O operations are left, which allow overlapping processing and I/O operations, including notifications of completed I/O operations.

In addition to blocking,select
The functions provided by the function (asynchronously blocking I/O) are similar to those provided by AIO. However, it blocks notification events rather than I/O calls.



Back to Top

Introduction to AIO on Linux

This section explores the asynchronous I/O model of Linux to help us understand how to use this technology in applications.

In the traditional I/O model, there is an I/O channel identified by a unique handle. In UNIX, these handles are file descriptors (equivalent to files, pipelines, sockets, etc ). In blocking I/O, we initiate a transmission operation. When the transmission operation is completed or an error occurs, the system will return the result after calling the operation.

AIO on Linux

AIO first appeared in the kernel of Version 2.5. It is now a standard feature of the kernel of version 2.6.

In asynchronous non-blocking I/O, we can initiate multiple transmission operations at the same time. This requires that each transmission operation has a unique context so that we can determine which transmission operation is completed in their time zone. In AIO, This Isaiocb
(Aio I/O Control Block) structure. This structure contains all information about the transmission, including the user buffer prepared for the data. When an I/O (called completion) Notification is generated,aiocb
The structure is used to uniquely identify the completed I/O operations. This API shows how to use it.



Back to Top

AIO API

The API of the AIO interface is very simple, but it provides the necessary functions for data transmission and provides two different notification models. Table 1 provides the AIO interface functions. This section will be detailed later.

Table 1. AIO API

API functions
Description

aio_read

Request asynchronous read Operations

aio_error

Check the status of asynchronous requests

aio_return

Returns the status of the completed asynchronous request.

aio_write

Request asynchronous write operation

aio_suspend

Suspends the calling process until one or more asynchronous requests have been completed (or failed)

aio_cancel

Cancel asynchronous I/O requests

lio_listio

Initiate a series of I/O operations

Each API function usesaiocb
Structure start or check. This structure has many elements, but Listing 1 only provides the elements that need to be (or can be) used.

Listing 1. fields related to the aiocb Structure

  struct aiocb {  int aio_fildes;               // File Descriptor  int aio_lio_opcode;           // Valid only for lio_listio (r/w/nop)  volatile void *aio_buf;       // Data Buffer  size_t aio_nbytes;            // Number of Bytes in Data Buffer  struct sigevent aio_sigevent; // Notification Structure  /* Internal fields */  ...};

sigevent
The structure tells AIO what operations should be performed when I/O operations are completed. We will explore this structure in the AIO presentation. Now we will show how the various aio api functions work and how we should use them.

Aio_read

aio_read
Function requests perform asynchronous read operations on a valid file descriptor. This file descriptor can represent a file, socket, or even a pipeline.aio_read
The function prototype is as follows:

int aio_read( struct aiocb *aiocbp );

aio_read
The function returns immediately after the request is queued. If the execution is successful, the returned value is 0. If an error occurs, the returned value is-1 and the value is set.errno
.

To perform a read operation, the application mustaiocb
Structure. The following example shows how to fillaiocb
Request structure, and useaio_read
To execute asynchronous read requests (currently ignore notifications. It also showsaio_error
But we will explain it later.

Listing 2. Examples of asynchronous read operations using aio_read

  #include ...  int fd, ret;  struct aiocb my_aiocb;  fd = open( "file.txt", O_RDONLY );  if (fd < 0) perror("open");  /* Zero out the aiocb structure (recommended) */  bzero( (char *)&my_aiocb, sizeof(struct aiocb) );  /* Allocate a data buffer for the aiocb request */  my_aiocb.aio_buf = malloc(BUFSIZE+1);  if (!my_aiocb.aio_buf) perror("malloc");  /* Initialize the necessary fields in the aiocb */  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_iocb )) > 0) {    /* got ret bytes on the read */  } else {    /* read failed, consult errno */  }

In Listing 2, after opening the file from which data is to be read, we cleared it.aiocb
Structure, and then allocate a data buffer. And put the reference to this data bufferaio_buf
. Then, we willaio_nbytes
The size of the buffer. And setaio_offset
Set to 0 (the first offset in the file ). We willaio_fildes
Set to the file descriptor from which the data is read. Callaio_read
Read requests. We can then callaio_error
To confirmaio_read
. As long as the status isEINPROGRESS
, And waits until the status changes. The request may be successful or fail.

Use the AIO interface to compile the program

We canaio.h
Find the function prototype and other required symbols in the header file. When compiling a program using this interface, we must use the POSIX real-time extension Library (librt
).

Note that using this API is very similar to reading content from files using standard library functions. Besidesaio_read
In addition to some asynchronous features, another difference is the setting of the read operation offset. In the traditionalread
During the call, the offset is maintained in the context of the file descriptor. For each read operation, the offset must be updated so that subsequent read operations can address the next data block. This is impossible for asynchronous I/O operations, because we can execute many read requests at the same time, so we must specify an offset for each specific Read Request.

Aio_error

aio_error
The function is used to determine the Request status. The prototype is as follows:

int aio_error( struct aiocb *aiocbp );

This function returns the following content:

  • EINPROGRESS
    , Indicating that the request has not been completed
  • ECANCELLED
    The request is canceled by the application.
  • -1
    , Indicating an error occurred. For details about the error cause, refererrno

Aio_return

Another difference between asynchronous I/O and standard block I/O is that we cannot immediately access the returned status of this function because we are not blockingread
Call. In the standardread
In the call, the return status is provided when the function returns. However, in asynchronous I/O, we need to useaio_return
Function. The prototype of this function is as follows:

ssize_t aio_return( struct aiocb *aiocbp );

Onlyaio_error
This function is called only after the request is completed (successful or incorrect.aio_return
The return value is equivalent to that in synchronization.read
Orwrite
Returned value of the system call (the number of transmitted bytes. If an error occurs, the returned value is-1
).

Aio_write

aio_write
A function is used to request an asynchronous write operation. The function prototype is as follows:

int aio_write( struct aiocb *aiocbp );

aio_write
The function returns immediately, indicating that the request has been queued (the return value is0
, The return value is-1
And set it accordingly.errno
).

This correspondsread
System calls are similar, but there are some differences in behavior that need attention. Recall thatread
For calling, the offset to be used is very important. However,write
For example, this offset is only available when no value is set.O_APPEND
Option file context is very important. IfO_APPEND
The offset will be ignored, and the data will be appended to the end of the file. Otherwise,aio_offset
The offset of the data in the file to be written.

Aio_suspend

We can useaio_suspend
Function to suspend (or block) The Calling process until the asynchronous request is complete. At this time, a signal or other timeout operations are generated. The caller providesaiocb
Reference List, any completion will causeaio_suspend
.aio_suspend
The function prototype is as follows:

int aio_suspend( const struct aiocb *const cblist[],                  int n, const struct timespec *timeout );

aio_suspend
Is very simple to use. We want to provideaiocb
Reference List. If any one is complete, the call will return0
. Otherwise-1
, Indicating that an error has occurred. See listing 3.

Listing 3. Using the aio_suspend function to block asynchronous I/O

  struct aioct *cblist[MAX_LIST]/* Clear the list. */bzero( (char *)cblist, sizeof(cblist) );/* Load one or more references into the list */cblist[0] = &my_aiocb;ret = aio_read( &my_aiocb );ret = aio_suspend( cblist, MAX_LIST, NULL );

Note,aio_suspend
The second parameter of iscblist
Number of elements, ratheraiocb
Number of references.cblist
AnyNULL
All elements areaio_suspend
Ignore.

Ifaio_suspend
Provides timeout, And the timeout does occur, then it will return-1
,errno
Will containEAGAIN
.

Aio_cancel

aio_cancel
The function allows us to cancel one or all I/O requests executed on a file descriptor. The prototype is as follows:

int aio_cancel( int fd, struct aiocb *aiocbp );

To cancel a request, we need to provide the file descriptor andaiocb
Reference. If the request is successfully canceled, the function returnsAIO_CANCELED
. If the request is complete, this function returnsAIO_NOTCANCELED
.

To cancel all requests for a given file descriptor, we need to provide the file descriptor and a pairaiocbp
OfNULL
Reference. If all requests are canceled, this function returnsAIO_CANCELED
; If at least one request is not canceled, this function returnsAIO_NOT_CANCELED
If no request can be canceled, this function returnsAIO_ALLDONE
. We can then useaio_error
To verify each AIO request. If the request has been canceledaio_error
Will return-1
Anderrno
Will be setECANCELED
.

Lio_listio

Finally, Aio provides a method to uselio_listio
API functions initiate multiple transfers at the same time. This function is very important, because it means that we can start a large number of I/O operations in a system call (one kernel context switch. From the performance perspective, this is very important, so it is worth some time to explore.lio_listio
The API function is prototype as follows:

int lio_listio( int mode, struct aiocb *list[], int nent,                   struct sigevent *sig );

mode
The parameter can beLIO_WAIT
OrLIO_NOWAIT
.LIO_WAIT
This call will be blocked until all I/O operations are completed. After the operation is queued,LIO_NOWAIT
Will return.list
Isaiocb
Reference List, the maximum number of elements isnent
Defined. Note:list
Can beNULL
,lio_listio
Will ignore it.sigevent
Reference defines the method that generates signals when all I/O operations are completed.

Forlio_listio
And the traditionalread
Orwrite
Requests differ slightly in the actions that must be specified, as shown in Listing 4.

Listing 4. Using the lio_listio function to initiate a series of requests

  struct aiocb aiocb1, aiocb2;struct aiocb *list[MAX_LIST];.../* Prepare the first aiocb */aiocb1.aio_fildes = fd;aiocb1.aio_buf = malloc( BUFSIZE+1 );aiocb1.aio_nbytes = BUFSIZE;aiocb1.aio_offset = next_offset;aiocb1.aio_lio_opcode = LIO_READ;...bzero( (char *)list, sizeof(list) );list[0] = &aiocb1;list[1] = &aiocb2;ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );

For read operations,aio_lio_opcode
The field value isLIO_READ
. For write operations, we need to useLIO_WRITE
,LIO_NOP
It is also effective for not performing operations.



Back to Top

AIO notification

Now we have read the available AIO functions. This section describes how to use asynchronous notifications in depth. We will explore the asynchronous function notification mechanism through signal and function callback.

Asynchronous notification using signals

Using signals for inter-process communication (IPC) is a traditional mechanism in Unix. AIO can also support this mechanism. In this example, the application needs to define a signal processing program, which will be called when a specified signal is generated. The application then configures an asynchronous request to generate a signal when the request is complete. As part of the signal context, specificaiocb
The request is provided to record multiple possible requests. Listing 5 shows this notification method.

Listing 5. Using signals as AIO request notifications

  void setup_io( ... ){  int fd;  struct sigaction sig_act;  struct aiocb my_aiocb;  ...  /* Set up the signal handler */  sigemptyset(&sig_act.sa_mask);  sig_act.sa_flags = SA_SIGINFO;  sig_act.sa_sigaction = aio_completion_handler;  /* Set up the AIO request */  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;  /* Link the AIO request with the Signal Handler */  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;  /* Map the Signal to the Signal Handler */  ret = sigaction( SIGIO, &sig_act, NULL );  ...  ret = aio_read( &my_aiocb );}void aio_completion_handler( int signo, siginfo_t *info, void *context ){  struct aiocb *req;  /* Ensure it's our signal */  if (info->si_signo == SIGIO) {    req = (struct aiocb *)info->si_value.sival_ptr;    /* Did the request complete? */    if (aio_error( req ) == 0) {      /* Request completed successfully, get the return status */      ret = aio_return( req );    }  }  return;}

In listing 5aio_completion_handler
Set the signal handler in the function to captureSIGIO
Signal. Then initializeaio_sigevent
Structure generationSIGIO
Signal to send a notification (this is throughsigev_notify
InSIGEV_SIGNAL
). When the read operation is complete, the signal processing program starts fromsi_value
Structure Extractionaiocb
And check the error status and return status to determine whether the I/O operation is complete.

For performance, this handler is also ideal for continuing I/O operations by requesting the next asynchronous transmission. In this way, when a data transmission is completed, we can start the next data transmission operation immediately.

Asynchronous notification using callback Functions

Another notification method is the system callback function. This mechanism does not generate a signal for the notification, but calls a function of the user space to implement the notification function. Insigevent
Theaiocb
To uniquely identify the specific request being completed. See Listing 6.

Listing 6. Use thread callback notification for AIO requests

  void setup_io( ... ){  int fd;  struct aiocb my_aiocb;  ...  /* Set up the AIO request */  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;  /* Link the AIO request with a thread callback */  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;  ...  ret = aio_read( &my_aiocb );}void aio_completion_handler( sigval_t sigval ){  struct aiocb *req;  req = (struct aiocb *)sigval.sival_ptr;  /* Did the request complete? */  if (aio_error( req ) == 0) {    /* Request completed successfully, get the return status */    ret = aio_return( req );  }  return;}

In Listing 6aiocb
After the request, we useSIGEV_THREAD
A thread callback function is requested as a notification method. Then we will specify a specific notification handler and load the context to be transmitted to the handler (in this case, it isaiocb
Request your own reference ). In this processing program, we simply referencesigval
Pointer and use the AIO function to verify that the request has been completed.



Back to Top

System Optimization for AIO

The proc file system contains two virtual files that can be used to optimize the performance of asynchronous I/O:

  • The/proc/sys/fs/AIO-Nr file provides the current number of System-range asynchronous I/O requests.
  • The/proc/sys/fs/AIO-max-Nr file is the maximum number of concurrent requests allowed. The maximum number is usually 64 KB, which is sufficient for most applications.



Back to Top

Conclusion

Using asynchronous I/O can help us build applications with faster I/O speeds and higher efficiency. If our applications can overlap processing with I/O operations, then AIO can help us build applications that can more efficiently use available CPU resources. Although this I/O model is different from the traditional blocking mode used in most Linux applications, the asynchronous notification model is very simple in concept and can simplify our design.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.