The final system call in this tutorial has the power to decrease the CPU usage of your programs very significantly in the situations where it is applicable.
Consider the following scenario. Suppose you wish to write a program which may take input from a terminal keyboard and which also may take input from the read end of a named pipe. If it can be guaranteed that the input from these two sources will arrive in a strictly alternating fashion so that first one then the other may be read, then producing code to achieve this is a simple matter. However, if (as is more likely) the input from the two sources is free to arrive in any order, then there is a problem because both of these input devices will block on read() if no data is ready when the call takes place. This means that a read() from the keyboard could suspend the process if no input is ready. Even though there may be plenty of input arriving from the pipe, the process will be unable to do anything about it.
The obvious solution to this problem is to use O~NONBLOCK on the two input devices, so that they both return immediately on read(), even if no input characters are ready. You can then write your code repeatedly to poll the two devices in turn, dealing with inputs from each as they occur.
This solution will work. However, repeated polling in this fashion does place a continuous load on the CPU, with no effective work being done except when input characters arrive.
The select() system call provides the perfect solution to the problem because it allows you to specify that you want your process to suspend itself while at the same time getting the kernel to monitor any desired collection of file descriptors looking for any activity. As soon as activity is spotted on any of the file descriptors being monitored, the select() call will return saying which file descriptor is ready to use.
This allows multiple randomly ordered inputs to a process to be sorted out without the heavy CPU overhead of polling for the inputs yourself. The prototype for the select() call is:
#include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *rdfds, fd_set *wrfds, fd_set *exfds, struct timeval *timeout);
where rdfds, wrfds and exfds are the set of read, write and exception handling file descriptors, respectively, that are to be monitored by select(), nfds is the highest numbered file descriptor that needs to be checked and the timeout parameter can cause select() to return early even if no file descriptors are ready.
The fd_sets pointed to by rdfds, wrfds and exfds are modified by the call to show which file descriptors are ready and the select() call returns a total of the number of ready file descriptors.
In order to simplify the setting, resetting and testing of appropriate file descriptor bits within an fd_set a set of macros is defined as follows:
FD_SET(fd, setptr) | set fd bit pointed to by setptr; |
FD_CLR(fd, setptr) | clear fd bit pointed to by setptr; |
FD_ZERO (setptr) | clear all bits pointed to by setptr; |
FD_ISSET(fd, setptr) | test fd bit pointed to by setptr. |
These macro definitions are made available by including the header file <types.h> in your code.