CS42
Solution
Buffering will work so long as the job's computation does not consume data faster than it can be read or produce it faster than it can be written. If this is not the case, then computation will be stalled waiting for the I/O to catch up. Because of the speed differences between CPUs and I/O subsystems, this could be a frequent occurrence, especially on input. If there are enough output buffers, output won't stall cause the computation to stall.
Spooling, on the other hand, seeks to overlap the computation and I/O of independent jobs. The dependencies which exist between the phases of one job in the buffering scheme aren't present when spooling is used. There are fewer opportunities for computation to stall. Of course, there must be sufficient memory to hold all the input data and to buffer the output data, and also enough jobs to run.
It is possible: All one has to do is implement a virtual machine with the appropriate protection mechanisms. Any user program runs within the virtual machine (which is really a simulator) and cannot directly access the hardware).
It isn't possible: If the system has no inherent protection, all one has to do is subvert the software-supplied protection system. To use the IBM PC as an example, we could just reset the system and boot our own, non-protected version of the OS from the floppy drive.
However, if the processor provides a kernel-mode disable interrupts instruction, then it is possible to atomically execute a critical section. Assume that this instruction disables all hardware interrupts and that it is available only in kernel mode.
State a set of conditions which, if met, would guarantee the atomic execution of a critical section. Why is this a poor method of achieving atomicity for very, very long critical sections? Why should the disable interrupts instruction be privileged?
Here's the set of conditions:
This is a poor method for achieving atomicity for long critical sections because interrupts are disabled (ignored) for a long time. It is never a good idea to ignore interrupts for long.
The disable interrupts instruction should be privileged because if it were available to user-mode processes, they could cause the system to hang by disabling interrupts and entering an infinite no-op loop.
One solution to these problems is to maintain a buffer pool which can be used for both input and output. With this scheme, each buffer is in one of three states at any time:
There are four system calls that are invoked by the user to accomplish I/O:
In addition, there are two interrupt handlers, one for input and one for output:
The diagram below illustrates how a buffer can move between these different states, and the role of the system software described above.
Write the pseudo-code for the four system routines and the two interrupt handlers. You may assume that there are no critical section problems, i.e., it is as if each of the routines executes with interrupts disabled. You may assume that high-level data structures such as lists and the appropriate manipulation routines exist, if you wish, but be sure to indicate precisely what you are assuming.
In this problem, the buffer pool is maintained as a set of three queues --- emptyqu, inputqu, outputqu.
The four system calls and the interrupt handlers use the buffers available in these queues for their computation. In addition, the two channels, INCH and OUTCH use temporary buffers for storing the data being read in or written out. inptr and outptr are pointers to buffers in the buffer pool which are being currently read by INCH or written by OUTCH.
Gbufin() { return(remqu(inputqu)); }
Rbufin(bufptr) { if (!input_busy){ /* INCH had to wait for an empty buffer */ inptr = bufptr; /* now, there's an empty buffer */ startread(INCH,inptr); input_busy = TRUE; } else insqu(emptyqu,bufptr); } Gbufout() { return(remqu(emptyqu)); } Rbufout(bufptr) { if (!output_busy) { /* OUTCH had no buffers to write */ outptr = bufptr; /* now, it does */ startwrite(OUTCH,outptr); output_busy = TRUE; } else insqu(outputqu,bufptr); }
The interrupt handlers :
IHinput(){ insqu(inputqu,inptr); inptr = remqu(emptyqu); if (inptr != NULL) startread(INCH,inptr) else input_busy = FALSE; /* couldn't get a buffer */ }
IHoutput(){ if (!input_busy) { /* INCH had to wait for an empty buffer */ inptr = outptr; /* now, there's an empty buffer */ startread(INCH,inptr); input_busy = TRUE; } else insqu(emptyqu,outptr); outptr = remqu(outqu); if (outptr != NULL) startwrite(OUTCH,outptr); else output_busy = FALSE; /* no output buffers, OUTCH is idle */ }
Initialization :
init() { input_busy = TRUE; output_busy = FALSE; inputqu = outputqu = NULL; for (i=0;i<MAXBUF;i++) /* Insert the buffer node in the emptyqu */ inptr = remqu(emptyqu); startread(INCH,inptr); }
insqu(queue,nodeptr) : this function inserts the node pointed to by
nodeptr at the end of the queue whose head pointer is queue.
remqu(queue) : this function returns the first node of the queue
queue, returning NULL if the queue is empty.