To get an idea of how an operating system's system calls interact with user
programs and device interrupt handlers, you're going to implement, in
pseudo-code, four short system calls and two short interrupt handlers.
This small ``system'' uses a pool of buffers to perform I/O between
application programs, an input device, and an output device.
The buffers in the buffer pool are in one of three states at any point in
time:
- INFULL -- input buffer full and available to be returned to
the user on request.
- OUTFULL -- output buffer full and waiting to be output to
the I/O device when ready.
- EMPTY -- empty buffer available for either input or output.
These states can be implemented as separate linked lists, which you can
initialize and manipulate using, at the pseudo-code level, the functions
you implemented in Project 0.
There are four system calls that are invoked by the user to accomplish I/O:
- bufptr Gbufin() -- returns a pointer to the beginning of
the next full input buffer from INFULL. This should block if INFULL is empty.
- Rbufin(bufptr) -- places the buffer pointed to by bufptr back into EMPTY.
- bufptr Gbufout -- returns a pointer to the beginning of an
empty buffer from EMPTY into which the user places data to be
output. This should block if EMPTY is empty.
- Rbufout(bufptr) -- places the full output buffer pointed to
by bufptr onto the end of OUTFULL.
In addition, there are two interrupt handlers, one for input and one for
output:
- IHinput -- called when an input operation has been
completed.
- IHoutput -- called when an output operation has been
completed.
The diagram below illustrates how a buffer can move between these different
states, and the role of the system software described above.