Click the aboveblue text to follow us
In embedded Linux, asynchronous I/O (AIO) is a powerful I/O model that allows processes to continue executing other tasks while waiting for I/O operations to complete.
Compared to I/O multiplexing mechanisms (such as select and poll), asynchronous I/O uses a signal notification mechanism, allowing processes to avoid repeatedly querying the I/O status. Instead, the kernel actively sends signals (such as SIGIO) to notify the process of changes in I/O status.
1
Differences Between I/O Multiplexing and Asynchronous I/O
I/O Multiplexing (select/poll/epoll)
In multiplexing mechanisms, processes use system calls like select, poll, or epoll to actively query the status of file descriptors, waiting for a file descriptor to become readable or writable.
Although these methods can effectively handle events from multiple file descriptors, they are essentially still synchronous I/O, as the process is blocked while querying.
Asynchronous I/O
In asynchronous I/O, processes register signal handler functions and set the asynchronous flag on file descriptors. When I/O operations are ready, the kernel automatically sends a signal (usually SIGIO) to the process, eliminating the need for active polling.
While waiting for I/O, the process can continue executing other tasks and perform the corresponding I/O operations upon receiving the signal.
2
Steps to Implement Asynchronous I/O
In Linux, using asynchronous I/O typically requires the signal mechanism.
The specific steps are as follows:
- Set the file descriptor to non-blocking mode
- Set the asynchronous flag on the file descriptor
- Specify the process that will receive asynchronous I/O events
- Register a signal handler for the SIGIO signal
2.1 Set Non-blocking Mode
First, the file descriptor needs to be set to non-blocking mode (O_NONBLOCK flag) to ensure that the current process is not blocked during I/O operations.
This can be achieved using the fcntl system call:
int fd = open("example.txt", O_RDWR | O_NONBLOCK);if (fd < 0) { perror("open"); return -1;}
2.2 Set Asynchronous I/O Flag (O_ASYNC)
Next, the asynchronous I/O flag (O_ASYNC) needs to be set for the file descriptor. This flag notifies the kernel to send a SIGIO signal to the receiving process when I/O operations on the file descriptor are ready.
This operation must also be completed using the fcntl system call:
int flags = fcntl(fd, F_GETFL); // Get current flagsflags |= O_ASYNC; // Add O_ASYNC flagfcntl(fd, F_SETFL, flags); // Set new flags
Note: The open function cannot directly enable asynchronous I/O by specifying the O_ASYNC flag; it must be set using fcntl.
2.3 Set the Receiving Process for Asynchronous I/O Events
By default, the process that created the file descriptor is the one that receives asynchronous I/O events.
If you need to delegate I/O events to another process, you can use the fcntl function to set the “owner” of the file descriptor, specifying which process should receive the notification signals for I/O events:
fcntl(fd, F_SETOWN, getpid()); // Set the current process as the receiver of asynchronous I/O events
2.4 Register the SIGIO Signal Handler
Finally, register a signal handler for the SIGIO signal. You can use the signal or sigaction function to register a handler for the SIGIO signal.
When I/O operations are ready on the file descriptor, the kernel sends a SIGIO signal to the receiving process, triggering the execution of the signal handler.
Here is an example of registering the signal handler:
void sigio_handler(int signum) { // Perform I/O operation char buffer[1024]; ssize_t bytes_read = read(fd, buffer, sizeof(buffer)); if (bytes_read > 0) { buffer[bytes_read] = '\0'; printf("Asynchronously read data: %s\n", buffer); }}int main() { // Register SIGIO signal handler signal(SIGIO, sigio_handler); // Set the file descriptor to non-blocking and asynchronous mode int fd = open("example.txt", O_RDWR | O_NONBLOCK); if (fd < 0) { perror("open"); return 1; } // Enable asynchronous I/O int flags = fcntl(fd, F_GETFL); flags |= O_ASYNC; fcntl(fd, F_SETFL, flags); // Set the receiving process for asynchronous I/O events fcntl(fd, F_SETOWN, getpid()); // The main program can perform other tasks while (1) { printf("Main process executing other tasks...\n"); sleep(1); } close(fd); return 0;}
Notes:
- In the sigio_handler function, the read function is used to read data from the file descriptor.
- In the main program, the process can continue executing other tasks without being blocked. When I/O operations are ready, a SIGIO signal will be automatically triggered, calling the signal handler.
3
Function and Considerations of the O_ASYNC Flag
The O_ASYNC flag is used to enable asynchronous I/O events for the file descriptor, allowing it to send a SIGIO signal to the specified process when I/O operations are executable.
Note the following points:
- Cannot set O_ASYNC directly through open. It must be set using fcntl to enable the asynchronous flag for the file descriptor.
- The receiving process for asynchronous I/O events must be the owner of the file descriptor. If another process needs to receive the SIGIO signal, the process ID must be specified using F_SETOWN.
- Safety of Signal Handlers: Signal handlers should be quick and simple, avoiding blocking I/O operations or complex logic, as this may affect the real-time performance of the main program.
4
Application Scenarios for Asynchronous I/O
- Network Programming: In server-side applications that need to handle a large number of connections, asynchronous I/O can prevent process blocking, making it suitable for high-concurrency scenarios.
- Serial Communication: In embedded systems, serial communication is a common method. Asynchronous I/O allows processes to perform other tasks simultaneously without being blocked by serial I/O.
- Sensor Data Acquisition: In real-time data acquisition systems, asynchronous I/O ensures the timeliness of data collection, allowing processes to continue executing other tasks while waiting for data to be ready, improving CPU utilization.
5
Defects and Optimizations of Asynchronous I/O
Limitations of the Default Asynchronous I/O Signal SIGIO
By default, asynchronous I/O uses the non-real-time signal SIGIO as a notification signal. Since SIGIO is a non-real-time signal, it does not support a signal queuing mechanism, which may lead to multiple notifications being lost.
For example:
- When the signal handler is processing one SIGIO signal, subsequent SIGIO signals may be blocked or ignored.
- The SIGIO signal can only be triggered again after the signal handler has finished executing, and some signal notifications may be lost.
This defect can lead to the process being unable to respond to I/O events in a timely manner, affecting the real-time performance of applications.
Inability to Know the Specific Event Type
In the default asynchronous I/O signal handler, the process cannot directly obtain the specific event type on the file descriptor (such as readable, writable, error, etc.), and usually can only blindly attempt to perform I/O operations.
To address this, we can utilize real-time signals to provide more precise event information and use the extended fields of the siginfo_t structure to identify event types.
6
Optimization Method: Use Real-Time Signals to Replace Default Signals
By replacing SIGIO with a real-time signal (such as SIGRTMIN), we can implement a signal queuing mechanism, ensuring that multiple I/O event notifications are not lost. The steps are as follows:
6.1 Set Real-Time Signal as Asynchronous I/O Notification Signal
Use the fcntl function’s F_SETSIG command to replace the default signal SIGIO with a real-time signal (such as SIGRTMIN):
fcntl(fd, F_SETSIG, SIGRTMIN); // Set SIGRTMIN as the asynchronous I/O notification signal for fd
If the parameter is set to 0, it will revert to the default SIGIO signal.
6.2 Use sigaction to Register Signal Handler
Use the sigaction function instead of the signal function to support more advanced signal handling methods.
By setting the SA_SIGINFO flag for sa_flags, we can use the extended parameter siginfo_t in the signal handler to obtain more event information:
void sigio_handler(int signo, siginfo_t *info, void *context) { if (info->si_signo == SIGRTMIN) { // Confirm the signal is SIGRTMIN int fd = info->si_fd; // Get the file descriptor where the event occurred int event = info->si_code; // Get the event type // Handle based on event type if (event == POLL_IN) { // Data is readable // Perform read operation } else if (event == POLL_OUT) { // Data is writable // Perform write operation } else if (event == POLL_ERR) { // I/O error // Error handling } }}int main() { struct sigaction sa; sa.sa_flags = SA_SIGINFO; // Use sa_sigaction handler sa.sa_sigaction = sigio_handler; // Register signal handler sigaction(SIGRTMIN, &sa, NULL); // Bind SIGRTMIN signal // Set asynchronous I/O flags for the file descriptor int fd = open("example.txt", O_RDWR | O_NONBLOCK); int flags = fcntl(fd, F_GETFL); flags |= O_ASYNC; fcntl(fd, F_SETFL, flags); // Set the receiving process and signal for asynchronous I/O events fcntl(fd, F_SETOWN, getpid()); fcntl(fd, F_SETSIG, SIGRTMIN); // Specify real-time signal SIGRTMIN // Main program continues executing while (1) { sleep(1); } close(fd); return 0;}
6.3 Use Extended Fields of siginfo_t to Obtain Event Details
In the signal handler for real-time signals, the si_code and si_band fields of the siginfo_t structure can help identify the type of event that occurred, as follows:
- si_fd: The file descriptor where the event occurred.
- si_code: Event type (such as POLL_IN, POLL_OUT, etc.), which can confirm the status of the file descriptor.
- si_band: Event bitmask, consistent with the revents field of the poll() function, used for further confirmation of event details.
For example:
With these extended fields, the signal handler can execute different I/O operations more flexibly, avoiding blind calls to read or write operations, thus further enhancing performance and reliability.
Optimizing asynchronous I/O with real-time signals can effectively address notification loss and insufficient event type issues, making asynchronous I/O more stable and reliable in scenarios with many file descriptors and high concurrency.
Combined with the extended information from siginfo_t, the signal handler can respond more accurately to I/O events.
In application development, one can consider using asynchronous I/O or epoll to build an efficient I/O processing framework based on factors such as the number of file descriptors, real-time requirements, and CPU resource usage.
Clickto read the original text for more exciting content~