The system calls the back-end Implementation of select and poll, and uses these two system calls to query whether the device can read or write, or whether it is in a certain state. For device drivers, we often need to tell the application about the status of the device. That is to say, we often need to tell the application whether data is ready. Linux does not handle this problem well in windows, and its message processing mechanism is not perfect. Generally, applications can only use methods such as read, write, and IOCTL to call the driver, if the driver does not have data, the process will be blocked; otherwise, the corresponding data will be returned. However, if an application serves multiple hardware devices at the same time, it cannot be suspended by one device. This is implemented in asynchronous mode. If data is returned and no data is returned, 0 or-1 is returned. Then, the application constantly needs to read the data to operate on the hardware. In this way, the application will read each file at intervals and then judge whether there is data. If not, it will continue to sleep. In this way, the program structure is not clear, and repeated sleep will reduce the system efficiency.
Functions of the select () function
The call to select () will be blocked until the specified file descriptor is ready to execute I/O, or the time specified by the optional parameter timeout has passed. The monitored file descriptors are divided into three types: Set, each corresponding to waiting for different events. The file descriptors listed in readfds are monitored for data available for reading (if the read operation is complete, it will not block ). The file descriptors listed in writefds are monitored to determine whether the write operation is complete without blocking. Finally, the file descriptors listed in ipvtfds are monitored for exceptions or uncontrolled data availability (these statuses are only applied to sockets ). The three types of set can be null. In this case, select () does not monitor this type of event. When select () is returned successfully, each set is modified so that it only contains the file descriptor for preparing I/O. Because the length of the fd_set type varies on different platforms, a set of standard macro definitions should be used to process such variables:
The basic Select Interface is very simple:
Int select (INT NFDs, fd_set * readset, fd_set * writeset,
Fd_set * effectset, struct timeval * timeout );
NFDs: number of file descriptors to be checked. The value should be the largest among the three fd_sets, rather than the total number of actual file descriptors.
Readset: a set of file descriptors used to check readability.
Writeset: a set of file descriptors used to check the writability.
Effectset: The file descriptor used to check the unexpected state.
Timeout: The NULL pointer represents an infinite wait; otherwise, it is a pointer to the timeval structure, representing
Long wait time. (If both TV _sec and TV _usec are equal to 0, the file descriptor State will not be affected, but the function will not be suspended.) The function will return the total number of corresponding operation file descriptors for the response operation, in addition, all three groups of data are modified at the appropriate position, and only some of the response operations are not modified. Then we should use the fd_isset macro to find the returned file descriptor group. The first parameter n is equal to the value of the maximum file descriptor in all sets plus 1. Therefore, the Select () caller checks which file descriptor has the maximum value, and adds this value to 1 and passes it to the first parameter.
The timeout parameter is a pointer to the timeval struct. The timeval is defined as follows:
# Include <sys/time. h>
Struct timeval {
Long TV _sec;/* seconds */
Long TV _usec;/* 10e-6 second */
};
If this parameter is not null, select () will return after TV _sec seconds and TV _usec microseconds even if no file descriptor is ready for I/O. When select () is returned, the status of the timeout parameter is undefined in different systems. Therefore, the timeout and file descriptor set must be reinitialized before each select () call. In fact, the current version of Linux automatically modifies the timeout parameter and sets its value to the remaining time. Therefore, if the timeout value is set to 5 seconds and then 3 seconds before the file descriptor is ready, TV _sec will change to 2 when the select () call returns. If both values in timeout are set to 0, the call to select () will return immediately. All pending events will be reported, but no subsequent events will be waited. File descriptor set is not directly operated. It is generally managed using several helper macros. This allows UNIX systems to implement the file descriptor set in their preferred way. However, most systems simply implement a set array. Fd_zero removes all file descriptors from the specified set. You should call select () before each call.
Fd_set writefds;
Fd_zero (& writefds );
Fd_set adds a file descriptor to the specified set. fd_clr removes a file descriptor from the specified set:
Fd_set (FD, & writefds);/* Add 'fd 'to the Set */
Fd_clr (FD, & writefds);/* oops, remove 'fd 'from theset */
Well-designed code should never use fd_clr, And it is rarely used in actual situations.
Fd_isset tests whether a file descriptor specifies a part of the set. If the file descriptor is set, a non-zero integer is returned. If not, 0 is returned. Fd_isset is used after select () is called to return data. It is used to test whether the specified file descriptor has the relevant action ready:
If (fd_isset (FD, & readfds ))
Because the file descriptor set is created statically, they impose a limit on the maximum number of file descriptors. The value of the maximum file descriptor that can be put into the set is specified by fd_setsize. In Linux, the value is 1024. Later in this chapter, we will also see derivatives of this restriction.
Return Value and error code
When select () succeeds, the number of file descriptors that prepare I/O is returned, including all three sets. If timeout is provided, the returned value may be 0. If an error occurs,-1 is returned, and errno is set to one of the following values:
Ebadf
An invalid file descriptor is provided to a set.
Eintr
A signal is captured while waiting, and a call can be initiated again.
Einval
The parameter n is a negative number, or the specified timeout is invalid.
Enomem
Insufficient memory to complete the request.
Functions of Poll ()
# Include <sys/poll. h>
Int poll (struct pollfd * FDS, unsignedint NFDs, int timeout );
Unlike select (), Poll () does not use an inefficient set of three bit-based file descriptors. Instead, it uses a separate structure pollfd array that points the FDS pointer to this group. The pollfd struct is defined as follows:
# Include <sys/poll. h>
Struct pollfd {
Int FD;/* file descriptor */
Short events;/* Requested events towatch */
Short revents;/* returned eventswitnessed */
};
Each pollfd struct specifies a monitored file descriptor. It can transmit multiple structs to instruct poll () to monitor multiple file descriptors. The events field of each struct is the event mask that monitors the file descriptor, which is set by the user. The revents field is the event mask of the file descriptor operation result. The kernel sets this field when calling the response. Any event requested in the events domain may be returned in the revents domain. Valid events are as follows:
Pollin
Data is readable.
Pollrdnorm
Common Data is readable.
Pollrdband
Readable data is preferred.
Pollpri
There is urgent data readable.
Pollout
Writing data does not cause blocking.
Pollwrnorm
Writing common data does not cause blocking.
Pollwrband
Writing priority data does not cause blocking.
Pollmsg
The sigpoll message is available.
In addition, the revents domain may return the following events:
Poller
The specified file descriptor is incorrect.
Pollhup
The specified file descriptor suspension event.
Pollnval
The specified file descriptor is invalid.
These events are meaningless in the events domain because they are always returned from revents when appropriate. Poll () is different from select (). You do not need to explicitly request exception reports. Pollin | pollpri is equivalent to the read event of select (), and pollout | pollwrband is equivalent to the write event of select. Pollin is equivalent to pollrdnorm | pollrdband, while pollout is equivalent to pollwrnorm.
For example, to monitor whether a file descriptor is readable and writable, we can set events to Pollin | pollout. When poll returns, we can check the flag in revents, which corresponds to the events structure of the file descriptor request. If the Pollin event is set, the file descriptor can be read without blocking. If pollout is set, the file descriptor can be written without blocking. These flags are not mutually exclusive: they may be set at the same time, indicating that the read and write operations of the file descriptor will return normally without blocking.
The timeout parameter specifies the number of milliseconds to wait. Poll returns no matter whether I/O is ready or not. If the value of timeout is negative, the infinite timeout is indicated. If the value of timeout is 0, the poll call returns immediately and lists the file descriptors for preparing I/O, but does not wait for other events. In this case, Poll () is returned as soon as it is elected. When the return value and error code are successful, Poll () returns the number of file descriptors whose revents field is not 0. If no event occurs before the timeout, Poll () returns 0; when a failure occurs, Poll () returns-1 and sets errno to one of the following values:
Ebadf
The specified file descriptor in one or more struct is invalid.
Efault
The FDS Pointer Points to an address that exceeds the address space of the process.
Eintr
A signal is generated before the request event, and the call can be initiated again.
Einval
The NFDs parameter exceeds the plimit_nofile value.
Enomem
The request cannot be completed because the available memory is insufficient.
Similar to the select principle, poll has no significant performance difference, but select has a limit on the number of file descriptors monitored. Poll is a system call. Its kernel entry function is sys_poll. sys_poll calls do_sys_poll directly without any processing. The execution process of do_sys_poll can be divided into three parts:
1. Copy the input pollfd array to the kernel space. Because the copy operation is related to the array length, this is an O (n) operation, in do_sys_poll, the code in this step includes the part starting from the function to calling do_poll.
2. query the status of the device corresponding to each file descriptor. If the device is not ready, add an item to the device's waiting queue and continue querying the status of the next device. If no device is ready after all the devices are queried, the current process needs to be suspended until the device is ready or times out. The pending operation is performed by calling schedule_timeout. After the device is ready, the process is notified to continue running. Then, all devices are traversed again to find the ready device. This step traverses all devices twice, and the time complexity is O (n), which does not include the waiting time. The related code is in the do_poll function.
3. Transmit the obtained data to the user space and perform the following operations, such as releasing the memory and detaching the waiting queue, the time complexity of operations such as copying data to a user space and detaching a waiting queue is also O (n). The specific code includes the part that ends after do_poll is called in the do_sys_poll function.
Functions of epoll ()
Epoll is an enhanced version of select/poll for Multiplexing I/O interfaces in Linux. It can significantly reduce the CPU usage of the system when a large number of concurrent connections are only active, because it does not reuse the file descriptor set to deliver results, it forces developers to re-Prepare the file descriptor set to be listened before each wait event. Another reason is that when obtaining the event, it does not need to traverse the entire listener descriptor set, as long as it traverses the descriptor set that is asynchronously awakened by kernel Io events and is added to the ready queue. In addition to the select/poll Io event level trigger (Level
In addition to triggered, edge triggered is also provided, which makes it possible for the user space program to cache the IO status, reduce epoll_wait/epoll_pwait calls, and improve application efficiency.
The epoll interface is very simple. There are three functions in total:
Intepoll_create (INT size); Create an epoll handle, which is used to tell the kernel the total number of this listener. This parameter is different from the first parameter in select () and returns the value of FD + 1 for the maximum listener. Note that after the epoll handle is created, it occupies an FD value. In Linux, If you view/proc/process ID/FD /, you can see this FD, so you must call close () to close it after epoll is used. Otherwise, the FD may be exhausted.
Intepoll_ctl (INT epfd, int op, int FD, struct epoll_event * event); epoll event registration function, which is different from select () it is to tell the kernel what type of event to listen for when listening for events, but to register the event type to listen for here. The first parameter is the returned value of epoll_create (). The second parameter represents an action and is represented by three macros:
Epoll_ctl_add: register a new FD to epfd;
Epoll_ctl_mod: modifies the listener events of the registered FD;
Epoll_ctl_del: delete an FD from epfd;
The third parameter is the FD to be monitored, and the fourth parameter is to tell the kernel what to listen for. The structepoll_event structure is as follows:
Struct epoll_event {
_ Uint32_t events;/* epoll events */
Epoll_data_t data;/* User Data variable */
};
Events can be a collection of the following macros:
Epollin: indicates that the corresponding file descriptor can be read (including the normal shutdown of the Peer socket );
Epollout: indicates that the corresponding file descriptor can be written;
Epollpri: indicates that the corresponding file descriptor has an urgent readable data (Here it should indicate that out-of-band data has arrived );
Epollerr: indicates that the corresponding file descriptor is incorrect;
Epollhup: indicates that the corresponding file descriptor is hung up;
Epollet: Set epoll to edge triggered mode, which is relative to level triggered.
Epolloneshot: only listens for an event once. After listening for this event, if you want to continue listening for this socket, you need to add this socket to the epoll queue again.
Int epoll_wait (INT epfd, struct epoll_event * events, int maxevents, int timeout );
Wait for event generation, similar to the select () call. The events parameter is used to get the event set from the kernel. maxevents tells us how big the kernel events is. The value of maxevents cannot be greater than the size when epoll_create () is created. The timeout parameter is the timeout time (in milliseconds, 0 will be returned immediately,-1 will be uncertain, or it is said to be permanently blocked ). This function returns the number of events to be processed. If 0 is returned, the Operation has timed out.
Epoll advantages
(1) A process is supported to open a large number of socket Descriptors (FD)
The most intolerable thing about the SELECT statement is that the FD opened by a process has certain limitations, which are set by fd_setsize. The default value is 2048. For im servers that need to support tens of thousands of connections, there are obviously too few. At this time you
First, you can choose to modify this macro and then re-compile the kernel. However, it is also pointed out that this will bring about a reduction in network efficiency,
Second, you can select a multi-process solution (the traditional Apache solution). However, although the cost of creating a process on Linux is relatively small, it cannot be ignored, in addition, data synchronization between processes is far less efficient than inter-thread synchronization, so it is not a perfect solution.
Epoll does not have this limit. The FD limit supported by epoll is the maximum number of files that can be opened. This number is generally greater than 2048. For example, the size of a machine with 1 GB of memory is about 0.1 million. You can check the number of machines with CAT/proc/sys/fs/file-max. Generally, this number has a great relationship with the system memory.
(2) Io efficiency does not linearly decrease as the number of FD increases
Another critical weakness of traditional select/poll is that when you have a large set of sockets, but due to network latency, only some of the sockets at any time are "active, however, each select/poll call will linearly scan all sets, resulting in a linear decline in efficiency.
Epoll does not have this problem. It only operates on "active" sockets-this is because epoll is implemented based on the callback function on each FD in kernel implementation. Then, only the "active" socket will take the initiative to call the callback function, other idle status socket will not, in this regard, epoll implements a "pseudo" AIO, this is because the driver is in the OS kernel. In some benchmarks, if all the sockets are basically active-for example, in a high-speed LAN environment, epoll is not more efficient than select/poll. On the contrary, if epoll_ctl is used too much, the efficiency is also slightly lower. However, once idle is used
Connections simulates the WAN environment, and epoll is far more efficient than select/poll.
(3) Use MMAP to accelerate message transmission between kernel and user space
This actually involves the specific implementation of epoll. Both select, poll, and epoll require the kernel to notify users of FD messages. It is important to avoid unnecessary memory copies, epoll is implemented through the same memory of the user space MMAP kernel. If you want me to focus on epoll from the 2.5 kernel, you will not forget the manual MMAP step.
(4) kernel fine-tuning
This is not an advantage of epoll, but an advantage of the entire Linux platform. Maybe you can doubt the Linux platform, but you cannot avoid the Linux platform giving you the ability to fine-tune the kernel. For example, if the Kernel TCP/IP protocol stack uses a memory pool to manage the sk_buff structure, you can dynamically adjust the memory pool (skb_head_pool) during runtime) the size --- through echoxxxx>/proc/sys/NET/CORE/hot_list_length. For example, the listen function's 2nd parameters (TCP completes the length of the packet queue after three handshakes) can also be dynamically adjusted based on the memory size of your platform. Even in a special system with a large number of data packets but the size of each data packet itself is small, try the latest napi NIC driver architecture.
Example:
# Include <stdio. h>
# Include <stdlib. h>
# Include <fcntl. h>
# Include <sys/select. h>
# Include <poll. h>
# Include <sys/time. h>
# Include <unistd. h>
# Include <sys/STAT. h>
Int main ()
{
Fd_set rfds, wfds;
Int FD, result;
Char Buf [10];
Struct pollfd FDS [2];
If (FD = open ("tempselect", o_creat | o_wronly, s_irusr | s_iwusr) <0)
Printf ("opentempselect error ");
Fd_zero (& rfds );
Fd_zero (& wfds );
Fd_set (FD, & rfds );
Fd_set (stdin_fileno, & wfds );
If (result = select (FD + 1, & rfds, & wfds, null, null) =-1)
Perror ("selecterror ");
Else if (result = 0)
Printf ("No fdready \ n ");
Else
{
Printf ("% d FD (s) Ready \ n", result );
If (fd_isset (FD, & rfds ))
Printf ("FD isready for read \ n ");
If (read (FD, Buf, 10) <0)
Perror ("readfd error ");
If (fd_isset (stdin_fileno, & wfds ))
Printf ("stdinis ready for write \ n ");
}
FDS [0]. FD = FD;
FDS [0]. Events = Pollin;
FDS [1]. FD = stdin_fileno;
FDS [1]. Events = pollout;
If (result = poll (FDS, 2,-1) =-1)
Perror ("pollerror ");
Else
{
Printf ("% d FD (s) Ready \ n", result );
If (FDS [0]. revents = Pollin)
Printf ("FD isready for read \ n ");
If (read (FD, Buf, 10) <0)
Perror ("readfd error ");
If (FDS [1]. revents = pollout)
Printf ("stdinis ready for write \ n ");
}
Exit (0 );
}
Result:
2 FD (s) Ready
FD is ready for read
Read FD error: Bad file descriptor
Stdin is ready for write
2 FD (s) Ready
FD is ready for read
Read FD error: Bad file descriptor
Stdin is ready for write