I/O reuse technology under Linux, and Select, Poll, and Epoll concurrency models

First of all, we need to make it clear : select, poll, and epoll are essentially synchronous I/O , because they are responsible for reading and writing after the read and write events are ready. This reading and writing process is blocked. Select, poll, and epoll are all I/O multiplexing mechanisms. They all monitor multiple descriptors through a mechanism (provided by the system). Once a descriptor is ready, the program can be notified to perform corresponding read and write operations.

So what is I/O multiplexing? I/O multiplexing is the ability of a process to notify the kernel in advance, so that once the kernel finds that one or more I/O conditions specified by the process are ready, it notifies the process.

There are five I/O models under Linux, namely blocking I/O , non-blocking I/ O , I/O multiplexing , signal-driven I/O , and asynchronous I/O .

        Blocking I/O , that is, the application calls the IO function, causing the program to block (the current process is suspended and suspended until the function returns), waiting for the data to be ready. If the data is not ready, the process will always be in a waiting state, only the data Returns when ready and returns to the process buffer or an error occurs. In network communication, when the socket function creates a socket, all sockets are blocked by default.
  
  Non-blocking I/O , the process repeatedly calls an IO function. If no data is ready, an error is returned, that is, it does not enter the blocking state, but continues to make function calls. Setting a SOCKET interface to non-blocking tells the kernel not to put the process to sleep when the requested I/O operation cannot be completed, but to return an error. In this way, our I/O operation function will continuously test whether the data is ready. If not, continue testing until the data is ready. During this continuous testing process, a large amount of CPU time will be occupied.
  
  I/O multiplexing is mainly realized through functions such as select and poll. Blocking on one of these two system calls instead of blocking on the real IO system call has no advantage over blocking IO; The key is to be able to monitor multiple IO ports at the same time (functions such as select and poll can be regarded as a kind of agent, and they replace the IO function for monitoring and waiting).
  
  Signal-driven I/O uses signals to allow the kernel to send a SIGIO signal to the process when the descriptor is ready. When the program runs to IO, the process continues to run without blocking. When the data is ready, the process will receive a SIGIO signal and can call the I/O operation function in the signal processing function to process the data.
  
  Asynchronous I/O tells the kernel to perform an operation in advance, and passes us after the kernel completes the entire operation. The process will not block while waiting for IO to be executed.
  
  Summary: Synchronous IO causes the process to block until the IO operation is completed.
     Asynchronous IO will not cause process blocking.
     IO reuse is blocked by selecting call first.

1、select

select monitors an array composed of multiple file descriptors (file descriptors) through system calls. When select() returns, the ready file descriptors in the array will be modified by the kernel (actually an integer). This allows the process to obtain these file descriptors for subsequent read and write operations. The select decoration monitors the entire array through traversal , and each traversal is linear . The select function allows a process to instruct the kernel to wait for one or more of multiple events to occur, and to wake it up only after one or more events occur or a specified period of time has elapsed. We call select to tell the kernel which descriptors (read, write, or exception conditions) it is interested in and how long to wait. The descriptors we are interested in are not limited to sockets, any descriptor can be tested using select.
 

select Disadvantages:

  • Every time select is called, the fd collection needs to be copied from user mode to kernel mode. This overhead will be very large when there are many fds.
  • There is a maximum limit on the number of fds that a single process can monitor, which defaults to 1024 on Linux (this limit can be increased by modifying the macro definition or recompiling the kernel)
  • And since the fd of select is placed in an array, and the entire array must be traversed linearly every time, when there are many fd, the overhead is also very high.

2、poll

The functions provided by the poll function are similar to select, but when processing stream devices, it can provide additional information, but there is no limit on the maximum number of connections (default 1024 on Linux) because it is based on linked list storage. Poll is the same as select except that it does not have the disadvantage of the maximum number of connections.

3、epoll

