[Next] [Art
of Assembly][Randall Hyde]
Art of Assembly Language: Chapter Seventeen
- Chapter 17 - Interrupts, Traps, and Exceptions
- 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:
Hello Again Professor Hyde,
Dallas gave me permission to take orders for the Computer Science 13 Manuals.
We would need to take charge card orders. The only cards we take are: Master
Card, Visa, and Discover. They would need to send the name, numbers, expiration
date, type of card, and authorization to charge $95.00 for the manual and
shipping, also we should have their phone number in case the company has
any trouble delivery. They can use my e-mail address for the orders and
I will process them as soon as possible. I would assume that two weeks would
be sufficient for printing, packages and delivery time.
I am open to suggestions if you can think of any to make this as easy as
possible.
Thank You for your business,
Kathy Chapman, Assistant
Printing and Reprographics
University of California
Riverside
(909) 787-4443/4444
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.
Art of Assembly Bug Report Submissions
Did you find an error in The Art of Assembly Language Programming?
You can let me know by using the form below to report the error to me so
that I can correct the error for the next beta version. Thank you.
The Submission Form
Please provide your name and e-mail address so I can contact you if
I have any questions regarding your submission.
Chapter 17 Interrupts, Traps, and Exceptions
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.
17.1 80x86 Interrupt Structure and Interrupt Service Routines (ISRs)
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.
- 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
Art of Assembly: Chaper Seventeen - 29 SEP 1996
[Next] [Art of Assembly][Randall
Hyde]