The process of an I/O operation

What is IO?

IO, the full English name is Input/Output, which translates to input/output . We usually hear a lot about disk IO and network IO. So what exactly is IO? Don't you feel confused? You seem to know roughly what it is, but you can't explain it clearly.

IO, that is, input/output , who is the input? Who is the output? If IO is separated from the main body, it will make people confused.

IO from a computer perspective

When we often talk about input and output, the more intuitive meaning is the input and output of the computer , and the computer is the main body . Do you still remember that when you were studying the principles of computer composition in college , there was a von Neumann structure , which divided a computer into five parts: arithmetic unit, controller, memory, input device, and output device.


Input devices are devices that input data and information into the computer. Keyboards and mice are all input devices; output devices are terminal devices of the computer hardware system and are used to receive and display computer data. Generally, monitors and printers are output devices.

For example, if you tap a few times on the mouse and keyboard, it will transmit your command data to the host. After the host performs calculations, it will output the returned data information to the monitor.

The mouse and monitor are just intuitive input and output. Going back to computer architecture, the process of data migration between the computer core and other devices is IO . For example, disk IO means reading data from the disk to the memory, which is considered an input. Correspondingly, writing the data in the memory to the disk is considered an output. This is the nature of IO.

Operating system IO

If we want to write the data in the memory to the disk, what will be the main body? The subject may be an application, such as a Java process (assuming a binary stream comes from the network, a Java process can write it to disk).

The operating system is responsible for computer resource management and process scheduling. The applications running on our computers actually need to go through the operating system to perform some special operations, such as reading and writing disk files, reading and writing memory, etc. Because these are relatively dangerous operations, they cannot be performed by applications and can only be left to the underlying operating system. In other words, if your application wants to write data to disk, it can only do so by calling the API opened by the operating system.

What is user space? What is kernel space?

Taking the 32-bit operating system as an example, it allocates 4G (2 to the 32nd power) of memory space to each process. The 4G accessible memory space is divided into two parts, one is user space and the other is kernel space. Kernel space is the area accessed by the operating system kernel and is a protected memory space, while user space is the memory area accessed by user applications.

Our application runs in user space, and there is no actual IO process. The real IO is performed by the operating system . That is, the IO operation of the application is divided into two actions: IO call and IO execution . IO calls are initiated by the process (the running state of the application), and IO execution is the work of the operating system kernel . The IO mentioned at this time is a trigger of the IO function of the operating system by the application program, that is, an IO call.

An IO process of the operating system

An IO operation initiated by the application consists of two phases:

IO call: The application process initiates a call to the operating system kernel .

IO execution: The operating system kernel completes the IO operation.

The operating system kernel completes IO operations and includes two processes:

Prepare data phase: The kernel waits for the I/O device to prepare data

Copy data phase: copy data from the kernel buffer to the user process buffer


In fact, IO is to transfer the internal data of the process to an external device, or to migrate the data of the external device to the inside of the process. External devices generally refer to hard drives and network cards for socket communication. A complete IO process includes the following steps:

The application process initiates an IO call request to the operating system

The operating system prepares the data and loads the data from the IO external device into the kernel buffer.

The operating system copies data, that is, copies the data in the kernel buffer to the user process buffer.

Blocking IO model

We already know what IO is, so what is blocking IO ?

Assume that the application process initiates an IO call , but if the kernel data is not ready , the application process will keep blocking and waiting until the kernel data is ready and copied from the kernel to user space before returning a success prompt. This IO operation is called blocking IO .


The more classic applications of blocking IO are blocking sockets and Java BIO .

The disadvantage of blocking IO is: if the kernel data is not ready, the user process will always be blocked, wasting performance . Non-blocking IO optimization can be used.

Non-blocking IO model

If the kernel data is not ready yet, you can return an error message to the user process first, so that it does not need to wait, but requests again through polling. This is non-blocking IO. The flow chart is as follows:


The process of non-blocking IO is as follows:

The application process initiates recvfrom to read data from the operating system kernel.

The operating system kernel data is not ready and the EWOULDBLOCK error code is returned immediately.

The application process polls the call and continues to initiate recvfrom to the operating system kernel to read data.

The operating system kernel data is ready and copied from the kernel buffer to user space.

Complete the call and return a success message.

Non-blocking IO model, referred to as NIO , Non-Blocking IO. Although it has greatly improved performance compared to blocking IO, it still has performance problems , that is, frequent polling , resulting in frequent system calls, which also consumes a large amount of CPU resources. You can consider the IO reuse model to solve this problem.

IO multiplexing model

Since NIO's invalid polling will cause CPU resource consumption, wouldn't it be good if we wait until the kernel data is ready and proactively notify the application process before making system calls?

Before that, let's review what a file descriptor fd (File Descriptor) is. It is a term in computer science and is formally a non-negative integer. When a program opens an existing file or creates a new file, the kernel returns a file descriptor to the process.

The core idea of ​​the IO reuse model: the system provides us with a type of functions (such as the select, poll, and epoll functions we have heard before and after), which can monitor the operations of multiple fds at the same time. If any one returns the kernel data when it is ready, the application process initiates the recvfrom system. transfer.

IO multiplexing selection

The application process can monitor multiple fds at the same time by calling the select function. Among the fds monitored by the select function, as long as any data status is ready, the select function will return to the readable state. At this time, the application process will initiate a recvfrom request . Read data.

