OSD Home

Interrupt/exception classification

Terms taken from Intel 386 Programmers Reference Manual: http://my.execpc.com/~geezer/os/386intel.zip

The 32 reserved interrupts/exceptions:
number interrupt/exception notes
0 divide error could be overflow as well as zero denominator
1 debug exception
2 non-maskable hardware interrupt (NMI)
3 INT3 instruction debugger breakpoint
4 INTO instruction detected overflow
5 BOUND instruction detected overrange
6 invalid instruction opcode
7 no coprocessor ESC, WAIT instructions
8 double fault possible for 386+ in real mode if INT vector exceeds IDT limit
9 coprocessor segment overrun not in real mode
10 invalid task state segment (TSS) not in real mode
11 segment not present not in real mode
12 stack fault possible in real mode if stack access straddles offset 0 or FFFFh
13 general protection fault (GPF) possible in real mode if non-stack memory access straddles 0 or FFFFh, or if instruction is longer than 15 bytes, or if a 32-bit address greater than FFFFh is used
14 page fault not in real mode
15 (reserved)
16 coprocessor error ESC, WAIT instructions
17 alignment check not in real mode; 486+ only
18 machine check Pentium+ only
19-31 (reserved)

Sequence of events after interrupt/exception

CPU actions after interrupt/exception

1. If there is a pmode privilege transition, the CPU loads the SS and (E)SP from the current Task State Segment (TSS).

2. If there is a pmode privilege transition, the old values of SS and (E)SP are pushed on the new stack.

3. CPU pushes CS, (E)IP, and (E)FLAGS registers on the (new) stack. Some pmode exceptions also push an error code.

4. CPU clears IF bit in (E)FLAGS, disabling further interrupts. This does not happen with protected mode trap gates.

5. CPU does a far jump; loading CS and (E)IP registers, to interrupt handler code.

There are ten possible exception/interrupt stack frames on the 32-bit x86. Three of them are associated with 16-bit pmode; two with virtual 8086 mode. The other five are:
<- 16 bits ->
<- 32 bits ->
<- 32 bits ->
<- 32 bits ->
<- 32 bits ->
... ... ... ... ...
... ... ... ... ...user SS
... ... ... ...user SS user ESP
... ... EFLAGS user ESP EFLAGS
FLAGS EFLAGS ...CS EFLAGS ...CS
CS ...CS EIP ...CS EIP
IP EIP error code EIP error code
16-bit real mode 32-bit pmode, no privilege change; no error code. 32-bit pmode, error code; no privilege change 32-bit pmode, privilege change; no error code 32-bit pmode, privilege change; error code

Handler code prologue

6. The handler must save on the stack all registers that it modifies. Since the programmed exceptions are 'expected', the handler can modify some registers without saving them (e.g. DOS or BIOS interrupts that return data in the registers).

In any case, it is often easier to just save all registers on the stack. If the handler needs to return a value in a register, it can manipulate the stacked register values.

7. The handler must put known-good values into segment registers before using them.

Handler code epilogue

8. When the handler is done, it must restore saved registers from the stack.

9. If the exception pushed an error code, the handler must pop it now and discard it.

10. The handler must terminate with an IRET instruction, not the usual RET.

Actions taken by the IRET instruction

11. IRET pops (E)IP, CS, and (E)FLAGS. If a pmode privilege transition occurs, it also pops SS and (E)SP.

How does the CPU 'find' the handlers?

In real mode, there is an Interrupt Vector Table (IVT) at address 0000:0000, with 256 4-byte entries. Each entry is a far address:
byte in IVT entry usage
0 LSB of handler offset (IP 7:0)
1 MSB of handler offset (IP 15:8)
2 LSB of handler segment (CS 7:0)
3 MSB of handler segment (CS 15:8)

In protected mode, there is an Interrupt Descriptor Table (IDT), at any address, with up to 256 8-byte entries. Each entry is a protected mode gate descriptor:
byte in IDT entry usage
0 byte 0 (LSB) of handler offset (EIP 7:0)
1 byte 1 of handler offset (EIP 15:8)
2 LSB of handler selector (CS 7:0)
3 MSB of handler selector (CS 15:8)
4 (not used, set to 0)
5 access byte (8Eh, 8Fh, 0EEh, or 0EFh)
6 byte 2 of handler offset (EIP 23:16)
7 byte 3 (MSB) of handler offset (EIP 31:24)
Note that the 32-bit EIP value is broken into two 16-bit fields in the gate descriptor, with four bytes between them. Few object file formats support the type of relocation needed for such a value, so the IDT must be built at run-time.

The IDT may contain interrupt gates (access byte = 8Eh or 0EEh) or trap gates (access byte = 8Fh or 0EFh). Interrupt gates clear the IF bit when an interrupt occurs, disabling further hardware interrupts. Trap gates leave the IF bit unchanged.

The access byte should be set to 8Eh or 8Fh for ring 0 (kernel privilege) gates, or to 0EEh or 0EFh for ring 3 (user privilege) gates. If code running at ring 3 tries to use an INT instruction to transit a ring 0 gate, a general protection fault will occur instead.

You must use the LIDT instruction to tell the CPU the size and location of the IDT. Like LGDT, LIDT accepts a 6-byte 'pseudo-descriptor' (2-byte table limit followed by 4-byte linear address of table).

Which interrupts should be handled?

