I/O Multiplexing

Multiplexing essentially means reading from or writing to multiple file descriptors simultaneously. We can use the select() Linux system call to implement multiplexing. The select() system call can be used to monitor file descriptors for regular files, terminals, pipes, sockets, etc. A call to select() blocks until the given file descriptors are read to perform I/O, or until an optional timeout has passed.

The simplest call to select() contains four arguments plus NULL for the fifth argument. The first argument is an int value that contains the highest numbered file descriptor in any of the sets being monitored, plus one. The second, third, and fourth arguments are pointers to file descriptor sets, which have the data type fd_set.

These file descriptor sets are not manipulated directly, but are instead manipulated via four helper macros, FD_ZERO(), FD_SET(), FD_CLR(), and FD_ISSET(). The FD_ZERO() macro clears the specified set, and should be used every time before calling select(). The FD_SET() macro adds a file descriptor to a given set.

#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void){
    
    fd_set readfds;
    fd_set writefds;
  
    int fdOne, fdTwo, fdThree;
    
    char *files[3] = {"FileOne", "FileTwo", "FileThree"};

    if((fdOne = open(files[0], O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU))<0){
        printf("Problem opening the first file descriptor.\n");
        exit(EXIT_FAILURE);
    }
    
    if((fdTwo = open(files[1], O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU))<0){
        printf("Problem opening the second file descriptor.\n");
        exit(EXIT_FAILURE);
    }
    
    if((fdThree = open(files[2], O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU))<0){
        printf("Problem opening the third file descriptor.\n");
        exit(EXIT_FAILURE);
    }
    
    
    //clear the read and write sets
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    
    //add file descriptors to the sets using FD_SET() 
    //macro
    FD_SET(fdOne, &readfds);
    FD_SET(fdTwo, &writefds);
    FD_SET(fdThree, &writefds);
    
    return 0;
    
}

The select() system call lets us do I/O multiplexing. We pass arguments to select() to tell the kernel what file descriptors we are interested in, whether we want to read from or write to the descriptor, and how long we want to wait.

On success, the select() system call returns the number of file descriptors ready for I/O, among the three sets. If there was an error, it returns -1. We can then use the FD_ISSET() macro to determine whether a file descriptor is in the set, meaning that it is ready to be read or to be written to.

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void){
    
    fd_set readfds;
    int retVal;
    int fd;
    
    fd = open("testing", O_CREAT | O_TRUNC | O_NONBLOCK, S_IRWXU);
    
    if(fd < 0){
        exit(EXIT_FAILURE);
    }
    
    //clear readfds
    FD_ZERO(&readfds);
    //add the file descriptor
    FD_SET(fd, &readfds);
    //add the descriptor for stdin
    FD_SET(STDIN_FILENO, &readfds);
    
    //ok let us see what happens
    //we're just looking to read, so pass in NULL
    //for other three parameters
    retVal = select(fd + 1, &readfds, NULL, NULL, NULL);
    
    if(retVal < 0){
        printf("select() failed.\n");
        exit(EXIT_FAILURE);
    }
    
    if(FD_ISSET(STDIN_FILENO, &readfds)){
        printf("Ready to read from stdin...\n");
    }
    
    if(FD_ISSET(fd, &readfds)){
        printf("File Descriptor %d is ready to read!", fd);
    }
      
    return 0;
    
}

Note what we mean by “ready”: a descriptor in the read set is ready if a read from that descriptor won’t block; likewise, a descriptor in the write set is ready if a write to that descriptor won’t block.

The last argument passed to select() specifies how long we want to wait.  If the argument is NULL, then we will wait forever, or until one of the specified descriptors is ready. If we pass the argument a timeval structure, we will wait the specified number of seconds and microseconds.

#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void){
    
    int retVal;
    timeval t;
    //wait for five seconds
    t.tv_sec = 5;
    t.tv_usec = 0;
    
    fd_set writefds;
    
    FD_ZERO(&writefds);
    
    //1
    FD_SET(STDOUT_FILENO, &writefds);
    //0
    FD_SET(STDIN_FILENO, &writefds);
    
    retVal = select(STDOUT_FILENO + 1, NULL, &writefds, NULL, &t);
    
    if(retVal==0){
        printf("select() timed out.\n");
    }
    if(retVal>0){
        if(FD_ISSET(STDOUT_FILENO, &writefds)){
            printf("stdout is ready.\n");
        }
        if(FD_ISSET(STDIN_FILENO, &writefds)){
            printf("stdin is ready.\n");
        }
    }
    return 0;
    
}

Note that a return value of 0 means that no descriptors are ready and that the timeout has thus passed.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s