StuBS
|
Interrupt handling in StuBS after Assignment 2 can not be interrupted by other interrupts. If an interrupt service routine takes a while, it can lead to latency for the handling of other interrupts. Therefore interrupt service routines should be as short as possible. In the multicore-case, lock aquisition also adds to the latency, as a core waiting for a lock of another core cannot make any progress. Access to shared data structures between the interrupt handler and normal execution is also problematic, as the programmer needs to manually block interrupts when necessary, making it error prone.
The synchronization of activities within StuBS should now be switched to the prologue/epilogue model. In this model the interrupt service routines (prologues) are not interruptable and are as short as possible, whereas the deferred longer epilogue can be interrupted by new prologues. Modify your operating system in such a way that synchronization is no longer based solely on disabling interrupts (hard synchronization).
For this purpose, you have to implement the Guard (and its wrapper Guarded) which implements level ½. The interrupt_handler and the Gate also need to be modified as well as the devices.
By introducing the epilogues a third level (level ½) between level 1 (prologue) and level 0 (normal execution) is added. The prologue handles interrupts with the devices, level ½ handles synchronization witht the rest of the system and can be interrupted by level 1 and normal execution can of course be interrupted by both.
Normal control flow can also enter level ½ at will for synchronization of shared data structures. For this case, the same assumptions hold.
The class Guard has three important methods:
Guard::enter() is called from normal execution (level 0) only and brings the processor to level ½ if possible or waits until the epilogue-level is free (multi-core only). Guard::leave() executes all queues epilogues and if the queue is empty drop back to level 0. Guard::relay() is called when a prologue requests the execution of the epilogue (so relay enteres level ½ from level 1).
The Guarded class is a wrapper around the methods of Guard. When the level 0 needs to enter level ½ it can create a new Guarded object on the stack, which is deleted when the scope is left. Using the constructor and destructor of the Guarded class cleverly scoping of code to be run on level ½ is possible. This is called RAII (Resource Acquisition is Initialization), where requesting a resource is coupled to the life time of a stack-local object.
This could used on a normal operating system for requesting a block of memory in the constructor of an object, which is only needed for the duration of a scope of code. When the scope is left or the function returns, the RAII-object is also freed, the destructor is executed, which frees the requested block of memory. Typical uses in the STL are the Smart-Pointers which couple the life time of the referenced objects to their own using RAII.
You don't have to write your own Queue, we are including a Queue implementation in the handouts. Each gate is extended to have n pointers to the next Gate object in the queues. Remember, there is a queue for each processor core. The Queue object takes a parameter for an index into the array of next-pointers.