介绍
One basic concept of Linux (actually Unix) is the rule that everything in Unix/Linux is a file. Each process has a table of file descriptors that point to files, sockets, devices and other operating system objects.
Typical system that works with many IO sources has an initializaion phase and then enter some kind of standby mode – wait for any client to send request and response it.
Simple solution is to create a thread (or process) for each client , block on read until a request is sent and write a response. This is working ok with a small amount of clients but if we want to scale it to hundred of clients, creating a thread for each client is a bad idea.
File Descriptor:
A file descriptor is a number that uniquely identifies an open file in a computer’s operating system. It describes a data resource, and how that resource may be accessed.
When a program asks to open a file — or another data resource, like a network socket — the kernel:
- Grants access.
- Creates an entry in the global file table.
- Provides the software with the location of that entry.
The descriptor is identified by a unique non-negative integer, such as 0, 12, or 567. At least one file descriptor exists for every open file on the system.
File descriptors were first used in Unix, and are used by modern operating systems including Linux, macOS, and BSD. In Microsoft Windows, file descriptors are known as file handles.
When a process makes a successful request to open a file, the kernel returns a file descriptor which points to an entry in the kernel’s global file table. The file table entry contains information such as the inode of the file, byte offset, and the access restrictions for that data stream (read-only, write-only, etc.).
On a Unix-like operating system, the first three file descriptors, by default, are STDIN (standard input), STDOUT (standard output), and STDERR (standard error).
Name | File descriptor | Description | Abbreviation |
---|---|---|---|
Standard input | 0 | The default data stream for input, for example in a command pipeline. In the terminal, this defaults to keyboard input from the user. | stdin |
Standard output | 1 | The default data stream for output, for example when a command prints text. In the terminal, this defaults to the user’s screen. | stdout |
Standard error | 2 | The default data stream for output that relates to an error occurring. In the terminal, this defaults to the user’s screen. | stderr |
1 | find / -name '*something*' 2>/dev/null |
The errors sent to /dev/null, and are not displayed.
IO Multiplexing
The solution is to use a kernel mechanism for polling over a set of file descriptors. There are 3 options you can use in Linux:
- select(2)
- poll(2)
- epoll
All the above methods serve the same idea, create a set of file descriptors , tell the kernel what would you like to do with each file descriptor (read, write, ..) and use one thread to block on one function call until at least one file descriptor requested operation available
Select System Call
The select( )
system call provides a mechanism for implementing synchronous multiplexing I/O
sh int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
A call to select( )
will block until the given file descriptors are ready to perform I/O, or until an optionally specified timeout has elapsed
The watched file descriptors are broken into three sets:
- File descriptors listed in the readfds set are watched to see if data is available for reading.
- File descriptors listed in the writefds set are watched to see if a write operation will complete without blocking.
- File descriptors in the exceptfds set are watched to see if an exception has occurred, or if out-of-band data is available (these states apply only to sockets).
Select – summary:
- We need to build each set before each call
- The function check any bit up to the higher number – O(n)
- We need to iterate over the file descriptors to check if it exists on the set returned from select
- The main advantage of select is the fact that it is very portable – every unix like OS has it
Poll system call
Unlike select()
, with its inefficient three bitmask-based sets of file descriptors, poll( )
employs a single array of nfds pollfd structures. the prototype is simpler:
1 | int poll (struct pollfd *fds, unsigned int nfds, int timeout); |
参考
https://devarea.com/linux-io-multiplexing-select-vs-poll-vs-epoll/#.Yxb5AHZBxD8
File descriptor:
https://www.computerhope.com/jargon/f/file-descriptor.htm#:~:text=A%20file%20descriptor%20is%20a,Grants%20access.
epoll python example:
http://scotdoyle.com/python-epoll-howto.html
select, poll, epoll:https://jvns.ca/blog/2017/06/03/async-io-on-linux--select--poll--and-epoll/