Article count:922 Read by:3074353

Account Entry

Thoroughly understand IO multiplexing

Latest update time:2023-12-06
    Reads:



Before explaining this technology, we need to preview files and file descriptors.


what is a file

Programmers using I/O ultimately cannot escape the concept of files.
In the Linux world, a file is a very simple concept. As programmers, we only need to understand it as a sequence of N bytes :
b1, b2, b3, b4, ....... bN
In fact, all I/O devices are abstracted into the concept of files. Everything is File. Disks, network data, terminals, and even inter-process communication tool pipes are all treated as files.
All I/O operations can also be implemented through file reading and writing. This very elegant abstraction allows programmers to use a set of interfaces to perform I/O operations on all peripherals .
Commonly used I/O operation interfaces generally include the following categories:
  • open file, open
  • Change the read and write position, seek
  • File reading and writing, read, write
  • Close the file, close
Programmers can implement almost all I/O operations through these interfaces. This is the power of the concept of files.

file descriptor

Like disk data, we need to specify a buff to load the data, which is usually written like this:
read(buff);
But we have overlooked a key issue here, that is, although we have specified where to write data, where should we read the data from?
From the previous section, we know that we can achieve almost all I/O operations through the concept of files, so the missing protagonist here is the file .
So how do we generally use files?
If you go to a popular restaurant on weekends, you should have experienced it. Generally, there will be a queue at popular restaurants on weekends, and then the waiter will give you a queue number, and the waiter can find you through this number. The advantage here is that the waiter does not need to remember you. Who are you, what is your name, where are you from, what are your hobbies, do you protect the environment and care for small animals, etc. The key point here is that the waiter knows nothing about you, but he can still find you with a number .
Similarly, if we want to use files in the Linux world, we also need to use a number. According to the " incomprehension principle ", this number is called a file descriptor, file descriptors , which is famous in the Linux world. The reason is the same as above. The queue number is the same.
Therefore, the file description is just a number, but through this number we can operate an open file, this should be remembered.
With the file descriptor, the process can know nothing about the file , such as where the file is on the disk, how it is loaded into the memory, and how it is managed. All this information is handled by the operating system, and the process does not need to care. The system only needs to give the process a file descriptor.
So let's improve the above procedure:
int fd = open(file_name); // 获取文件描述符read(fd, buff);
How about it? It's very simple, isn't it?

What to do if there are too many file descriptors