You do not need an IVT or IDT if: Otherwise, you should handle the 32 reserved exceptions (0-31), any additional programmed exceptions that are used by your OS code, plus (if you enable hardware interrupts) the 16 IRQs. It is not necessary to completely fill the IDT, but you may want to fill the real-mode IVT, as a debugging measure.

The 8259 programmable interrupt controller (PIC) chips

These chips make hardware interupts (IRQs) behave like software interrupts (INT instruction). (Remember, though, that hardware interrupts are asynchronous, meaning they can happen at any time.)

The BIOS programs the 8259 chips so that IRQs 0-7 are mapped to interrupts 8-15. Because interupts 8-15 are reserved for use by the CPU, this mapping makes it difficult to determine if an interrupt came from the CPU or external hardware. Unless you need DOS or BIOS compatability, the PICs should be re-programmed so IRQs do not use any of the CPU-reserved interrupts (see code snippet below).

The 8259 chips can mask (enable and disable) individual IRQs. Zeroing a bit at port 21h or A1h enables the corresponding IRQ:
Bits at I/O port 21h
(1st 8259)
IRQ Bits at I/O port A1h
(2nd 8259)
IRQ
b0 IRQ 0 (timer) b0 IRQ 8 (realtime clock)
b1 IRQ 1 (keyboard) b1 IRQ 9
b2 IRQ 2 (cascade; reserved for 2nd 8259) b2 IRQ 10
b3 IRQ 3 (COM2,4) b3 IRQ 11
b4 IRQ 4 (COM1,3) b4 IRQ 12 (PS/2 mouse)
b5 IRQ 5 (LPT) b5 IRQ 13 ('386 coprocessor)
b6 IRQ 6 (floppy) b6 IRQ 14 (primary IDE drives)
b7 IRQ 7 b7 IRQ 15 (secondary IDE drives)

The cascade bit must be zero to enable IRQs 8-15.

Examples:

        /* Enable IRQ0 (timer) and IRQ1 (keyboard) at the
        8259 PIC chips, disable others: */
        outportb(0x21, ~0x03);
        outportb(0xA1, ~0x00);

        /* Enable IRQ6 (floppy) and IRQs 14-15 (IDE) at the
        8259 chips, leave others as they are: */
	outportb(0x21, inportb(0x21) & ~0x44);
	outportb(0xA1, inportb(0xA1) & ~0xC0);
Handlers for IRQs must issue End Of Interrupt (EOI) to one or both PIC chips. For IRQ 0-7:
        outportb(0x20, 0x20);
For IRQ 8-15, both 8259 chips must get EOI:
        outportb(0xA0, 0x20);
        outportb(0x20, 0x20);

Distinguishing between CPU exceptions, IRQs, and traps

Distinguishing between CPU exceptions, IRQs, and traps is done by design. Example:
Software interrupt (INT instruction) Kernel privilege (ring 0) User privilege (ring 3)
0-31 Avoid (reserved for CPU-generated exceptions) Causes General Protection Fault instead
32-47 Avoid (reserved for IRQs) Causes General Protection Fault instead
48 Avoid OK (syscall software interrupt)
49-255 Causes Double Fault instead Causes Double Fault instead

Code snippets

Re-program the PICs so IRQs 0-15 go to INTs 32-47.

There is, of course, no standard for interrupt handlers written in asm. Unfortunately, there is no real standard for defining interrupt handlers in C, either. Here are some code snippets for various enviroments:
OS C NASM
16-bit DOS (real mode) Turbo C interrupt demo using DOS. The combination of real mode and Turbo C's interrupt keyword and getvect() and setvect() functions makes this code quite simple. xxx - to do
32-bit DOS (protected mode) DJGPP interrupt demo using DOS (DPMI) xxx - to do
16-bit x86, no OS Borland C interrupt demo that does NOT use DOS. The getvect() and setvect() functions use DOS interrupts INT 21h AH=35h and INT 21h AH=25h. This demo uses stand-alone versions of these functions. Similar to code at left, but getvect() and setvect() are implemented in a separate NASM file, so it works with the free Turbo C compilers. Thanks to Martin Coleman for this code!
32-bit x86, no OS DJGPP interrupt demo that does NOT use DOS. Though DJGPP has functions for installing interrupt handlers under DPMI (DOS Protected Mode Interface), GCC in general has no built-in support for interrupts. You must write all or part of the interrupt handling code in asm. xxx - to do
Windows
  • Win95: One Win95 program can spawn another in debug mode, and intercept the debug events generated when the second program uses INT 03h. All other software interrupts cause a blue-screen. Apart from this method, interrupt handlers are allowed only in kernel components (VxDs and drivers).
  • Windows CE: Since this OS was designed for use in embedded systems, I presume you can install arbitrary interrupt handlers, but I don't know if this is true, or what the details are.
  • Windows NT: Works as Win95 does, but software interrupts other than INT 03h are allowed.
Linux xxx - outside of a kernel module, is it possible to install hardware and software interrupt handlers under Linux?

Links

Hooking Interrupt and Exception Handlers in Linux, by mammon_: http://www.eccentrix.com/members/mammon/Text/linux_hooker.txt

TO DO

- make the C interrupt demos work with Watcom C
- re-entrancy:
	http://www.ganssle.com/articles/areentra.htm
- atomicity and race conditions
- the handler must be kept SHORT AND SWEET. Deferred interrupt processing:
        Linux bottom halves, Windows DPC

REPORT BUGS OR ERRORS IN THIS DOCUMENT