Add Book to My BookshelfPurchase This Book Online

Chapter 6 - Special-Purpose File Operations

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

Managing Multiple File Descriptors
Sometimes a single program must be able to manage several file descriptors, acting immediately on any input received from them, and yet also performing other computations when no input is received. For example, consider a multi-player Star Trek game. While none of the players are typing, the program must draw the ships, planets, and so forth, and move them about on each player's screen. But when a player types a command (such as turn left), the program must immediately receive that input and act on it.
Doing something like this is difficult with the functions you have learned about so far, primarily because the read function blocks until input is available. This means that when the program issues a read call, it becomes “stuck” until the player types something—it cannot perform its other duties, such as updating the screen. Fortunately, most modern versions of the UNIX operating system provide a way to handle this task.
The select and poll functions provide a mechanism for a program to check on a group of file descriptors and learn when any of those descriptors are ready to provide input, ready to receive output, or have an exceptional condition pending on them. The select function is usually provided on BSD-based systems, while poll is usually provided on System V-based systems. SVR4 provides both select as a library emulation routine and poll as a system call.
The select Function
Although emulated with a library routine in SVR4, select is more frequently used than poll. The select function is called as follows:
    #include <sys/types.h>
    #include <sys/time.h>
    int select(int maxfd, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);
    void FD_SET(int fd, fd_set *fdset);
    void FD_CLR(int fd, fd_set *fdset);
    int FD_ISSET(int fd, fd_set *fdset);
    void FD_ZERO(fd_set *fdset);
 NoteIn HP-UX 10.0, the ANSI C function prototype is misdeclared as taking parameters of type int * instead of type fd_set *. This is a typographical error only; select still uses the fd_set type.
When called, select examines the file descriptor sets pointed to by readfds, writefds, and exceptfds to see if any of their file descriptors are ready for reading, ready for writing, or have an exceptional condition pending on them. Out-of-band data (see Chapter 14, Networking With Sockets) is the only exceptional condition. When select returns, it replaces the file descriptor sets with subsets containing those file descriptors that are ready for the requested operation.
Each file descriptor set is a bit field in which a non-zero bit indicates that the file descriptor of that number should be checked. The maxfd parameter indicates the highest-numbered bit that should be checked; the file descriptors from 0 to maxfd-1 are examined in each file descriptor set. (Much of the documentation on select calls this parameter nfds, implying that it is the number of file descriptors to check.) If a particular condition is not of interest, any of readfds, writefds, and exceptfds can be given as null pointers.
The FD_ZERO macro clears all the bits in a file descriptor set; always call this before setting any bits. The FD_SET and FD_CLR macros set and clear individual bits corresponding to file descriptors in a file descriptor set. The FD_ISSET macro returns non-zero if the bit corresponding to the file descriptor fd is set, and zero otherwise.
If timeout is not a null pointer, it specifies a maximum interval to wait for the requested operations to become ready. If timeout is given as a null pointer, then select blocks indefinitely (you can use this  to have the program “just sit there” until something happens). To effect a poll, in which the select call just checks all the file descriptors and returns their status, timeout should be a non-null pointer to a zero-valued struct timeval structure. (The struct timeval structure is discussed in Chapter 7, Time of Day Operations.)
When select returns, it usually returns a number greater than zero, indicating the number of ready file descriptors contained in the file descriptor sets. If the timeout expires with none of the file descriptors becoming ready, select returns 0. If an error occurs, select returns -1 and places an error code in the external variable errno.
Example 6-1 shows a program that reads from three terminal devices. Each time you type something on one of the terminals, the program reads it and prints it. If no one types anything on any of the devices within 10 seconds, the program prints a reminder to the user. When the string S-T-O-P is read from one of the terminals, the program exits.
Example 6-1:  select
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#define NTTYS       3           /* number of ttys to use     */
#define TIMEOUT     10          /* number of seconds to wait */
int     fds[NTTYS];             /* file descriptors          */
char    *fileNames[NTTYS];      /* file names                */
int     openFiles(char **);
void    readFiles(fd_set *);
int
main(int argc, char **argv)
{
    fd_set readfds;
    int i, n, maxfd;
    struct timeval tv;
    /*
     * Check that we have the right number of arguments.
     */
    if (argc != (NTTYS+1)) {
        fprintf(stderr, "You must supply %d tty names.\n", NTTYS);
        exit(1);
    }
    /*
     * Open the files.  The highest numbered file descriptor
     * (plus one) is returned in maxfd.
     */
    maxfd = openFiles(++argv);
    /*
     * Forever...
     */
    for (;;) {
        /*
         * Zero the bitmask.
         */
        FD_ZERO(&readfds);
        /*
         * Set bits in the bitmask.
         */
        for (i=0; i < NTTYS; i++)
            FD_SET(fds[i], &readfds);
        /*
         * Set up the timeout.
         */
        tv.tv_sec = TIMEOUT;
        tv.tv_usec = 0;
        /*
         * Wait for some input.
         */
        n = select(maxfd, (int *) &readfds, (int *) 0, (int *) 0, &tv);
        /*
         * See what happened.
         */
        switch (n) {
        case -1:            /* error           */
            perror("select");
            exit(1);
        case 0:             /* timeout         */
            printf("\nTimeout expired.  Type something!\n");
            break;
        default:            /* input available */
            readFiles(&readfds);
            break;
        }
    }
}
/*
* openFiles - open all the files, return the highest file descriptor.
*/
int
openFiles(char **files)
{
    int i, maxfd;
    maxfd = 0;
    /*
     * For each file...
     */
    for (i=0; i < NTTYS; i++) {
        /*
         * Open it.
         */
        if ((fds[i] = open(*files, O_RDONLY)) < 0) {
            perror(*files);
            exit(1);
        }
        /*
         * Make sure it's a tty.
         */
        if (!isatty(fds[i])) {
            fprintf(stderr, "All files must be tty devices.\n");
            exit(1);
        }
        /*
         * Save the name.
         */
        fileNames[i] = *files++;
        /*
         * Save the highest numbered fd.
         */
        if (fds[i] > maxfd)
            maxfd = fds[i];
    }
    return(maxfd + 1);
}
/*
* readFiles - read input from any files that have some.
*/
void
readFiles(fd_set *readfds)
{
    int i, n;
    char buf[BUFSIZ];
    /*
     * For each file...
     */
    for (i=0; i < NTTYS; i++) {
        /*
         * If it has some input available...
         */
        if (FD_ISSET(fds[i], readfds)) {
            /*
             * Read the data.
             */
            n = read(fds[i], buf, sizeof(buf));
            buf[n] = '\0';
            /*
             * Print it out.
             */
            printf("\nRead %d bytes from %s:\n", n, fileNames[i]);
            printf("\t%s\n", buf);
            /*
             * Is it telling us to stop?
             */
            if (strcmp(buf, "S-T-O-P\n") == 0)
                exit(0);
        }
    }
}
    % select /dev/pts/3 /dev/pts/4 /dev/pts/5