After so much preparation, we finally come to the topic of high performance and high concurrency.
We know from the previous sections that all I/O operations can be performed through file-like concepts, which of course includes network communication.
If you have a web server, after the three-way handshake is successful, we will call accept to obtain a link. By calling this function, we will also get a file descriptor. Through this file descriptor, we can process the request sent by the client and process it. The results are sent back. In other words, we can communicate with the client through this descriptor.
// 通过accept获取客户端的文件描述符int conn_fd = accept(...);
The processing logic of the server is usually to read the client request data and then execute some specific logic:
if(read(conn_fd, request_buff) > 0) { do_something(request_buff);}
Isn't it very simple? However, the world is complex after all, and of course it is not that simple.
The next step is more complicated.
Since our topic is high concurrency, it is impossible for the server to communicate with only one client , but may communicate with thousands of clients at the same time. At this time, you need to deal with no longer as simple as one descriptor, but may have to deal with thousands of descriptors .
In order not to make the problem too complicated at the beginning, let's simplify it first, assuming that we only handle the requests of two clients at the same time.
Some students may say that this is not simple yet, so we can just write like this:
if(read(socket_fd1, buff) > 0) { // 处理第一个 do_something();}if(read(socket_fd2, buff) > 0) { // 处理第二个 do_something();
If there is no data to read at this time, the process will be blocked and suspended. At this time, we will not be able to process the second request, even if the data of the second request is already in place, which means that when processing a certain client Since the process is blocked, all other remaining clients must wait . This is obviously intolerable on a server that handles tens of thousands of clients at the same time.
If you are smart, you will definitely think of using multi-threading to open a thread for each client request, so that if one client is blocked, it will not affect the threads processing other clients. Note that since it is high concurrency, then we have to be successful. Will tens of thousands of requests open thousands of threads? Creating and destroying a large number of threads will seriously affect system performance.
So how to solve this problem?
The key point here is that we do not know in advance whether the I/O device corresponding to a file description is readable or writable. Performing I/O when the peripheral is unreadable or unwritable will only cause The process is blocked and suspended.
Therefore, to solve this problem elegantly, we must think about it from other perspectives.

Don't call me, I'll call you if necessary

Everyone must have received sales calls in their lives, and more than one. If you receive ten or eight sales calls in a day, your body will be exhausted.
The key point of this scenario is that the caller doesn't know if you want to buy something and can only ask you over and over again. Therefore, a better strategy is not to let them call you and write down their phone number. Call them if you need to so the salesperson doesn't bother you over and over again (although this isn't possible in real life).
In this example, you are like the kernel, the promoter is like the application, the phone number is like the file descriptor, and communicating with you by phone is like I/O.
Now you should understand that a better way to handle multiple file descriptors actually exists in cold calls.
Therefore, compared to the previous section where we actively asked the kernel through the I/O interface whether the peripherals corresponding to these file descriptors are ready, a better way is to throw these file descriptors of interest to Kernel, and told the kernel domineeringly: " I have 10,000 file descriptors here. You monitor them for me. When there are file descriptors that can be read and written, you tell me so that I can handle them easily ." Instead of weakly asking the kernel: "Can the first file descriptor be read and written? Can the second file descriptor be read and written? Can the third file descriptor be read and written?..."
In this way, the application changes from "busy" active to leisurely passive . Anyway, the kernel will notify me when the file description is readable and writable , so I don't need to be so diligent if I can be lazy.
This is a more efficient I/O processing mechanism. Now we can handle multiple I/Os at one time . Let’s give this mechanism a name. Let’s resort to the “ principle of not understanding ” again and call it multiple I/Os. Road multiplexing, this is I/O multiplexing.

I/O multiplexing, I/O multiplexing

The word multiplexing is actually mostly used in the field of communications. In order to make full use of communication lines, we hope to transmit multiple signals in one channel. If we want to transmit multiple signals in one channel, we need to combine the multiple signals into one channel. The device that combines into one signal is called a multiplexer. Obviously, the receiver needs to restore the original multiplex signal after receiving the combined signal. This device is called a demultiplexer, as shown in the figure:
Back to our topic.
The so-called I/O multiplexing refers to such a process:
1. We got a bunch of file descriptors (whether it is network-related, disk file-related, etc., any file descriptor will do)
2. Tell the kernel by calling a certain function : " Don't return to this function yet. You monitor these descriptors for me. You will return when there are I/O read and write operations in this pile of file descriptors. "
3. When the called function returns, we will know which file descriptors can perform I/O operations.
In other words, through I/O multiplexing we can process multiple I/Os at the same time . So what functions can be used for I/O multiplexing?
In the Linux world, there are three mechanisms that can be used for I/O multiplexing:
  • select
  • poll
  • epoll
Next, let’s introduce the awesome Three Musketeers of I/O Multiplexing.

I/O Multiplexing Three Musketeers

Essentially, select, poll, and epoll are all blocking I/O, which is what we often call synchronous I/O. The reason is that when calling these I/O multiplexing functions, if any of the file descriptors that need to be monitored are unavailable. If the file descriptor is readable or writable, the process will be blocked and suspended until the file descriptor is readable or writable.
1. select: fledgling
Under the I/O multiplexing mechanism of select, we need to tell select the file description set we want to monitor in the form of function parameters, and then select will copy these file descriptor sets to the kernel. We know that the data copy is There is performance loss, so in order to reduce the performance loss caused by this kind of data copy, the Linux kernel limits the size of the set and stipulates that the file description set monitored by the user cannot exceed 1024. At the same time, we can only know when select returns Some file descriptors can be read and written, but we don't know which one , so the programmer must traverse to find out which file descriptor can be read and written.
Therefore, in summary, select has the following characteristics:
  • There is a limit to the number of file descriptors I can look after, no more than 1024
  • The file descriptor given to me by the user needs to be copied to the kernel
  • I can only tell you that there are file descriptors that meet the requirements, but I don’t know which one. You can find them one by one by yourself (traverse)
Therefore, we can see that these features of the select mechanism are undoubtedly inefficient in scenarios where high-concurrency network servers often have tens of thousands or hundreds of thousands of concurrent connections.

2. poll: small success
Poll and select are very similar. The optimization of poll compared to select only solves the limitation that the number of file descriptors cannot exceed 1024. The performance of select and poll will decrease as the number of monitored file descriptions increases, so they are not suitable for high concurrency scenarios. .
3. epoll: Unique in the world
Among the three problems faced by select, the limit on the number of file descriptions has been solved in poll. What about the remaining two problems?
To solve the copy problem, the strategies used by epoll are individual defeat and shared memory .
In fact, the frequency of changes in the file descriptor set is relatively low. Select and poll frequently copy the entire set, and the kernel is almost annoyed to death. epoll is very considerate by introducing epoll_ctl to operate only those file descriptors that have changed. At the same time, epoll It has also become good friends with the kernel and shares the same memory. What is stored in this memory is a set of file descriptors that are readable or writable, thus reducing the copy overhead of the kernel and the program.
To solve the problem of needing to traverse the file descriptors to know which one is readable and writable, the strategy used by epoll is to "be a little brother".
Under the select and poll mechanisms, the process has to wait on each file descriptor in person . If any file description is readable or writable, the process will be awakened. However, after the process is awakened, it will be confused and not know which file descriptor it is. If the character is readable or writable, check it again from beginning to end.
But epoll is much more sensible and takes the initiative to find a way to act as a younger brother to stand up for his elder brother.
Under this mechanism, the process does not need to die in person. The process only needs to wait on epoll. Epoll replaces the process and waits on each file descriptor. When which file descriptor is readable or writable, epoll is told. Epoll uses a small Record it carefully and then wake up the elder brother: "Brother process, wake up quickly, I have written down all the file descriptors you want to process." In this way, after the process is awakened, there is no need to check it from beginning to end, because the epoll brother has already memorized it. Came down.
Therefore, we can see that under the mechanism of epoll, the strategy of "don't call me, I will call you if necessary" is actually used. The process does not need to bother asking each file descriptor over and over again. Instead, he turned over and became the master, "Which of your file descriptors is readable or writable and proactively reports it?" This mechanism is actually the famous event-driven, Event-driven, which is also the theme of our next article.
In fact, on the Linux platform, epoll is basically synonymous with high concurrency .

Recently, many friends have asked me for some essential information for programmers, so I dug out the treasures at the bottom of the box and shared them with everyone for free!


Scan the QR code of the poster to get it for free.



Latest articles about

 
EEWorld WeChat Subscription

 
EEWorld WeChat Service Number

 
AutoDevelopers

About Us Customer Service Contact Information Datasheet Sitemap LatestNews

Room 1530, Zhongguancun MOOC Times Building,Block B, 18 Zhongguancun Street, Haidian District,Beijing, China Tel:(010)82350740 Postcode:100190

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号