In linux2.6 (2.5.44 to be precise), the method is directly supported by the kernel. epoll solves the shortcomings of select and poll.

  • For the first shortcoming, epoll’s solution is to copy all fds into the kernel every time a new event is registered in epoll, instead of repeatedly copying while waiting, ensuring that each fd will only Copy 1 time.
  • Regarding the second shortcoming, epoll does not have this limitation. The upper limit of fd it supports is the maximum number of files that can be opened. The specific number can be viewed by cat /proc/sys/fs/file-max. Generally speaking, this number is related to the system memory. bigger.
  • For the third shortcoming, epoll's solution is not like select and poll that traverse all fds and poll all fd collections every time. Instead, when registering a new event, specify a callback function for each fd. When the device is ready When this callback function is called, the callback function will add the ready fd to a ready table. (So ​​epoll actually only needs to traverse the ready table).

epoll supports both horizontal triggering and edge triggering:

  • Level-triggered : As long as the conditions are met, an event is triggered (as long as there is data that has not been obtained, the kernel will continue to notify you). eg: In horizontal trigger mode, repeated calls epoll.poll()will repeatedly notify the event of interest until all data related to the event has been processed. (select, poll are horizontal triggers, and epoll is horizontal triggers by default)
  • Edge-triggered : An event is triggered every time the state changes. eg: In edge-triggered mode, epoll.poll() will only return one event after a read or write event occurs on the socket. The calling epoll.poll()program must process all data related to this event, and subsequent epoll.poll()calls will not be notified of this event.

The difference between select, poll and epoll:

The implementation of poll is very similar to that of select, except that the way to describe the fd set is different. Poll uses the pollfd structure instead of the fd_set structure of select. Everything else is similar. The solution to epoll is in the epoll_ctl function. Each time a new event is registered in the epoll handle (specify EPOLL_CTL_ADD in epoll_ctl), all fds will be copied into the kernel instead of repeated copies during epoll_wait. epoll ensures that each fd will only be copied once during the entire process.

The solution of epoll is not like select or poll, which adds current to the device waiting queue corresponding to fd every time. Instead, it only hangs current once during epoll_ctl (this time is essential) and specifies a callback function for each fd. , when the device is ready and wakes up the waiters on the waiting queue, this callback function will be called, and this callback function will add the ready fd to a ready linked list). The job of epoll_wait is actually to check whether there is a ready fd in the ready list (use schedule_timeout() to sleep for a while and judge the effect, which is similar to step 7 in the select implementation).

Regarding the third shortcoming of poll, epoll does not have this limitation. The upper limit of FD it supports is the maximum number of files that can be opened. This number is generally much larger than 2048.
Select and poll implementations need to continuously poll all fd collections until the device is ready. During this period, sleep and wake-up may have to alternate multiple times. In fact, epoll also needs to call epoll_wait to continuously poll the ready list. During this period, it may alternate between sleeping and waking up multiple times. However, when the device is ready, it calls the callback function, puts the ready fd into the ready list, and wakes up in epoll_wait to go to sleep. process. Although both sleep and alternate, select and poll need to traverse the entire fd collection when "awake", while epoll only needs to determine whether the ready list is empty when "awake", which saves a lot of CPU time. This is the performance improvement brought by the callback mechanism.

Each time select and poll are called, the fd collection must be copied from the user state to the kernel state once, and current must be placed in the device waiting queue once, while epoll only needs to be copied once, and current must be placed in the waiting queue only once. (At the beginning of epoll_wait, note that the waiting queue here is not a device waiting queue, but a waiting queue defined internally by epoll). This can also save a lot of expenses. The biggest advantage of epoll is that it will not reduce efficiency as the number of FDs increases. Polling is used in select. The data structure is similar to an array data structure, while epoll maintains a queue. You can directly check whether the queue is empty. That's it. epoll will only operate on "active" sockets - this is because in the kernel implementation, epoll is implemented based on the callback function on each fd. Then, only the "active" socket will actively call the callback function (add this handle to the queue), but other idle status handles will not. At this point, epoll implements a "pseudo" AIO. But if most of the I/O is "active" and the usage rate of each I/O port is high, the efficiency of epoll is not necessarily higher than that of select (it may be complicated to maintain the queue).
 

Guess you like

Origin blog.csdn.net/a154555/article/details/126940621