Running this program for yourself requires a bit of work to see how it works. It's best if you start up a window system such as X11 or OpenWindows, although you can also do it if you have access to several hard-wired terminals. To run the example, perform the following steps:
 1.Start up four terminal windows, or log in on four separate terminals.
 2.On each of the first three terminals, type tty. This command tells you the name of the terminal device file that you are using.
 3.On each of the first three terminals, type sleep 1000000. This allows the program to read from these terminals without competing for input with the shell process running on each terminal. When you finish with the demonstration, you can just interrupt this command.
 4.On the fourth terminal, type the select command followed by the device names of the other three terminals. Note that if you use the Korn shell, select is a special command to the shell, so use the command ./select to invoke the example program.
 5.Type something on each of the first three terminals and watch what the program prints on the fourth terminal. Then don't type anything on the terminals for 10 seconds and watch the program print its timeout message. Finally, type the string S-T-O-P on any one of the terminals to make the program exit.
The poll Function
The poll function is similar to select, except that it uses a structure of type struct pollfd for each file descriptor instead of file descriptor sets.
    #include <stropts.h>
    #include <poll.h>
    int poll(struct pollfd *fds, unsigned long nfds, int timeout);
The fds parameter points to an array of nfds structures of type struct pollfd, one for each file descriptor of interest. The structure contains three elements:
    struct pollfd {
        int   fd;
        short events;
        short revents;
    };
