Part A: Scan the PDP-8 manual pages on registers, memory management and interrupts in order to find all components of the CPU and MMU state that would need to be saved on behalf of one process while another is running. Document this state information, item by item.
The information that needs to be saved includes:* PC the program counter
* AC the accumulator
* MQ the accumulator
* L the link bit
* IF the current instruction field
* DF the current data field
* GT the greater-than flag (possibly useless)
* U the user-mode (privilege) bitThere are several other bits of CPU state such as IE (interrupt enable) but these seem not to be part of process states. For example, all bits involved with enabling interrupts will always indicate interrupts enabled for ready processes, since a process only disables interrupts durring an interval when it cannot be preempted and must not relinquish.
Part B: Re-scan the first 5 sections of the manual, looking for the tools you would need to use to save and restore the components of the CPU state.
We'll use DCA to store the AC as the first instruction of interrupt service routines, and CLA/TAD to restore it just before return from interrupt. AC will usually not be saved across kernel calls that block a user process; instead, it will be used for parameter passing.The interrupt or kernel call will save the PC, but then we'll have to use the sequence CLA/TAD/DCA to move the saved PC to an appropriate location in the process description record.
We'll use the sequences CLA/MQA/DCA and CLA/TAD/MQL to save and restore the MQ register.
We'll use the sequences GTF/DCA and CLA/TAD/RTF to save and restore the L, GT, U, IF and DF registers.
Part C: Suggest instruction sequences to be used for saving the process state on entry to the interrupt handler, and for restoring the process state on exit from the interrupt handler.
First, we assume that the interrupt handler has reserved locations in
field zero to point to each component of the current process description
record. Each component is prefixed with SAV, where the suffix names the
component. The skeleton interrupt service routine would be:
Location Value
0000 -0- /the return address
0001 JMP INTRPT /goto interrupt service routine
INTRPT, DCA I SAVAC /save AC
GTF
DCA I SAVF /save flags (IF,DF,L etc)
MQA
DCA I SAVMQ /save MQ
TAD 0
DCA I SAVPC /save PC
--- body of interrupt service routine ---
CLA
TAD I SAVPC /get PC (but don't restore)
DCA 0
TAD I SAVMQ /get MQ
MQL
TAD I SAVAC /get AC (but don't restore)
DCA TMP
TAD I SAVF /get flags
RTF
CLA
TAD TMP /restore AC
ION /enable interrputs
JMP I 0 /restores PC
This code is a bit tricky! Some instructions clear AC, some don't, and
the restore of the flags doesn't change the real IF register; rather, that
change only occurs when the JMP is executed. Similarly, the ION instruction
doesn't enable interrupts, but rather, this takes place after the immediately
following instruction, the JMP.
quicksort (int a, int b)
/* given a and b, bounds of the region of array to be sorted */
{
if (a >= b) return;
c = partition( a, b );
cobegin
quicksort( a, c );
quicksort( c, b );
coend;
}
Part B: Write a less naive version of Quicksort that counts the number of processes running and only creates new processes when there are CPUs to spare. When the set of CPUs is exhausted, it should run sequentially.
int processes = 1;
quicksort (int a, int b)
/* given a and b, bounds of the region of array to be sorted */
{
if (a >= b) return;
c = partition( a, b );
if (processes++ < maxprocesses) { -- atomic!
cobegin
quicksort( a, c );
quicksort( c, b );
coend;
processes--; -- atomic!
} else {
quicksort( a, c );
quicksort( c, b );
}
}
In the above, we assumed that the ++ and -- operators are atomic,
so that competing processes attempting to increment and decrement
processes will not cause unexpected results.
The Problem: Design a generalized waitfor(ID) routine that awaits the termination of a particular child.
waitfor( int id )
{
if (is_set_member( id, terminated )) {
remove_from_set( id, terminated );
return;
}
for (;;) {
int id1 = wait();
if (id == id1) return;
add_to_set( id1, terminated );
}
}
In the above, we assume that we have a good implementation of the set
abstraction for arbitrarily sized sets of process IDs. The size of the
set terminated will never exceed the number of child processes of this
process, but it is illegal to wait for any particular child more than once.