login
v2
v1

jmoiron.net

select(2)

why I bothered

Why write anything at all or comment on the select(2) function call when there are already man pages about it and lots of unix documentation out there? Whenever I find myself not completely understanding something in a decent ammount of time, I find it helpful to write it down. I don't know if I need help, either, but some day I know I'll forget how to use select(2) and what each one of its arguments does, and I'm sure that a little elaboration on the manpage itself might help people to learn what the hell is going on.

synopsys

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>

int

select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
    struct timeval *timeout);

FD_SET(fd, &fdset);

FD_CLR(fd, &fdset);

FD_ISSET(fd, &fdset);

FD_ZERO(&fdset);

this part of the man page shouldn't confuse anyone; they are functions (in this case only one) that have to do with the system call select and a few macro's that manipulate all of the fd_set arguments. The macro's are explained in all of their confusing detail, as are all of the arguments, in the description. description

Select() examines the I/O descriptor sets whose addresses are passed in readfds, writefds, and exceptfds to see if some of their descriptors are ready for reading, are ready for writing, or have an exceptional condi- tion pending, respectively. The only exceptional condition detectable is out-of-band data received on a socket. The first nfds descriptors are checked in each set; i.e., the descriptors from 0 through nfds-1 in the descriptor sets are examined. On return, select() replaces the given descriptor sets with subsets consisting of those descriptors that are ready for the requested operation. Select() returns the total number of ready descriptors in all the sets.

It doesn't make sense to include this paragraph before you know what a descriptor set is, but for some reason they do it just in case you do. Anyway, read on and then read the first paragraph again, it should make more sense.

The descriptor sets are stored as bit fields in arrays of integers.  The
following macros are provided for manipulating such descriptor sets:
FD_ZERO(&fdset) initializes a descriptor set fdset to the null set.
FD_SET(fd, &fdset) includes a particular descriptor fd in
fdset.  FD_CLR(fd, &fdset) removes fd from
fdset.  FD_ISSET(fd, &fdset) is non-
zero if fd is a member of fdset, zero otherwise.  The behavior of these
macros is undefined if a descriptor value is less than zero or greater
than or equal to FD_SETSIZE, which is normally at least equal to the max-
imum number of descriptors supported by the system.

Before calling select(2), make sure that any file descriptors you want to include in the read set first have a call to the FD_ZERO macro.. that is, if you want select to block on file-descriptor "tcp_socket_1" until its readable (either a new connection is incoming or theres data there to be read), make sure that first you do FD_ZERO(tcp_socket_1, &read_set), or whatever fd_set instances you have declared. The macro's provide basic functionality into reading and determining just what the hell is going on with select, but the notion of file descriptor sets isn't really explained with clarity. I think this is the main reason people get confused over select.

Whenever you run the FD_ZERO macro, you clear out the fd_set instance, lets call it rd_set. When you call FD_SET(filedescriptor, rd_set), that file descriptor is added to the file descriptor set, whichever one it may be (write, read, except). You can add multiple descriptors to the set, and provided that the first argument (ndfs, which stands for number of file descriptors) is higher than all of the descriptors in the read set, select will actually bother to check them. Take a server, for example, since writing one is the reason I bothered with this: if you want to be able to accept different connections from different people at the same time but on different ports, you can add your two listened and opened sockets to your rd_set and then run a blocking select call. Its actually intuitive once you realize what fd_set's are and how they work. Understanding how the select system call changes the fd_set variables, although not necessarily needed because of "FD_ISSET", is a little more complicated; but you must keep in mind that your calls to select, since they call each fd_set with a &, will change each of these variables and they will most likely have to be re-initialized, or else select will not block again.

The completely and utterly self explanatory section on providing select with NULL arguments to disregard unblocking on those conditions follows. I do not mention timeout because for my application, its not necessary. If I ever need to use it, I'll update this.

If timeout is a non-nil pointer, it specifies the maximum interval to wait for the selection to complete. System activity can lengthen the interval by an indeterminate amount.

If timeout is a nil pointer, the select blocks indefinitely.

To effect a poll, the timeout argument should be non-nil, pointing to a zero-valued timeval structure.

Any of readfds, writefds, and exceptfds may be given as nil pointers if no descriptors are of interest.