In the non-blocking IO model (NIO), N (N>=1) polling system calls are required. However, with the IO multiplexing model of select, only one query is required, which greatly optimizes performance.

However, select has several disadvantages:

The maximum number of monitored IO connections is limited, generally 1024 on Linux systems.

After the select function returns, the ready descriptor fd is found by traversing fdset. (I only know that an I/O event occurred, but I don’t know which streams it is, so I traverse all streams )

Because there is a limit on the number of connections , poll was later proposed . Compared with select, poll solves the problem of connection limit . However, select, like poll, still needs to traverse the file descriptor to obtain the ready socket. If a large number of clients are connected at the same time, only a few may be in the ready state at a time. As the number of monitored descriptors increases, the efficiency will decrease linearly .

Therefore, the classic multiplexing model epoll was born.

IO multiplexing epoll

In order to solve the problems of select/poll, the multiplexing model epoll was born. It is implemented using event drive. The flow chart is as follows: epoll first registers an fd (file descriptor) through epoll_ctl(). Once it is ready based on a certain fd When, the kernel will use a callback mechanism to quickly activate this fd, and will be notified when the process calls epoll_wait(). The tricky operation of traversing file descriptors is removed here , and a mechanism of listening for event callbacks is used . This is the highlight of epoll.

Let’s summarize the differences between select, poll, and epoll.

selectpollepoll

The underlying data structure array linked list red-black tree and double linked list

Get the ready fd traversal traversal event callback

Event complexity O(n)O(n)O(1)

Maximum number of connections 1024 unlimited unlimited

fd data copy Every time you call select, you need to copy fd data from user space to kernel space. Every time you call poll, you need to copy fd data from user space to kernel space using memory mapping (mmap). There is no need to copy fd frequently from user space. Data to kernel space

epoll obviously optimizes the execution efficiency of IO, but it may still be blocked when the process calls epoll_wait(). Can Jiang Zi: You don’t need me to keep asking you if the data is ready. After I send the request, you can just notify me when your data is ready. This is how the signal-driven IO model was born .

Signal driven model of IO model

Signal-driven IO no longer uses active inquiry to confirm whether the data is ready, but sends a signal to the kernel (a SIGIO signal is established when calling sigaction), and then the application user process can do other things without blocking. When the kernel data is ready, the application process is notified through the SIGIO signal that the data is ready for readability. After the application user process receives the signal, it immediately calls recvfrom to read the data.

The signal-driven IO model returns immediately after the application process sends a signal and does not block the process. It already has the feel of an asynchronous operation. But if you look closely at the flow chart above, you will find that when the data is copied to the application buffer , the application process is still blocked. Looking back, whether it is BIO, NIO, or signal driver, it is blocked when data is copied from the kernel to the application buffer. Are there any optimization plans? AIO (true asynchronous IO)!

IO model asynchronous IO (AIO)

The BIO, NIO and signal drivers mentioned earlier are blocked when data is copied from the kernel to the application buffer, so they are not truly asynchronous. AIO realizes non-blocking of the entire IO process, that is, after the application process issues a system call, it returns immediately, but what is returned immediately is not the processing result, but something like a successful submission . When the kernel data is ready, copy the data to the user process buffer, and send a signal to notify the user process that the IO operation is completed.

The process is as follows:

The optimization idea of ​​asynchronous IO is very simple. You only need to send a request to the kernel to complete all operations of data status inquiry and data copy, without blocking and waiting for results. In daily development, there are business scenarios with similar ideas:

For example, if a batch transfer is initiated, but batch transfer processing is time-consuming, the backend can first notify the frontend that the transfer submission was successful, and then notify the frontend of the result after the result is processed.

Blocking, non-blocking, synchronous, asynchronous IO division

IO model

Blocking I/O model synchronous blocking

Non-blocking I/O model Synchronous non-blocking

I/O multiplexing model synchronous blocking

Signal-driven I/O model is synchronous and non-blocking

Asynchronous IO (AIO) model asynchronous non-blocking

A popular example to understand BIO, NIO, and AIO

Synchronous blocking (blocking-IO) referred to as BIO

Synchronous non-blocking (non-blocking-IO) referred to as NIO

Asynchronous-non-blocking-IO (asynchronous-non-blocking-IO) referred to as AIO

A classic life example:

Xiao Ming went to eat Coconut Chicken from Tongren Four Seasons. He queued there and waited for an hour before starting to eat hot pot. ( BIO )

Xiaohong also went to Tongren Four Seasons Coconut Chicken. She saw that the wait would be quite long, so she went to the shopping mall. Every time she went shopping, she would run back to see if it was her turn . So in the end she went shopping and ate coconut chicken. ( NIO )

Like Xiaohua, he went to eat Coconut Chicken. Since he was a senior member, the store manager said, you can just go to the mall and hang out. I will call you as soon as a seat becomes available . So Xiaohua didn't have to sit and wait, and he didn't have to run back every once in a while to see if he was waiting. In the end, he also had delicious coconut chicken ( AIO ).



Author: Ma Yunsheng
Link: https://www.jianshu.com/p/f3cc37e6b90d
 

Guess you like

Origin blog.csdn.net/m1234567q/article/details/132886593