The fd element contains the file descriptor of interest. If fd is equal to -1, the structure is ignored; this allows particular descriptors to be turned “on” and “off” without rearranging the array. The events element contains a set of flags describing the events of interest for that file descriptor. The revents element contains a subset of these flags, indicating the events that are actually set on that file descriptor. The flags in the events and revents elements are constructed by or ing together the following values:
POLLIN
Data other than high priority data can be read without blocking.
POLLRDNORM
Normal data (priority band 0) can be read without blocking.
POLLRDBAND
Data from a non-zero priority band can be read without blocking.
POLLPRI
High-priority data can be read without blocking.
POLLOUT
Normal data can be written without blocking.
POLLWRNORM
The same as POLLOUT.
POLLWRBAND
Priority data (non-zero priority band) can be written. This event only examines bands that have been written to at least once.
POLLERR
An error has occurred on the device or stream. This flag is only valid in the revents element of the structure.
POLLHUP
A hangup has occurred on the stream. This event and POLLOUT are mutually exclusive; a stream is never writable once a hangup has occurred. This flag is only valid in the revents element of the structure.
POLLNVAL
The specified fd value is not a valid file descriptor. This flag is only valid in the revents element of the structure.
If none of the defined events has occurred on any of the selected file descriptors when poll is called, it waits for at least timeout milliseconds before returning. If the value of timeout is INFTIM, then poll blocks until one of the selected events occurs. To effect a poll, specify timeout as zero.
When poll returns, it normally returns a number greater than zero, indicating the number of file descriptors for which the revents element of their struct pollfd structure is non-zero. If the timeout expires before any selected events have occurred, poll returns 0. If an error occurs, poll returns -1 and places an error code in the external variable errno. When poll returns, the fd and events elements of the descriptor array are not modified; this allows the array to be immediately reused without having to reinitialize it.
Example 6-2 shows another program that reads from three terminal devices. Each time someone types on one of the terminals, the program reads it and prints it. If no one types anything on any of the devices within 10 seconds, the program prints a reminder to the user. When the string S-T-O-P is read from one of the terminals, the program exits.
Example 6-2:  poll
#include <stropts.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#define NTTYS       3           /* number of ttys to use     */
#define TIMEOUT     10          /* number of seconds to wait */
int     fds[NTTYS];             /* file descriptors          */
char    *fileNames[NTTYS];      /* file names                */
int     openFiles(char **);
void    readFiles(struct pollfd *);
int
main(int argc, char **argv)
{
    int i, n, maxfd;
    struct pollfd pfds[NTTYS];
    /*
     * Check that we have the right number of arguments.
     */
    if (argc != (NTTYS+1)) {
        fprintf(stderr, "You must supply %d tty names.\n", NTTYS);
        exit(1);
    }
    /*
     * Open the files.  The highest numbered file descriptor
     * (plus one) is returned in maxfd.
     */
    maxfd = openFiles(++argv);
    /*
     * We only need to initialize these once.
     */
    for (i=0; i < NTTYS; i++) {
        pfds[i].fd = fds[i];
        pfds[i].events = POLLIN;
    }
    /*
     * Forever...
     */
    for (;;) {
        /*
         * Wait for some input.
         */
        n = poll(pfds, NTTYS, TIMEOUT * 1000);
        /*
         * See what happened.
         */
        switch (n) {
        case -1:            /* error           */
            perror("poll");
            exit(1);
        case 0:             /* timeout         */
            printf("\nTimeout expired.  Type something!\n");
            break;
        default:            /* input available */
            readFiles(pfds);
            break;
        }
    }
}
/*
* openFiles - open all the files, return the highest file descriptor.
*/
int
openFiles(char **files)
{
    int i, maxfd;
    maxfd = 0;
    /*
     * For each file...
     */
    for (i=0; i < NTTYS; i++) {
        /*
         * Open it.
         */
        if ((fds[i] = open(*files, O_RDONLY)) < 0) {
            perror(*files);
            exit(1);
        }
        /*
         * Make sure it's a tty.
         */
        if (!isatty(fds[i])) {
            fprintf(stderr, "All files must be tty devices.\n");
            exit(1);
        }
        /*
         * Save the name.
         */
        fileNames[i] = *files++;
        /*
         * Save the highest numbered fd.
         */
        if (fds[i] > maxfd)
            maxfd = fds[i];
    }
    return(maxfd + 1);
}
/*
* readFiles - read input from any files that have some.
*/
void
readFiles(struct pollfd *pfds)
{
    int i, n;
    char buf[BUFSIZ];
    /*
     * For each file...
     */
    for (i=0; i < NTTYS; i++) {
        /*
         * If it has some input available...
         */
        if (pfds[i].revents & POLLIN) {
            /*
             * Read the data.
             */
            n = read(fds[i], buf, sizeof(buf));
            buf[n] = '\0';
            /*
             * Print it out.
             */
            printf("\nRead %d bytes from %s:\n", n, fileNames[i]);
            printf("\t%s\n", buf);
            /*
             * Is it telling us to stop?
             */
            if (strcmp(buf, "S-T-O-P\n") == 0)
                exit(0);
        }
    }
}
    % poll /dev/pts/3 /dev/pts/4 /dev/pts/5
To run this program, follow the instructions given for Example 6-1.

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback