Basics of interrupt handling |
The easiest way to handle interrupts is to treat them as a special form of subroutines, very similiar but with major subtle differences.
Both subroutines and interrupt service routines (ISR) perform a specific planned task designed by the programmer. The major difference between the subroutine and the ISR is when and how this task gets activated.
The subroutine call is totally predictable -- synchronous with the code execution. In debugged code, the subroutine is executed exactly when the programmer expects.
By contrast, the interrupt occurs in response to some external stimulus. One (of several) hardwired line connected to the processor is activated (normally pulled low) by an external device that wishes the processor to provide some service. This request for service can occur at any time -- asynchronous with the code execution. The processor must stop the instruction it is doing (or perhaps finish it) and jump to the proper ISR.
The processor has particular architectural resources for handling the synchronous subroutine tasks. It also has particular resources to handle the asynchronous interrupt driven tasks. These two sets of resources perform essentially identical tasks except for side effects of one task being synchronous and the other being asynchronous.
As discussed earlier, part of the CALL instruction is a specification of what subroutine is to be executed next. Since the CALL instruction is placed specifically in memory by the programmer, it is possible to place the subroutine specification in memory along side the instruction that requires the information.
Since the interrupt is asynchronous, the instruction that will be accessed when the interrupt occurs is unknown. It is therefore not possible to place the starting address in the memory next to the instruction, as there is no way to know where to put it! The same interrupt can occur in conjunction with the execution of any instruction in the code.
Instead of placing information about the start of the ISR in the code, each of the possible interrupts that can occur is allocated a specific fixed memory location where this information is stored.
These memory locations are called the "vector address table" on the 68K processors. There are over a 100 entries in this table corresponding to the number of different classifications of interrupts on this processor. When the interrupt occurs, the processor logic goes to the table and accesses the address placed there. This address is then used to "vector" to the actual start of the ISR.
By contrast, the PowerPC responds to only 16 different interrupt classes. Each of these classes are allocated 256 bytes of memory in a specific memory location. When the interrupt occurs, the processor actually switches directly into the ISR at the fixed location in memory for that class of interrupt.
This random effect is what makes the interrupt so difficult to debug. Several years ago, a floating point exception (software interrupt) on a system running UNIX could cause the user to end up being SUPER USER, if and only if, the values in the floating processor were set a certain way when the interrupt occurred. The occurance was 1 in 100 billion floating point instructions. But then with a 100 MHz processor, that's one possible problem every 10 minutes with an image processing algorithm!
When the subroutine call occurs, the address of the instruction after the CALL is stored. On completing the subroutine, during the return-from-subroutine instruction, this address is retrieved and placed into the program counter and the original code flow resumes.
Obviously, essentially the same operation must occur when the "return-from-interrupt" instruction occurs. However, there are some important differences. Something more than the return address must be stored. For most interrupts, the processor status register is also stored. This is because the interrupt might occur between an instruction where some condition (e.g. greater than zero) has been tested and the following instruction where the condition test result is beingused. The instructions associated with the interrupt service routine must be able to restore all information about the state of all the processor resources. This has to be done since interrupts are random in occurance. It does not have to be done with subroutines since no programmer would put a subroutine call between a test and the use of that test, or at least that is what is supposed to happen.
For other interrupts, often called exceptions, a great deal more than the return address and the status register information must be stored if the programmer is going to be able to resume standard program flow. You should do some research into the following exception types
We have discussed the conventions for register use during subroutine. The conventions for register use during interrupt service routines is far simpler. Since the interrupt is asynchronous, you have no idea what register will be in use. Simply save everything you modify, every register must be treated as "non-volatile".
The "save-everything" principle is a good one to follow. However, it some custom code situations and with some processors it is not always necessary. The AMD 29K RISC processor, recently discontinued, had 192 registers and one coding convention on that processor is to set aside several registers specifically for use during interrupts. A number of the newer CISC processors have a register bank dedicated for interrupt handling. This approach speeds time critical ISR routines as there is no need to save the registers to (slow) external memory during the ISR. That's only partly true. Just as subroutines can call other subroutines, then so can interrupt service routines be interrupted by other interrupts!
Since subroutines are "synchronous", we can place parameters for the subroutine on the memory stack (or in specific registers) prior to issuing the CALL instruction.
Since interrupts are "asynchronous", we can't place parameters on the stack for use by the ISR since we don't know when the parameters will be needed! Instead, communication is acheived between the main program and the ISR through a series of "global" variables called semaphore, flags and special buffers.
A major problem with "global variables" is that the "main" program may be just about to modify or use the semaphore variable, or may have just tested it. While the main program is deciding what to do, an interrupt may occur and the value gets changed in the ISR! To avoid this classic memory problem in situations where a "misread" is life-threatening, special coding practices must be followed. Many processors also have special non-interruptable-till-finished instructions of the "test-and-set" variety. We shall examine this problem in detail in the "Full Companion".
![]() |
Last modified: July 22, 1996 01:29 PM by M. Smith.
Copyright -- M. R. Smith