Lab 5 - Event-driven Concurrency
Lab goals:
- Learn about I/O multiplexing
- Gain some familiarity with the threads assignment
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:
readsetis a set of file descriptors to be watched to see if data becomes available for reading on that file descriptor (i.e., if a read from that file descriptor can now be completed without blocking).writesetis a set of file descriptors to be watched to see if they are ready for writing on that file descriptor (i.e., if a write to that file descriptor can now be completed without blocking).exceptsetis a set of file descriptors to be watched for exceptions (i.e., if an exception occurred on that file descriptor). Theexceptsetset is not relevant to this lab and should be specified here as aNULLpointer.
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:
- One or more file descriptors in the
readsetare ready for reading. - One or more file descriptors in the
writesetare ready for writing. - One of more file descriptors in the
exceptsetincurred an exception. - The timeout given by
timeouthas expired.
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:
FD_ZERO(&fdset)initializes the descriptor setfdsetto a null (i.e., empty) set.FD_SET(fd, &fdset)adds the file descriptorfdto the setfdset.FD_CLR(fd, &fdset)removes the file descriptorfdfrom the setfdset.FD_ISSET(fd, &fdset)checks if the file descriptorfdis a member of the setfdset; the result is non-zero (true) if that file descriptor is in the set and is 0 (false) otherwise.
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