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:
readset
is 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).writeset
is 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).exceptset
is a set of file descriptors to be watched for exceptions (i.e., if an exception occurred on that file descriptor). Theexceptset
set is not relevant to this lab and should be specified here as aNULL
pointer.
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
readset
are ready for reading. - One or more file descriptors in the
writeset
are ready for writing. - One of more file descriptors in the
exceptset
incurred an exception. - The timeout given by
timeout
has 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 setfdset
to a null (i.e., empty) set.FD_SET(fd, &fdset)
adds the file descriptorfd
to the setfdset
.FD_CLR(fd, &fdset)
removes the file descriptorfd
from the setfdset
.FD_ISSET(fd, &fdset)
checks if the file descriptorfd
is 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