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) |
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 |
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.
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.
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) |
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).
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);
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 |
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 |
|
|
Linux | xxx - outside of a kernel module, is it possible to install hardware and software interrupt handlers under Linux? |
- 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