CIS 307: Unix III

Select

At times a process needs to wait for messages from more than one source, say two sources. If the process blocks on one source, it is in no position to know if there is a message from the other source. If it checks each source without blocking, it performs a busy loop. Unix gives us a mechanism, select, for blocking on more than one source, waking up when there is information from any of them. In fact select allows us to wait at the same time for messages, events, or write operations.

Select is a complex service:

    #include <sys/types.h>
    #include <sys/time.h>
    int select(
          int nfds,          /* Number of objects monitored*/
          fd_set *readfds,   /* Set of file descriptors open for reading*/
          fd_set *writefds,  /* Set of file descript. open for writing*/
          fd_set *exceptfds, /* Set of file descript. with exception pending*/
          struct timeval *timout); /* Structure specifying timeout */
        If timout is null, then we block until one of the fd objects
        becomes ready. If timout points to a structure with timeout 
        equal to 0, then we do not block at all. Otherwise we will block
        up to the time specified by timout.
    FD_ZERO(&fdset)   /* fdset becomes the empty set */
    FD_SET(fd,&fdset) /* fd is added to fdset */
    FD_CLR(fd,&fdset) /* fd is removed from fdset */
    FD_ISSET(fd,&fdset) /* it determines if fd is in fdset */
    The structure timeval, defined in sys/time.h is
	struct timeval { int tv_sec;    /* second */
			 int tv_usec;}  /* microseconds */

Here are three examples of use of select. The first is a simple program from Stevens for determining the maximum size for a pipe. [When run on my alpha the program prints that the size is 65536.]

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

    int main(void)
    {
	int		i, n, fd[2];
	fd_set		writeset;
	struct timeval	tv;

	if (pipe(fd) < 0){
		perror("pipe");
		exit(1);}
	FD_ZERO(&writeset);  /* set to zero the writeset */

	for (n = 0; ; n++) { /* write 1 byte at a time until pipe is full */
		FD_SET(fd[1], &writeset); /*add write-end of pipe to writeset*/
		tv.tv_sec = tv.tv_usec = 0; /* don't wait at all */
		/* select returns the number of objects (in readset, */
		/* writeset, or exceptionset) that are ready. */
		/* In our case there will be at most one ready object.*/
		if ( (i = select(fd[1]+1, NULL, &writeset, NULL, &tv)) < 0) {
			perror("select");
			exit(1);}
		else if (i == 0) /*We cannot write to pipe, i.e. it is full*/
			break;
		if (write(fd[1], "a", 1) != 1){
			perror("write error");
			exit(1);}
	}
	printf("pipe capacity = %d\n", n);
	exit(0);
    }
The second is a program also from Stevens for waiting on a timer.
    #include	<sys/types.h>
    #include	<sys/time.h>

    main(argc, argv)
    int	argc;
    char	*argv[];
    {
	long			atol();
	static struct timeval	timeout;

	if (argc != 3) {
		printf("usage: timer <#seconds> <#microseconds>");
		exit(1);}
	timeout.tv_sec  = atol(argv[1]);
	timeout.tv_usec = atol(argv[2]);

	/* selet blocks waiting for the timeout to expire. */
	if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &timeout) < 0){
		perror("select error");
		exit(1);}
	exit(0);
    }

The third is a simple program where a process reads fixed size messages from two pipes. You should terminate the program from the terminal with a CONTROL-C.

    #include <sys/types.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include <stdio.h>
    #define MAXLINE 12

    int main(void)
    {
        int     i, m, n; 
	int     fd1[2];   /* pipe for communications from child1 to parent */
	int     fd2[2];   /* pipe for communications from child2 to parent */
        pid_t   pid;
        char    line[MAXLINE];
	fd_set   readset;
	static struct timeval timeout;

        if (pipe(fd1) < 0) {
                perror("pipe1");
                exit(1);}
        if (pipe(fd2) < 0) {
                perror("pipe2");
                exit(1);}
	/* child1 */
        if ( (pid = fork()) < 0) {
                perror("fork1");
                exit(1);}
        else if (pid == 0) {            
                close(fd1[0]);
		while (1) {
                    write(fd1[1], "from child1\n", MAXLINE);
		    sleep(1);}
		exit(0);}
	/* child2 */
        if ( (pid = fork()) < 0) {
                perror("fork2");
                exit(1);}
        else if (pid == 0) {            
                close(fd2[0]);
		while (1) {
                    write(fd2[1], "from child2\n", MAXLINE);
		    sleep(1.3);}
		exit(0);}
        /* parent */
        close(fd1[1]);
        close(fd2[1]);
	m = 1 + ((fd1[0]<fd2[0])?fd2[0]:fd1[0]); /*Max # of objs to wait for*/
	FD_ZERO(&readset);
	while (1) {
	    FD_SET(fd1[0], &readset);
	    FD_SET(fd2[0], &readset);
	    if (select(m,&readset, NULL,NULL,NULL) < 0) {
	       perror("select");
	       exit(1);}
            if (FD_ISSET(fd1[0], &readset)) {
               n = read(fd1[0], line, MAXLINE);
               write(STDOUT_FILENO, line, n);}
            if (FD_ISSET(fd2[0], &readset)) {
               n = read(fd2[0], line, MAXLINE);
               write(STDOUT_FILENO, line, n);}
	  }
        exit(0);
    }
Note that in a more realistic program we will deal with messages that are of variable sizes and more than two pipes (thinking of how you would deal with many file descriptors without having to increase the size of your program? can you also think of ways to be fair to the message sources?)

There will be other occasions to see select, in action.

ingargiola.cis.temple.edu