next up previous
Next: The Terminal Device Up: Implementation Previous: Implementation

Interrupt Processing

Structure of Interrupt Handlers

In the writing of interrupt handlers we face the following two contradictory goals:

they should be written in high-level languages so that they are easy to understand and modify

they should be written in assembly language for efficiency reasons and because they manipulate hardware registers and use special call/return sequences that cannot be coded in high-level languages

To satisfy both goals Xinu employs the following two-level strategy. Interrupts branch to low-level interrupt dispatch routines that are written in assembler language. These handle low-level tasks such as saving registers and returning from the interrupt when it has been processed. However, they do little else -- they call high-level interrupt routines to do the bulk of interrupt processing, passing them enough information to identify the interrupting device.

Xinu provides three interrupt dispatchers: one to handle input interrupts, one to handle output interrupts, and one to handle clock interrupts. Input and output dispatchers are separated for convenience, and a special clock dispatcher is provided for efficiency reasons.

NT provides an even more modular structure. A single routine, called the trap handler, handles both traps (called exceptions by NT) and interrupts, saving and restoring registers, which are common to both. If the asynchronous event was an interrupt, then it calls an interrupt handler. The task of this routine is to raise the processor priority to that of the device interrupting (so that a lower-level device cannot preempt), call either an internal kernel routine or an extrenal routine called an ISR, and then restore the processor priority. These two routines roughly correspond to our high-level interrupt routine and the trap handler corresponds to our low-level routine. Thus, NT trades off the efficiency of 2 levels for the reusability of 3 levels.

The device table entry for an input or output interrupt handler points at the high-level part of the interrupt handler, which is device-specific, and not the low-level part which is shared by all devices (except the clock).

Identifying the High-Level Routine

If all input (output) interrupts branch to the same input (output) dispatch routine, how does the dispatcher know which device-specific interrupt routine to call? The input (output) dispatch routine needs some way to discover the device that interrupted it, so that it can use this information to call the appropriate high-level routine. There are several ways to identify an interrupting device. Here are two of them:

The dispatcher may use a special machine instruction to get either the device address or the interrupt vector address of the device. Not all machines have such instructions.

The dispatcher may poll devices until it finds one with an interrupt pending.

The 11/2 has no special instruction to help identify the interrupting device. Therefore, the following `trick' is used, which is common to other operating systems for the same architecture. The device descriptor (not the device address) is stored in the second word of the interrupt vector. Recall that this word stores the value to be loaded into the PS register when the interrupt routine is called. Xinu uses the lower order 4 bits, which are used for condition codes, to store the descriptor of the device. These four bits are then used to identify the high-level routine.

Interrupt Dispatch Table

An interrupt dispatch table is used to relate device descriptors with (high-level) interrupt routines. The table is indexed by a device descriptor and each entry contains the following information:

the address of the input interrupt routine

an input code which is passed as an argument to the input interrupt routine

the address of the output interrupt routine

an output code which is passed as an argument to the above routine

The input (output) dispatch routine uses the device descriptor to access the appropriate dispatch table entry and calls the input (output) interrupt routine with the input (output) code as an argument code. The input and output codes can be anything the high-level routines need. In Xinu, they are initially the minor number of the device. Thus only one interrupt routine is needed for all devices of the same type. The minor number is used to distinguish between these devices.

Initialization of Dispatch Table and Interrupt Vectors

A routine called ioinit(descrp) initializes both the dispatch table entry and the input and output vectors of the device. The dispatch table is initialized as follows:

the addresses of the input and output interrupt routines are obtained from the device table

the input and output codes are assigned the minor number of the device

The input (output) vector is assigned as follows:

the PC points to the input (output) dispatcher shared by all devices

the condition four bits of the PS are assigned the device descriptor and the priority bits the highest possible priority so that interrupts are disabled.

Input and Output Dispatchers

The input (output) dispatcher does the following tasks:

saves the value of PS on the stack (why?)

saves the value of register r0 and r1, which are the only registers it will modify

gets device descriptor from saved value of PS

stores address of the input (output) routine in a register

pushes the input (output) code on stack

does a jsr to the interrupt routine

restores r0 and r1 from the stack

pops the arg, saved values of r0 and r1 and PS from stack

executes a `return from interrupt' instruction that restores the PC and PS value current when the interrupt routine was called.

Rules for Interrupt Processing

There are several rules for interrupt processing: First, they should ensure that shared data are not manipulated simultaneously by different processes. One way to do so is to make interrupt routines uninterruptible. Thus the PS value stored in the interrupt vector of a device disables interrupts.

This value is loaded when the interrupt handler is invoked. As a result, the interrupt routine is uninterruptible while the PS maintains this priority.

However, the PS may be changed while the interrupt routine is executing if it calls resched, which may switch to a process that has interrupts enabled. Therefore, the interrupt routine has to ensure that it completes changes to global data structures before it makes any call that results in context switching.

An interrupt routine should also make sure that it does not keep interrupts disabled too long. For instance, if the processor does not accept a character from an input device before another arrives, data will be lost.

Finally, interrupt routines should never call routines that could block the current process ( that is the process executing when the interrupt occurred) in some queue. Otherwise, if the interrupt occurs while the null process is executing, the ready list will be empty. However, resched assumes that there is always some process to execute! Some process should always be runnable to that interrupts can be executed. Thus interrupt routines need to call only those routines that leave the current process in the ready or current state, and may not call routines such as wait.

Rescheduling while Processing an Interrupt

We assumed above that interrupt routines could call resched. We now answer the following questions: First, is it useful to do so? Second, is it safe?

It is useful to call resched from an interrupt routine. An output routine after removing a character from a buffer may signal a semaphore to allow another process to write data to the buffer space that it makes available. Similarly, an input routine might send data it obtains from the device to a process. In each case, the routine resched is called.

It is also safe to call resched. Intuitively it may not seem so, because switching to a process that has interrupts enabled could lead to a sequence of interrupts piling up until the stack overflowed. However, such a danger does not exist for the following reason: A process that is executing an interrupt handler cannot be interrupted again (why?). (Some other process, however, can be.) Thus a process's stack will hold the PS and PC value for only one interrupt and there will never be more interrupts pending than the number of processes in the system.


next up previous
Next: The Terminal Device Up: Implementation Previous: Implementation



Prasun Dewan
Tue Feb 12 13:32:55 EST 2002