| Table of Content | Chapter Seventeen
(Part 2) |
|
| CHAPTER
SEVENTEEN: INTERRUPTS TRAPS AND EXEPTIONS (Part 1) |
||
| 17.1 -
80x86 Interrupt Structure and Interrupt Service Routines (ISRs) 17.2 - Traps 17.3 - Exceptions 17.3.1 - Divide Error Exception (INT 0) 17.3.2 - Single Step (Trace) Exception (INT 1) 17.3.3 - Breakpoint Exception (INT 3) 17.3.4 - Overflow Exception (INT 4/INTO) 17.3.5 - Bounds Exception (INT 5/BOUND) 17.3.6 - Invalid Opcode Exception (INT 6) 17.3.7 - Coprocessor Not Available (INT 7) 17.4 - Hardware Interrupts 17.4.1 - The 8259A Programmable Interrupt Controller (PIC) 17.4.2 - The Timer Interrupt (INT 8) 17.4.3 - The Keyboard Interrupt (INT 9) 17.4.4 - The Serial Port Interrupts (INT 0Bh and INT 0Ch) 17.4.5 - The Parallel Port Interrupts (INT 0Dh and INT 0Fh) 17.4.6 - The Diskette and Hard Drive Interrupts (INT 0Eh and INT 76h) 17.4.7 - The Real-Time Clock Interrupt (INT 70h) 17.4.8 - The FPU Interrupt (INT 75h) 17.4.9 - Nonmaskable Interrupts (INT 2) 17.4.10 - Other Interrupts 17.5 - Chaining Interrupt Service Routines 17.6 - Reentrancy Problems 17.7 - The Efficiency of an Interrupt Driven System 17.7.1 - Interrupt Driven I/O vs. Polling 17.7.2 - Interrupt Service Time 17.7.3 - Interrupt Latency 17.7.4 - Prioritized Interrupts 17.8 - Debugging ISRs |
Copyright 1996 by Randall Hyde
All rights reserved. Duplication other than for immediate display through a browser is prohibited by U.S. Copyright Law. This material is provided on-line as a beta-test of this text. It is for the personal use of the reader only. If you are interested in using this material as part of a course please contact rhyde@cs.ucr.edu Supporting software and other materials are available via anonymous ftp from ftp.cs.ucr.edu. See the "/pub/pc/ibmpcdir" directory for details. You may also download the material from "Randall Hyde's Assembly Language Page" at URL: http://webster.ucr.edu Notes: This document does not contain the laboratory exercises programming assignments exercises or chapter summary. These portions were omitted for several reasons: either they wouldn't format properly they contained hyperlinks that were too much work to resolve they were under constant revision or they were not included for security reasons. Such omission should have very little impact on the reader interested in learning this material or evaluating this document. This document was prepared using Harlequin's Web Maker 2.2 and Quadralay's Webworks Publisher. Since HTML does not support the rich formatting options available in Framemaker this document is only an approximation of the actual chapter from the textbook. If you are absolutely dying to get your hands on a version other than HTML you might consider having the UCR Printing a Reprographics Department run you off a copy on their Xerox machines. For details please read the following EMAIL message I received from the Printing and Reprographics Department:
We are currently working on ways to publish this text in a form other than HTML (e.g. Postscript PDF Frameviewer hard copy etc.). This however is a low-priority project. Please do not contact Randall Hyde concerning this effort. When something happens an announcement will appear on "Randall Hyde's Assembly Language Page." Please visit this WEB site at http://webster.ucr.edu for the latest scoop. Redesigned 10/2000 with "MS FrontPage 98" using
17" monitor 1024x768 |
|
The concept of an interrupt is something that has expanded
in scope over the years. The 80x86 family has only added to the confusion surrounding
interrupts by introducing the int (software interrupt) instruction. Indeed
different manufacturers have used terms like exceptions
faults
aborts
traps
and
interrupts to describe the phenomena this chapter discusses. Unfortunately
there is no
clear consensus as to the exact meaning of these terms. Different authors adopt different
terms to their own use. While it is tempting to avoid the use of such misused terms
altogether
for the purpose of discussion it would be nice to have a set of well defined
terms we can use in this chapter. Therefore
we will pick three of the terms above
interrupts
traps
and exceptions
and define them. This chapter attempts to use the most
common meanings for these terms
but don't be surprised to find other texts using them in
different contexts.
On the 80x86 there are three types of events commonly known as interrupts: traps exceptions and interrupts (hardware interrupts). This chapter will describe each of these forms and discuss their support on the 80x86 CPUs and PC compatible machines.
Although the terms trap and exception are often used
synonymously
we will use the term trap to denote a programmer initiated and expected
transfer of control to a special handler routine. In many respects
a trap is nothing more
than a specialized subroutine call. Many texts refer to traps as software interrupts. The
80x86 int instruction is the main vehicle for executing a trap. Note that
traps are usually unconditional; that is
when you execute an int
instruction
control always transfers to the procedure associated with the trap. Since
traps execute via an explicit instruction
it is easy to determine exactly which
instructions in a program will invoke a trap handling routine.
An exception is an automatically generated trap (coerced rather than requested) that occurs in response to some exceptional condition. Generally there isn't a specific instruction associated with an exception[1] instead an exception occurs in response to some degenerate behavior of normal 80x86 program execution. Examples of conditions that may raise (cause) an exception include executing a division instruction with a zero divisor executing an illegal opcode and a memory protection fault. Whenever such a condition occurs the CPU immediately suspends execution of the current instruction and transfers control to an exception handler routine. This routine can decide how to handle the exceptional condition; it can attempt to rectify the problem or abort the program and print an appropriate error message. Although you do not generally execute a specific instruction to cause an exception as with the software interrupts (traps) execution of some instruction is what causes an exception. For example you only get a division error when executing a division instruction somewhere in a program.
Hardware interrupts the third category that we will refer to simply as interrupts are program control interruption based on an external hardware event (external to the CPU). These interrupts generally have nothing at all to do with the instructions currently executing; instead some event such as pressing a key on the keyboard or a time out on a timer chip informs the CPU that a device needs some attention. The CPU interrupts the currently executing program services the device and then returns control back to the program.
An interrupt service routine is a procedure written specifically to handle a trap exception or interrupt. Although different phenomenon cause traps exceptions and interrupts the structure of an interrupt service routine or ISR is approximately the same for each of these.
Despite the different causes of traps exceptions and interrupts they share a common format for their handling routines. Of course these interrupt service routines will perform different activities depending on the source of the invocation but it is quite possible to write a single interrupt handling routine that processes traps exceptions and hardware interrupts. This is rarely done but the structure of the 80x86 interrupt system allows this. This section will describe the 80x86's interrupt structure and how to write basic interrupt service routines for the 80x86 real mode interrupts.
The 80x86 chips allow up to 256 vectored interrupts. This means that you can have up to 256 different sources for an interrupt and the 80x86 will directly call the service routine for that interrupt without any software processing. This is in contrast to nonvectored interrupts that transfer control directly to a single interrupt service routine regardless of the interrupt source.
The 80x86 provides a 256 entry interrupt vector table beginning at address 0:0 in memory. This is a 1K table containing 256 4-byte entries. Each entry in this table contains a segmented address that points at the interrupt service routine in memory. Generally we will refer to interrupts by their index into this table so interrupt zero's address (vector) is at memory location 0:0 interrupt one's vector is at address 0:4 interrupt two's vector is at address 0:8 etc.
When an interrupt occurs regardless of source the 80x86 does the following:
1) The CPU pushes the flags register onto the stack.
2) The CPU pushes a far return address (segment:offset) onto the stack segment value first.
3) The CPU determines the cause of the interrupt (i.e. the interrupt number) and fetches the four byte interrupt vector from address 0:vector*4.
4) The CPU transfers control to the routine specified by the interrupt vector table entry.
After the completion of these steps
the interrupt service
routine takes control. When the interrupt service routine wants to return control
it must
execute an iret (interrupt return) instruction. The interrupt return pops the
far return address and the flags off the stack. Note that executing a far return is
insufficient since that would leave the flags on the stack.
There is one minor difference between how the 80x86
processes hardware interrupts and other types of interrupts - upon entry into the hardware
interrupt service routine
the 80x86 disables further hardware interrupts by clearing the
interrupt flag. Traps and exceptions do not do this. If you want to disallow further
hardware interrupts within a trap or exception handler
you must explicitly clear the
interrupt flag with a cli instruction. Conversely
if you want to allow
interrupts within a hardware interrupt service routine
you must explicitly turn them back
on with an sti instruction. Note that the 80x86's interrupt disable flag only
affects hardware interrupts. Clearing the interrupt flag will not prevent the execution of
a trap or exception.
ISRs are written like almost any other assembly language
procedure except that they return with an iret instruction rather than ret.
Although the distance of the ISR procedure (near vs. far) is usually of no significance
you should make all ISRs far procedures. This will make programming easier if you decide
to call an ISR directly rather than using the normal interrupt handling mechanism.
Exceptions and hardware interrupts ISRs have a very special restriction: they must preserve the state of the CPU. In particular these ISRs must preserve all registers they modify. Consider the following extremely simple ISR:
SimpleISR proc far mov ax 0 iret SimpleISR endp
This ISR obviously does not preserve the machine state; it explicitly disturbs the value in ax and then returns from the interrupt. Suppose you were executing the following code segment when a hardware interrupt transferred control to the above ISR:
mov ax 5 add ax 2
; Suppose the interrupt occurs here.
puti . . .
The interrupt service routine would set the ax
register to zero and your program would print zero rather than the value five. Worse yet
hardware interrupts are generally asynchronous
meaning they can occur at any time and
rarely do they occur at the same spot in a program. Therefore
the code sequence above
would print seven most of the time; once in a great while it might print zero or two (it
will print two if the interrupt occurs between the mov ax
5 and add
ax
2 instructions). Bugs in hardware interrupt service routines are very difficult
to find
because such bugs often affect the execution of unrelated code.
The solution to this problem of course is to make sure you preserve all registers you use in the interrupt service routine for hardware interrupts and exceptions. Since trap calls are explicit the rules for preserving the state of the machine in such programs is identical to that for procedures.
Writing an ISR is only the first step to implementing an interrupt handler. You must also initialize the interrupt vector table entry with the address of your ISR. There are two common ways to accomplish this - store the address directly in the interrupt vector table or call DOS and let DOS do the job for you.
Storing the address yourself is an easy task. All you need to do is load a segment register with zero (since the interrupt vector table is in segment zero) and store the four byte address at the appropriate offset within that segment. The following code sequence initializes the entry for interrupt 255 with the address of the SimpleISR routine presented earlier:
mov ax 0 mov es ax pushf cli mov word ptr es:[0ffh*4] offset SimpleISR mov word ptr es:[0ffh*4 + 2] seg SimpleISR popf
Note how this code turns off the interrupts while changing
the interrupt vector table. This is important if you are patching a hardware interrupt
vector because it wouldn't do for the interrupt to occur between the last two mov
instructions above; at that point the interrupt vector is in an inconsistent state and
invoking the interrupt at that point would transfer control to the offset of SimpleISR and
the segment of the previous interrupt 0FFh handler. This
of course
would be a disaster.
The instructions that turn off the interrupts while patching the vector are unnecessary if
you are patching in the address of a trap or exception handler[2].
Perhaps a better way to initialize an interrupt vector is
to use DOS' Set Interrupt Vector call. Calling DOS with ah equal to 25h
provides this function. This call expects an interrupt number in the al
register and the address of the interrupt service routine in ds:dx. The call
to MS-DOS that would accomplish the same thing as the code above is
mov ax 25ffh ;AH=25h AL=0FFh. mov dx seg SimpleISR ;Load DS:DX with mov ds dx ; address of ISR lea dx SimpleISR int 21h ;Call DOS mov ax dseg ;Restore DS so it mov ds ax ; points back at DSEG.
Although this code sequence is a little more complex than poking the data directly into the interrupt vector table it is safer. Many programs monitor changes made to the interrupt vector table through DOS. If you call DOS to change an interrupt vector table entry those programs will become aware of your changes. If you circumvent DOS those programs may not find out that you've patched in your own interrupt and could malfunction.
Generally it is a very bad idea to patch the interrupt vector table and not restore the original entry after your program terminates. Well behaved programs always save the previous value of an interrupt vector table entry and restore this value before termination. The following code sequences demonstrate how to do this. First by patching the table directly:
mov ax 0 mov es ax ; Save the current entry in the dword variable IntVectSave: mov ax es:[IntNumber*4] mov word ptr IntVectSave ax mov ax es:[IntNumber*4 + 2] mov word ptr IntVectSave+2 ax ; Patch the interrupt vector table with the address of our ISR pushf ;Required if this is a hw interrupt. cli ; " " " " " " " mov word ptr es:[IntNumber*4] offset OurISR mov word ptr es:[IntNumber*4+2] seg OurISR popf ;Required if this is a hw interrupt. ; Okay do whatever it is that this program is supposed to do: . . . ; Restore the interrupt vector entries before quitting: mov ax 0 mov es ax pushf ;Required if this is a hw interrupt. cli ; " " " " " " mov ax word ptr IntVectSave mov es:[IntNumber*4] ax mov ax word ptr IntVectSave+2 mov es:[IntNumber*4 + 2] ax popf ;Required if this is a hw interrupt. . . .
If you would prefer to call DOS to save and restore the interrupt vector table entries you can obtain the address of an existing interrupt table entry using the DOS Get Interrupt Vector call. This call with ah=35h expects the interrupt number in al; it returns the existing vector for that interrupt in the es:bx registers. Sample code that preserves the interrupt vector using DOS is
; Save the current entry in the dword variable IntVectSave: mov ax 3500h + IntNumber ;AH=35h AL=Int #. int 21h mov word ptr IntVectSave bx mov word ptr IntVectSave+2 es ; Patch the interrupt vector table with the address of our ISR mov dx seg OurISR mov ds dx lea dx OurISR mov ax 2500h + IntNumber ;AH=25 AL=Int #. int 21h ; Okay do whatever it is that this program is supposed to do: . . . ; Restore the interrupt vector entries before quitting: lds bx IntVectSave mov ax 2500h+IntNumber ;AH=25 AL=Int #. int 21h . . .
| 17.2 Traps |
A trap is a software-invoked interrupt. To execute a trap
you use the 80x86 int (software interrupt) instruction[3].
There are only two primary differences between a trap and an arbitrary far procedure call:
the instruction you use to call the routine (int vs. call) and
the fact that a trap pushes the flags on the stack so you must use the iret
instruction to return from it. Otherwise
there really is no difference between a trap
handler's code and the body of a typical far procedure.
The main purpose of a trap is to provide a fixed subroutine
that various programs can call without having to actually know the run-time address.
MS-DOS is the perfect example. The int 21h instruction is an example of a
trap invocation. Your programs do not have to know the actual memory address of DOS' entry
point to call DOS. Instead
DOS patches the interrupt 21h vector when it loads into
memory. When you execute int 21h
the 80x86 automatically transfers control
to DOS' entry point
whereever in memory that happens to be.
There is a long lists of support routines that use the trap
mechanism to link application programs to themselves. DOS
BIOS
the mouse drivers
and
Netware' are a few examples. Generally
you would use a trap to call a resident
program function. Resident programs load themselves into memory and remain resident once
they terminate. By patching an interrupt vector to point at a subroutine within the
resident code
other programs that run after the resident program terminates can call the
resident subroutines by executing the appropriate int instruction.
Most resident programs do not use a separate interrupt
vector entry for each function they provide. Instead
they usually patch a single
interrupt vector and transfer control to an appropriate routine using a function number
that the caller passes in a register. By convention
most resident programs expect the
function number in the ah register. A typical trap handler would execute a
case statement on the value in the ah register and transfer control to the appropriate
handler function.
Since trap handlers are virtually identical to far procedures in terms of use we will not discuss traps in any more detail here. However the text chapter will explore this subject in greater depth when it discusses resident programs.
[1] Although we
will classify the into instruction in this category. This is an exception to
this rule.
[2] Strictly speaking this code sequence does not require the pushf cli and popf instructions because interrupt 255 does not correspond to any hardware interrupt on a typical PC machine. However it is important to provide this example so you're aware of the problem.
[3] You can also simulate an int instruction by pushing the flags and executing a far call to the trap handler. We will consider this mechanism later on.
Chapter Seventeen: Interrupts
Traps
and Exeptions (Part 1)
29 SEP 1996