COMP 321: Introduction to Computer Systems

Lab 5 - Event-driven Concurrency

Lab goals:


Resources


Event-Based Concurrent I/O

The lectures have given a very high-level overview of the concept of event-driven concurrent I/O operations, including a basic treatment of the select() system call. The select() system call is widely used to implement event-driven servers. In particular, calling select() allows a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become ready for some class of I/O operation (e.g., input is now possible):

int select(int nfds, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);

Three of the arguments to the select() system call each specify an independent set of file descriptors to be watched, and the nfds argument should be the highest-numbered file descriptor across all three of these sets, plus 1; in other words, nfds specifies the number of file descriptors, since file descriptor numbers begin at 0. The three sets of file descriptors are as follows:

The first nfds file descriptors (i.e., file descriptor number 0 through number nfds-1, inclusive) are checked in each file descriptor set. If any of the three file descriptor sets are specified as a NULL pointer, that file descriptor set is ignored (i.e., no file descriptors are of interest for that type of operation).

A call to select() normally blocks the calling process and does not return until at least one of the specified file descriptors meets the condition for which it is being watched. However, the timeout argument to select() specifies an upper bound on the amount of time before which the call to select() returns, even if none of the watched file descriptors is satisfied. If timeout is NULL, select can block indefinitely.

Specifically, select() will block the calling process and not return until at least one of the following conditions is satisfied:

The value returned by a call to select() is the total number of file descriptors that are ready (from all three of the given file descriptor sets). In addition, on return, select() modifies each of the given file descriptor sets in-place to be a subset consisting of only those file descriptors that are ready for the given respective operation. If, instead, the specified timeout expired before any of the file descriptors became ready, then select() returns 0.

Each file descriptor set is stored as an fd_set, a bit field in an array of integers. The following macros are provided for manipulating such file descriptor sets:


Submission

For this lab and all labs in this course, at the end of your scheduled lab session, you should "submit" your work (regardless of how much your completed) using git push.

You should always include in your repo all files that you created in the lab, other than those (such as the output of the compiler) generated automatically from other files. Think of this as including just the files necessary to backup and to be able to recreate your work. Do not simply add all files to your repo; for example, do not simply type something like git add . or git add *, and never add any object files, executable files, or core files to your repo. Only add the actual source files (for example, .c and .h files) that should be in your repo.