Project 7: Interrupt Descriptor Table (IDT)

Build a complete interrupt handling system with IDT, ISR stubs, exception handlers, and hardware IRQ support—the foundation for preemptive multitasking and hardware event processing.


Quick Reference

Attribute Value
Difficulty Expert (Level 4)
Time Estimate 2 weeks
Language C with x86 Assembly
Alternative Rust
Prerequisites Project 6 (Protected Mode), understanding of CPU exceptions
Key Topics IDT structure, ISR stubs, 8259 PIC, CPU exceptions, hardware IRQs
Software/Tools GCC cross-compiler (i686-elf-gcc), NASM, QEMU, GDB
Main Book “Operating Systems: Three Easy Pieces” by Arpaci-Dusseau

Table of Contents


1. Learning Objectives

By completing this project, you will:

  1. Master the x86 interrupt architecture: Understand how the CPU handles synchronous (exceptions) and asynchronous (hardware) interrupts
  2. Implement the Interrupt Descriptor Table: Create and load an IDT with 256 entries for all possible interrupt vectors
  3. Write ISR stubs in assembly: Create interrupt service routine entry points that properly save and restore CPU state
  4. Program the 8259 PIC: Remap and configure the Programmable Interrupt Controller for hardware IRQs
  5. Handle CPU exceptions: Implement handlers for divide-by-zero, page faults, general protection faults, and other exceptions
  6. Process hardware interrupts: Handle timer ticks (IRQ0), keyboard input (IRQ1), and other hardware events
  7. Understand the stack during interrupts: Know exactly what the CPU pushes, what you push, and how to return cleanly
  8. Build the foundation for preemptive multitasking: Understand how timer interrupts enable context switching

2. Theoretical Foundation

2.1 Core Concepts

What Are Interrupts?

Interrupts are signals that cause the CPU to temporarily stop what it’s doing, save its state, execute a handler function, and then resume the original work. They are the fundamental mechanism for:

  • Hardware events: Keyboard presses, timer ticks, disk I/O completion
  • CPU exceptions: Divide by zero, page faults, invalid opcodes
  • System calls: Software-triggered interrupts (INT instruction)
+-----------------------------------------------------------------------+
|                    INTERRUPT CLASSIFICATION                            |
+-----------------------------------------------------------------------+
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │                        INTERRUPTS                               │  |
|  │                            │                                    │  |
|  │     ┌──────────────────────┴──────────────────────┐            │  |
|  │     │                                             │            │  |
|  │     ▼                                             ▼            │  |
|  │ ┌─────────────────────┐               ┌─────────────────────┐  │  |
|  │ │     EXCEPTIONS      │               │   EXTERNAL (IRQs)   │  │  |
|  │ │   (Synchronous)     │               │   (Asynchronous)    │  │  |
|  │ │                     │               │                     │  │  |
|  │ │ Caused by executing │               │ Caused by hardware  │  │  |
|  │ │ instructions        │               │ devices             │  │  |
|  │ └──────────┬──────────┘               └──────────┬──────────┘  │  |
|  │            │                                     │              │  |
|  │   ┌────────┴────────┐                   ┌────────┴────────┐    │  |
|  │   │                 │                   │                 │    │  |
|  │   ▼                 ▼                   ▼                 ▼    │  |
|  │ ┌───────┐     ┌───────┐           ┌───────┐         ┌───────┐ │  |
|  │ │ Faults│     │ Traps │           │Maskable│         │  NMI  │ │  |
|  │ │       │     │       │           │ (INTR) │         │       │ │  |
|  │ │Return │     │Return │           │        │         │Always │ │  |
|  │ │to same│     │to next│           │Can be  │         │handled│ │  |
|  │ │instr  │     │instr  │           │disabled│         │       │ │  |
|  │ └───────┘     └───────┘           └───────┘         └───────┘ │  |
|  │                                                                 │  |
|  │ Examples:       Examples:          Examples:       Example:     │  |
|  │ - Page Fault   - Breakpoint       - Timer (IRQ0)  - Memory     │  |
|  │ - Div by Zero  - INT instruction  - Keyboard      - Parity     │  |
|  │ - GP Fault     - Overflow         - Disk          - Error      │  |
|  │                                   - Network                     │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

The Interrupt Descriptor Table (IDT)

The IDT is a table in memory that tells the CPU where to find the handler for each interrupt vector (0-255). When an interrupt occurs, the CPU:

  1. Uses the interrupt number as an index into the IDT
  2. Reads the IDT entry to find the handler address
  3. Saves the current state (registers, flags, return address)
  4. Jumps to the handler
+-----------------------------------------------------------------------+
|                    INTERRUPT DESCRIPTOR TABLE                          |
+-----------------------------------------------------------------------+
|                                                                        |
|  IDT Register (IDTR) - loaded with LIDT instruction:                  |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │  Limit (16-bit)           │  Base Address (32-bit)              │  |
|  │  Size of IDT - 1          │  Physical address of IDT            │  |
|  │  (256 * 8) - 1 = 0x7FF    │  e.g., 0x00000000                   │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  IDT Layout (256 entries, each 8 bytes = 2048 bytes total):           |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Vector 0:   Divide Error (#DE)                    [Fault]       │  |
|  │ Vector 1:   Debug Exception (#DB)                 [Fault/Trap]  │  |
|  │ Vector 2:   NMI Interrupt                         [Interrupt]   │  |
|  │ Vector 3:   Breakpoint (#BP)                      [Trap]        │  |
|  │ Vector 4:   Overflow (#OF)                        [Trap]        │  |
|  │ Vector 5:   BOUND Range Exceeded (#BR)            [Fault]       │  |
|  │ Vector 6:   Invalid Opcode (#UD)                  [Fault]       │  |
|  │ Vector 7:   Device Not Available (#NM)            [Fault]       │  |
|  │ Vector 8:   Double Fault (#DF)                    [Abort]       │  |
|  │ Vector 9:   Coprocessor Segment Overrun           [Fault]       │  |
|  │ Vector 10:  Invalid TSS (#TS)                     [Fault]       │  |
|  │ Vector 11:  Segment Not Present (#NP)             [Fault]       │  |
|  │ Vector 12:  Stack-Segment Fault (#SS)             [Fault]       │  |
|  │ Vector 13:  General Protection Fault (#GP)        [Fault]       │  |
|  │ Vector 14:  Page Fault (#PF)                      [Fault]       │  |
|  │ Vector 15:  Reserved                                            │  |
|  │ Vector 16:  x87 FPU Error (#MF)                   [Fault]       │  |
|  │ Vector 17:  Alignment Check (#AC)                 [Fault]       │  |
|  │ Vector 18:  Machine Check (#MC)                   [Abort]       │  |
|  │ Vector 19:  SIMD Floating-Point (#XM)             [Fault]       │  |
|  │ Vector 20:  Virtualization Exception (#VE)        [Fault]       │  |
|  │ Vectors 21-31: Reserved by Intel                                │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ Vector 32:  IRQ0 - Programmable Interval Timer    [After remap] │  |
|  │ Vector 33:  IRQ1 - Keyboard                       [After remap] │  |
|  │ Vector 34:  IRQ2 - Cascade (PIC2)                 [After remap] │  |
|  │ Vector 35:  IRQ3 - COM2                           [After remap] │  |
|  │ Vector 36:  IRQ4 - COM1                           [After remap] │  |
|  │ Vector 37:  IRQ5 - LPT2 / Sound Card              [After remap] │  |
|  │ Vector 38:  IRQ6 - Floppy Disk                    [After remap] │  |
|  │ Vector 39:  IRQ7 - LPT1 / Spurious                [After remap] │  |
|  │ Vector 40:  IRQ8 - CMOS Real-Time Clock           [After remap] │  |
|  │ Vector 41:  IRQ9 - ACPI / Legacy IRQ2             [After remap] │  |
|  │ Vector 42:  IRQ10 - Available                     [After remap] │  |
|  │ Vector 43:  IRQ11 - Available                     [After remap] │  |
|  │ Vector 44:  IRQ12 - PS/2 Mouse                    [After remap] │  |
|  │ Vector 45:  IRQ13 - FPU / Coprocessor             [After remap] │  |
|  │ Vector 46:  IRQ14 - Primary ATA Hard Disk         [After remap] │  |
|  │ Vector 47:  IRQ15 - Secondary ATA Hard Disk       [After remap] │  |
|  ├─────────────────────────────────────────────────────────────────┤  |
|  │ Vectors 48-255: Available for software interrupts               │  |
|  │   Vector 0x80 (128): Often used for system calls (like Linux)   │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

IDT Entry Format (8 bytes for 32-bit Protected Mode)

Each IDT entry (gate descriptor) is 8 bytes with a specific structure:

+-----------------------------------------------------------------------+
|                    IDT ENTRY FORMAT (Gate Descriptor)                  |
+-----------------------------------------------------------------------+
|                                                                        |
|  8-byte structure (bits numbered from right to left):                 |
|                                                                        |
|  Bytes 7-6 (bits 63-48):   Offset High (bits 31-16 of handler addr)   |
|  Byte 5 (bits 47-40):      Flags/Attributes                           |
|  Byte 4 (bits 39-32):      Reserved (must be 0)                       |
|  Bytes 3-2 (bits 31-16):   Segment Selector (0x08 for kernel code)    |
|  Bytes 1-0 (bits 15-0):    Offset Low (bits 15-0 of handler addr)     |
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ 63        48│47      40│39    32│31        16│15         0     │  |
|  │             │          │        │            │                  │  |
|  │ Offset High │ Flags    │ Zero   │ Selector   │  Offset Low      │  |
|  │ (31:16)     │          │        │ (code seg) │  (15:0)          │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Flags/Attributes Byte (Byte 5):                                      |
|  ┌───┬───┬───┬───┬───┬───┬───┬───┐                                   |
|  │ P │ DPL   │ S │    Type       │   Bits: 7  6-5  4   3-0           |
|  └───┴───┴───┴───┴───┴───┴───┴───┘                                   |
|                                                                        |
|  P   (bit 7):    Present (1 = valid entry, 0 = unused)                |
|  DPL (bits 6-5): Descriptor Privilege Level                           |
|                  0 = kernel only, 3 = user can trigger               |
|  S   (bit 4):    Storage Segment (always 0 for gates)                 |
|  Type (bits 3-0): Gate type                                           |
|                                                                        |
|  Gate Types (for 32-bit protected mode):                              |
|  ┌────────────────────────────────────────────────────────────────┐   |
|  │ 0x5 (0101): 32-bit Task Gate                                   │   |
|  │ 0xE (1110): 32-bit Interrupt Gate  <-- Use for interrupts      │   |
|  │ 0xF (1111): 32-bit Trap Gate       <-- Use for exceptions      │   |
|  └────────────────────────────────────────────────────────────────┘   |
|                                                                        |
|  Interrupt Gate vs Trap Gate:                                         |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Interrupt Gate: Automatically clears IF flag (disables ints)   │  |
|  │                 Use for: IRQs, critical sections               │  |
|  │                                                                 │  |
|  │ Trap Gate:      Does NOT modify IF flag                        │  |
|  │                 Use for: Exceptions, system calls               │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Common Flags values:                                                  |
|    0x8E = 10001110 = Present, Ring 0, Interrupt Gate (32-bit)         |
|    0x8F = 10001111 = Present, Ring 0, Trap Gate (32-bit)              |
|    0xEE = 11101110 = Present, Ring 3, Interrupt Gate (user callable)  |
|                                                                        |
+-----------------------------------------------------------------------+

The 8259 Programmable Interrupt Controller (PIC)

The 8259 PIC is the chip that manages hardware interrupts on legacy PC systems. There are actually two PICs: a master and a slave, cascaded together:

+-----------------------------------------------------------------------+
|                    8259 PIC ARCHITECTURE                               |
+-----------------------------------------------------------------------+
|                                                                        |
|  Hardware Devices           Master PIC          Slave PIC             |
|                             (I/O 0x20-0x21)     (I/O 0xA0-0xA1)       |
|                                                                        |
|  ┌─────────────┐           ┌─────────────┐     ┌─────────────┐        |
|  │ Timer       │──IRQ0────►│ IR0         │     │             │        |
|  │ (PIT)       │           │             │     │             │        |
|  └─────────────┘           │             │     │             │        |
|                            │             │     │             │        |
|  ┌─────────────┐           │             │     │             │        |
|  │ Keyboard    │──IRQ1────►│ IR1         │     │             │        |
|  │ Controller  │           │             │     │             │        |
|  └─────────────┘           │             │     │             │        |
|                            │             │     │             │        |
|                            │             │     │             │        |
|                  ┌────────►│ IR2         │◄────│ INT         │        |
|                  │ Cascade │   (Cascade) │     │ (to master) │        |
|                  │         │             │     │             │        |
|  ┌─────────────┐ │         │             │     │             │        |
|  │ COM2        │──IRQ3────►│ IR3         │     │ IR0 ◄──────IRQ8 (RTC)|
|  └─────────────┘           │             │     │             │        |
|                            │             │     │ IR1 ◄──────IRQ9      |
|  ┌─────────────┐           │             │     │             │        |
|  │ COM1        │──IRQ4────►│ IR4         │     │ IR2 ◄──────IRQ10     |
|  └─────────────┘           │     MASTER  │     │             │        |
|                            │     8259A   │     │ IR3 ◄──────IRQ11     |
|  ┌─────────────┐           │             │     │             │        |
|  │ LPT2/Sound  │──IRQ5────►│ IR5         │     │ IR4 ◄──────IRQ12(PS2)|
|  └─────────────┘           │             │     │     SLAVE   │        |
|                            │             │     │     8259A   │        |
|  ┌─────────────┐           │             │     │ IR5 ◄──────IRQ13(FPU)|
|  │ Floppy      │──IRQ6────►│ IR6         │     │             │        |
|  └─────────────┘           │             │     │ IR6 ◄──────IRQ14(ATA)|
|                            │             │     │             │        |
|  ┌─────────────┐           │             │     │ IR7 ◄──────IRQ15(ATA)|
|  │ LPT1        │──IRQ7────►│ IR7         │     │             │        |
|  └─────────────┘           │             │     │             │        |
|                            │             │     │             │        |
|                            │ INT────────────────────────────►│ CPU    |
|                            │ (to CPU)    │     │             │ INTR   |
|                            └─────────────┘     └─────────────┘        |
|                                                                        |
|  Default vector mapping (conflicts with CPU exceptions!):              |
|    Master PIC: IRQ0-IRQ7 → Vectors 0x08-0x0F (conflicts!)             |
|    Slave PIC:  IRQ8-IRQ15 → Vectors 0x70-0x77                         |
|                                                                        |
|  After remapping (required!):                                          |
|    Master PIC: IRQ0-IRQ7 → Vectors 0x20-0x27 (32-39)                  |
|    Slave PIC:  IRQ8-IRQ15 → Vectors 0x28-0x2F (40-47)                 |
|                                                                        |
+-----------------------------------------------------------------------+

Stack Layout During Interrupts

When an interrupt occurs, the CPU automatically pushes certain values onto the stack before jumping to your handler. Understanding this is critical:

+-----------------------------------------------------------------------+
|                    STACK DURING INTERRUPT HANDLING                     |
+-----------------------------------------------------------------------+
|                                                                        |
|  What the CPU pushes automatically (before your handler runs):        |
|                                                                        |
|  WITHOUT privilege level change (interrupt from Ring 0):              |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ High addresses                                                   │  |
|  │ ┌───────────────────────────────────────────────────────────┐   │  |
|  │ │  EFLAGS                                     (pushed first) │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  CS (code segment selector)                               │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EIP (return address)                                     │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  Error Code (only for some exceptions!)                   │   │  |
|  │ └───────────────────────────────────────────────────────────┘   │  |
|  │ Low addresses  ◄── ESP points here                              │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  WITH privilege level change (interrupt from Ring 3 → Ring 0):        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ High addresses (kernel stack)                                    │  |
|  │ ┌───────────────────────────────────────────────────────────┐   │  |
|  │ │  SS (user stack segment)                   (pushed first) │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  ESP (user stack pointer)                                 │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EFLAGS                                                   │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  CS                                                       │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EIP                                                      │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  Error Code (if applicable)                               │   │  |
|  │ └───────────────────────────────────────────────────────────┘   │  |
|  │ Low addresses  ◄── ESP points here (on kernel stack)           │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Exceptions with error codes (CPU pushes automatically):              |
|    #DF (8), #TS (10), #NP (11), #SS (12), #GP (13), #PF (14), #AC (17)|
|                                                                        |
|  Exceptions WITHOUT error codes (you must push a dummy value):        |
|    #DE (0), #DB (1), #NMI (2), #BP (3), #OF (4), #BR (5), #UD (6),   |
|    #NM (7), #MF (16), #MC (18), #XM (19), and all IRQs               |
|                                                                        |
+-----------------------------------------------------------------------+

Complete Stack After ISR Stub Runs

Your ISR stub needs to save additional state to create a uniform structure:

+-----------------------------------------------------------------------+
|                    COMPLETE INTERRUPT FRAME                            |
+-----------------------------------------------------------------------+
|                                                                        |
|  After your ISR stub pushes everything:                               |
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ High addresses                                                   │  |
|  │ ┌───────────────────────────────────────────────────────────┐   │  |
|  │ │  (SS - only if privilege change)                          │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  (ESP - only if privilege change)                         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EFLAGS                                   [CPU pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  CS                                       [CPU pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EIP                                      [CPU pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  Error Code (or dummy 0)                  [You pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  Interrupt Number                         [You pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EAX                                      [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  ECX                                      [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EDX                                      [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EBX                                      [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  ESP (value before pusha)                 [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EBP                                      [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  ESI                                      [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  EDI                                      [pusha]         │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  DS                                       [You pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  ES                                       [You pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  FS                                       [You pushed]    │   │  |
|  │ ├───────────────────────────────────────────────────────────┤   │  |
|  │ │  GS                                       [You pushed]    │   │  |
|  │ └───────────────────────────────────────────────────────────┘   │  |
|  │ Low addresses  ◄── ESP points here                              │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  This structure is often defined as a C struct for easy access:       |
|                                                                        |
|  struct interrupt_frame {                                              |
|      uint32_t gs, fs, es, ds;                  // Segment registers   |
|      uint32_t edi, esi, ebp, esp;              // pusha (reversed)    |
|      uint32_t ebx, edx, ecx, eax;              // pusha continued     |
|      uint32_t int_no, err_code;                // Pushed by ISR stub  |
|      uint32_t eip, cs, eflags;                 // Pushed by CPU       |
|      uint32_t useresp, ss;                     // Only if ring change |
|  };                                                                    |
|                                                                        |
+-----------------------------------------------------------------------+

2.2 Why This Matters

Interrupts Are Fundamental to All Operating Systems

Without interrupts:

  • No keyboard input: The CPU would have to constantly poll the keyboard
  • No timer ticks: No way to preempt running processes
  • No disk I/O: Would have to poll disk status continuously
  • No exception handling: Divide by zero would crash the entire system
  • No multitasking: Can’t switch between processes

Real Operating Systems Depend on This

+-----------------------------------------------------------------------+
|                    INTERRUPT USAGE IN REAL SYSTEMS                     |
+-----------------------------------------------------------------------+
|                                                                        |
|  Linux Kernel:                                                         |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ arch/x86/kernel/idt.c         - IDT setup and management        │  |
|  │ arch/x86/kernel/irq.c         - IRQ handling                    │  |
|  │ arch/x86/entry/entry_32.S     - ISR entry points (32-bit)       │  |
|  │ arch/x86/include/asm/desc.h   - IDT structures                  │  |
|  │ kernel/irq/handle.c           - Generic IRQ handling            │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Windows Kernel:                                                       |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ ntoskrnl.exe                  - Interrupt dispatch              │  |
|  │ hal.dll                       - Hardware abstraction (PIC/APIC) │  |
|  │ Interrupt Request Levels (IRQL) - Priority-based handling       │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Real-Time Operating Systems:                                         |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Deterministic interrupt latency is CRITICAL                     │  |
|  │ - Medical devices: Pacemakers must respond in microseconds      │  |
|  │ - Automotive: ABS braking systems, airbag deployment            │  |
|  │ - Industrial: Robot arm control, CNC machines                   │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

2.3 Historical Context

The x86 interrupt architecture has evolved significantly:

+-----------------------------------------------------------------------+
|                    EVOLUTION OF x86 INTERRUPTS                         |
+-----------------------------------------------------------------------+
|                                                                        |
|  1978: 8086                                                            |
|  ├── Interrupt Vector Table (IVT) at 0x0000-0x03FF                    |
|  ├── 256 vectors, each 4 bytes (segment:offset)                       |
|  └── No protection, any code can modify IVT                           |
|                                                                        |
|  1981: IBM PC (8088) + 8259 PIC                                       |
|  ├── Master PIC for IRQ0-IRQ7                                         |
|  ├── Default mapping: IRQ0-7 → INT 8-15 (conflicts with exceptions!)  |
|  └── Later: Slave PIC added for IRQ8-15                               |
|                                                                        |
|  1985: 80386 Protected Mode                                           |
|  ├── Interrupt Descriptor Table (IDT) replaces IVT                    |
|  ├── Each entry 8 bytes with gate descriptors                         |
|  ├── Privilege levels enforced                                        |
|  └── This is what we implement in this project                        |
|                                                                        |
|  1993: Pentium + APIC                                                 |
|  ├── Advanced Programmable Interrupt Controller                       |
|  ├── Local APIC per CPU + I/O APIC for devices                        |
|  ├── Supports more than 16 IRQs                                       |
|  └── Required for SMP (symmetric multiprocessing)                     |
|                                                                        |
|  2003: AMD64 Long Mode                                                |
|  ├── IDT entries expanded to 16 bytes each                            |
|  ├── 64-bit handler addresses                                         |
|  └── IST (Interrupt Stack Table) for dedicated stacks                 |
|                                                                        |
|  Modern systems: APIC is standard, but 8259 PIC still emulated       |
|  for compatibility (and used during boot before APIC is configured)   |
|                                                                        |
+-----------------------------------------------------------------------+

2.4 Common Misconceptions

Misconception Reality
“All interrupts push error codes” Only specific exceptions push error codes; for others, you must push a dummy value for uniform stack layout
“IRQs start at vector 0” By default they start at 8 (master) and 0x70 (slave), but this conflicts with exceptions. You MUST remap them!
“You can use BIOS for interrupt handling” BIOS uses real-mode IVT; in protected mode you must set up your own IDT
“Interrupt handlers are just regular C functions” They need special entry/exit code to save/restore all state and use IRET
“The CPU disables all interrupts during an ISR” Only interrupt gates disable IF; trap gates do not. And you can manually enable interrupts if needed.
“One PIC handles all 16 IRQs” There are two PICs (master and slave) cascaded together
“The EOI (End of Interrupt) is automatic” You MUST send EOI to the PIC(s) or no more IRQs from that line will be delivered

3. Project Specification

3.1 What You Will Build

A complete interrupt handling subsystem for your x86 kernel that:

  1. Creates an IDT with 256 entries covering all CPU exceptions and hardware IRQs
  2. Implements ISR stubs in assembly that properly save/restore CPU state
  3. Remaps the 8259 PIC to avoid conflicts with CPU exception vectors
  4. Handles CPU exceptions with informative error messages (divide by zero, page fault, etc.)
  5. Handles hardware IRQs including timer interrupts and keyboard input
  6. Provides a common interrupt handler in C that dispatches to specific handlers
  7. Demonstrates working interrupts with a test suite

3.2 Functional Requirements

ID Requirement Description
FR-1 IDT Creation Create 256 IDT entries with correct format
FR-2 IDT Loading Load IDT using LIDT instruction
FR-3 ISR Stubs 256 assembly ISR stubs that save/restore state
FR-4 PIC Remapping Remap master PIC to vectors 0x20-0x27, slave to 0x28-0x2F
FR-5 Exception Handling Handle at least: #DE (0), #UD (6), #DF (8), #GP (13), #PF (14)
FR-6 Timer IRQ Handle IRQ0 (timer) and count ticks
FR-7 Keyboard IRQ Handle IRQ1 (keyboard) and read scancodes
FR-8 EOI Sending Properly acknowledge IRQs to PIC after handling
FR-9 C Handler Common C handler that receives interrupt_frame struct

3.3 Non-Functional Requirements

ID Requirement Description
NFR-1 Modular Code Separate files for IDT, ISRs, PIC, and handlers
NFR-2 Documented Each IDT entry and exception explained in comments
NFR-3 Debuggable Print meaningful information on exceptions
NFR-4 Stable System should not crash from unexpected interrupts
NFR-5 Extensible Easy to add new interrupt handlers

3.4 Example Usage / Output

# Build and run
$ make
nasm -f elf32 -o isr.o isr.asm
i686-elf-gcc -c -ffreestanding -O2 -Wall -o kernel.o kernel.c
i686-elf-gcc -c -ffreestanding -O2 -Wall -o idt.o idt.c
i686-elf-gcc -c -ffreestanding -O2 -Wall -o pic.o pic.c
i686-elf-gcc -c -ffreestanding -O2 -Wall -o handlers.o handlers.c
i686-elf-ld -T linker.ld -o kernel.elf boot.o isr.o kernel.o idt.o pic.o handlers.o
i686-elf-objcopy -O binary kernel.elf kernel.bin

$ qemu-system-i386 -kernel kernel.elf

# Screen output:
================================================================================
                    x86 Kernel - Interrupt Demo
================================================================================

[INIT] Setting up IDT (256 entries)...
[INIT] Remapping 8259 PIC (master: 0x20, slave: 0x28)...
[INIT] Loading IDT at 0x00001000 (limit: 0x07FF)...
[INIT] Enabling interrupts (STI)...

=== Interrupt Test Suite ===

[Test 1] Divide by zero exception
  Triggering: 1/0
  [EXCEPTION 0] Divide Error (#DE)
    EIP: 0x00100234
    Error Code: 0x00000000
  [HANDLED] Exception handled, continuing...

[Test 2] Timer interrupt (IRQ0)
  Waiting for timer ticks...
  [IRQ 0] Timer tick #1 (PIT frequency: ~18.2 Hz)
  [IRQ 0] Timer tick #2
  [IRQ 0] Timer tick #3

[Test 3] Keyboard interrupt (IRQ1)
  Press a key...
  [IRQ 1] Keyboard: scancode 0x1E (key 'A' pressed)
  [IRQ 1] Keyboard: scancode 0x9E (key 'A' released)

[Test 4] Software interrupt (INT 0x80)
  Triggering: INT 0x80
  [INT 0x80] System call handler invoked
    EAX: 0x00000001 (syscall number)

=== All interrupt tests passed! ===

System running with interrupts enabled...
Timer ticks: 47
Press 'Q' to halt system.

3.5 Real World Outcome

After completing this project, you will have:

  1. Working interrupt system that handles both hardware and software interrupts
  2. Foundation for keyboard input - can read key presses
  3. Foundation for preemptive scheduling - timer interrupts for context switching
  4. Exception handling - graceful handling of CPU faults
  5. Understanding of x86 interrupt architecture applicable to real OS development

4. Solution Architecture

4.1 High-Level Design

+-----------------------------------------------------------------------+
|                    INTERRUPT HANDLING ARCHITECTURE                     |
+-----------------------------------------------------------------------+
|                                                                        |
|  Interrupt Sources                   Your Kernel                      |
|                                                                        |
|  ┌─────────────┐                    ┌─────────────────────────────┐   |
|  │   Timer     │─IRQ0──┐            │         IDT (256 entries)   │   |
|  │   (PIT)     │       │            │                             │   |
|  └─────────────┘       │            │  ┌───────────────────────┐  │   |
|                        │            │  │ Vector 0: isr0 ────────────────┐
|  ┌─────────────┐       │            │  │ Vector 1: isr1         │  │   │
|  │  Keyboard   │─IRQ1──┤            │  │ ...                    │  │   │
|  │             │       │            │  │ Vector 13: isr13 ──────────────┤
|  └─────────────┘       │            │  │ Vector 14: isr14       │  │   │
|                        │            │  │ ...                    │  │   │
|  ┌─────────────┐       │            │  │ Vector 32: irq0 ───────────────┤
|  │   Other     │─IRQx──┤            │  │ Vector 33: irq1        │  │   │
|  │   Devices   │       │            │  │ ...                    │  │   │
|  └─────────────┘       │            │  │ Vector 128: int80 ─────────────┤
|                        │            │  └───────────────────────┘  │   │
|  ┌─────────────┐       │            │                             │   │
|  │ CPU (div0,  │───────┘            └─────────────────────────────┘   │
|  │ page fault, │                                                      │
|  │ etc.)       │                                                      │
|  └─────────────┘                                                      │
|                                                                        │
|         │                                                              │
|         │ Interrupt!                                                   │
|         ▼                                                              │
|  ┌─────────────────────────────────────────────────────────────────┐  │
|  │                    ISR STUB (Assembly)                          │◄─┘
|  │                                                                 │
|  │  1. Push error code (dummy 0 if not provided by CPU)           │
|  │  2. Push interrupt number                                       │
|  │  3. PUSHA (save all general-purpose registers)                  │
|  │  4. Push segment registers (DS, ES, FS, GS)                     │
|  │  5. Load kernel data segment (0x10)                             │
|  │  6. CALL isr_handler (C function)                               │
|  │  7. Pop segment registers                                       │
|  │  8. POPA (restore general-purpose registers)                    │
|  │  9. ADD ESP, 8 (remove error code and int number)               │
|  │  10. IRET (return from interrupt)                               │
|  │                                                                 │
|  └─────────────────────────────────────────────────────────────────┘
|         │
|         │ CALL with stack pointer as argument
|         ▼
|  ┌─────────────────────────────────────────────────────────────────┐
|  │                    isr_handler (C)                              │
|  │                                                                 │
|  │  void isr_handler(interrupt_frame* frame) {                     │
|  │      if (frame->int_no < 32) {                                 │
|  │          exception_handler(frame);  // CPU exception            │
|  │      } else if (frame->int_no < 48) {                          │
|  │          irq_handler(frame);        // Hardware IRQ             │
|  │          send_eoi(frame->int_no);   // Acknowledge to PIC       │
|  │      } else {                                                   │
|  │          software_int_handler(frame); // Software interrupt     │
|  │      }                                                          │
|  │  }                                                              │
|  │                                                                 │
|  └─────────────────────────────────────────────────────────────────┘
|         │
|         │ Dispatch to specific handler
|         ▼
|  ┌─────────────────────────────────────────────────────────────────┐
|  │                    Specific Handlers                            │
|  │                                                                 │
|  │  exception_handlers[]:                                          │
|  │    [0]  = divide_error_handler                                  │
|  │    [6]  = invalid_opcode_handler                                │
|  │    [13] = general_protection_handler                            │
|  │    [14] = page_fault_handler                                    │
|  │                                                                 │
|  │  irq_handlers[]:                                                │
|  │    [0]  = timer_handler                                         │
|  │    [1]  = keyboard_handler                                      │
|  │                                                                 │
|  └─────────────────────────────────────────────────────────────────┘
|                                                                        |
+-----------------------------------------------------------------------+

4.2 Key Components

+-----------------------------------------------------------------------+
|                    COMPONENT BREAKDOWN                                 |
+-----------------------------------------------------------------------+
|                                                                        |
|  File Structure:                                                       |
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ isr.asm                                                         │  |
|  │   - ISR stub macros (ISR_NOERR, ISR_ERR)                        │  |
|  │   - 256 individual ISR entry points (isr0...isr255)             │  |
|  │   - Common ISR stub code (save state, call C, restore, iret)    │  |
|  │   - IRQ-specific stubs with PIC handling                        │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ idt.c / idt.h                                                   │  |
|  │   - IDT entry structure (idt_entry_t)                           │  |
|  │   - IDT pointer structure (idt_ptr_t)                           │  |
|  │   - IDT array (idt_entries[256])                                │  |
|  │   - idt_set_gate() function to configure entries                │  |
|  │   - idt_init() function to set up all entries                   │  |
|  │   - idt_load() assembly stub to execute LIDT                    │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ pic.c / pic.h                                                   │  |
|  │   - PIC port definitions (0x20, 0x21, 0xA0, 0xA1)               │  |
|  │   - pic_remap() function                                         │  |
|  │   - pic_send_eoi() function                                      │  |
|  │   - pic_mask_irq() / pic_unmask_irq() functions                  │  |
|  │   - IRQ_OFFSET constant (0x20)                                   │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ handlers.c / handlers.h                                         │  |
|  │   - interrupt_frame structure definition                        │  |
|  │   - isr_handler() - main C interrupt handler                    │  |
|  │   - exception_handler() - handles CPU exceptions                │  |
|  │   - irq_handler() - handles hardware IRQs                       │  |
|  │   - Individual handlers: timer_handler, keyboard_handler, etc.  │  |
|  │   - Exception names table for debug output                       │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ io.h (inline functions)                                         │  |
|  │   - outb(port, value) - write byte to I/O port                  │  |
|  │   - inb(port) - read byte from I/O port                         │  |
|  │   - io_wait() - short delay for PIC initialization              │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

4.3 Data Structures

IDT Entry Structure

// IDT Entry (Gate Descriptor) - 8 bytes
typedef struct {
    uint16_t offset_low;   // Bits 0-15 of handler address
    uint16_t selector;     // Code segment selector (usually 0x08)
    uint8_t  zero;         // Must be zero
    uint8_t  type_attr;    // Type and attributes (see below)
    uint16_t offset_high;  // Bits 16-31 of handler address
} __attribute__((packed)) idt_entry_t;

// type_attr values:
// 0x8E = 10001110 = Present, Ring 0, 32-bit Interrupt Gate
// 0x8F = 10001111 = Present, Ring 0, 32-bit Trap Gate
// 0xEE = 11101110 = Present, Ring 3, 32-bit Interrupt Gate (user-callable)

IDT Pointer Structure

// IDT Pointer - 6 bytes (loaded with LIDT)
typedef struct {
    uint16_t limit;  // Size of IDT - 1 (256 * 8 - 1 = 2047 = 0x7FF)
    uint32_t base;   // Address of IDT
} __attribute__((packed)) idt_ptr_t;

Interrupt Frame Structure

// Matches the stack layout after ISR stub saves state
typedef struct {
    // Pushed by ISR stub (in reverse order)
    uint32_t gs, fs, es, ds;          // Segment registers
    uint32_t edi, esi, ebp, esp_dummy; // PUSHA (esp_dummy is useless)
    uint32_t ebx, edx, ecx, eax;       // PUSHA continued
    uint32_t int_no, err_code;         // Interrupt number and error code

    // Pushed by CPU automatically
    uint32_t eip, cs, eflags;          // Return address and flags
    uint32_t useresp, ss;              // Only present on privilege change
} __attribute__((packed)) interrupt_frame_t;

4.4 Algorithm Overview

IDT Initialization Flow

+-----------------------------------------------------------------------+
|                    IDT INITIALIZATION SEQUENCE                         |
+-----------------------------------------------------------------------+
|                                                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Step 1: Initialize all 256 IDT entries to default handler       │  |
|  │                                                                  │  |
|  │   for (int i = 0; i < 256; i++) {                               │  |
|  │       idt_set_gate(i, (uint32_t)default_handler, 0x08, 0x8E);   │  |
|  │   }                                                              │  |
|  └────────────────────────────┬────────────────────────────────────┘  |
|                               │                                        |
|                               ▼                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Step 2: Set up exception handlers (vectors 0-31)                │  |
|  │                                                                  │  |
|  │   idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E);  // #DE          │  |
|  │   idt_set_gate(1, (uint32_t)isr1, 0x08, 0x8E);  // #DB          │  |
|  │   ...                                                            │  |
|  │   idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E); // #GP         │  |
|  │   idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E); // #PF         │  |
|  │   ...                                                            │  |
|  └────────────────────────────┬────────────────────────────────────┘  |
|                               │                                        |
|                               ▼                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Step 3: Remap the 8259 PIC                                       │  |
|  │                                                                  │  |
|  │   pic_remap(0x20, 0x28);                                         │  |
|  │   // Master PIC: IRQ0-7 → Vectors 0x20-0x27                     │  |
|  │   // Slave PIC:  IRQ8-15 → Vectors 0x28-0x2F                    │  |
|  └────────────────────────────┬────────────────────────────────────┘  |
|                               │                                        |
|                               ▼                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Step 4: Set up IRQ handlers (vectors 32-47)                     │  |
|  │                                                                  │  |
|  │   idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E);  // Timer       │  |
|  │   idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E);  // Keyboard    │  |
|  │   ...                                                            │  |
|  │   idt_set_gate(47, (uint32_t)irq15, 0x08, 0x8E); // Secondary IDE│ |
|  └────────────────────────────┬────────────────────────────────────┘  |
|                               │                                        |
|                               ▼                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Step 5: Set up software interrupt handlers (optional)           │  |
|  │                                                                  │  |
|  │   idt_set_gate(0x80, (uint32_t)isr128, 0x08, 0xEE); // syscall  │  |
|  │   // Note: 0xEE allows Ring 3 to trigger this interrupt         │  |
|  └────────────────────────────┬────────────────────────────────────┘  |
|                               │                                        |
|                               ▼                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Step 6: Load the IDT                                            │  |
|  │                                                                  │  |
|  │   idt_ptr.limit = sizeof(idt_entries) - 1;                      │  |
|  │   idt_ptr.base = (uint32_t)&idt_entries;                        │  |
|  │   idt_load((uint32_t)&idt_ptr);  // LIDT instruction            │  |
|  └────────────────────────────┬────────────────────────────────────┘  |
|                               │                                        |
|                               ▼                                        |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Step 7: Enable interrupts                                       │  |
|  │                                                                  │  |
|  │   asm volatile ("sti");  // Set Interrupt Flag                  │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

PIC Remapping Sequence

+-----------------------------------------------------------------------+
|                    8259 PIC REMAPPING SEQUENCE                         |
+-----------------------------------------------------------------------+
|                                                                        |
|  Why remap?                                                            |
|  - Default: Master PIC IRQs 0-7 → Vectors 0x08-0x0F                   |
|  - Problem: Vectors 0x08-0x0F are used by CPU exceptions!             |
|    - 0x08 = Double Fault (#DF)                                        |
|    - 0x0D = General Protection Fault (#GP)                            |
|  - Solution: Remap IRQs to vectors 0x20-0x2F (32-47)                  |
|                                                                        |
|  Remapping sequence:                                                   |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │                                                                  │  |
|  │  // Save current masks                                           │  |
|  │  uint8_t mask1 = inb(PIC1_DATA);                                 │  |
|  │  uint8_t mask2 = inb(PIC2_DATA);                                 │  |
|  │                                                                  │  |
|  │  // Start initialization sequence (ICW1)                         │  |
|  │  outb(PIC1_COMMAND, 0x11);  // ICW1: Initialize + ICW4 needed   │  |
|  │  io_wait();                                                      │  |
|  │  outb(PIC2_COMMAND, 0x11);  // ICW1 for slave                   │  |
|  │  io_wait();                                                      │  |
|  │                                                                  │  |
|  │  // ICW2: Set vector offsets                                     │  |
|  │  outb(PIC1_DATA, 0x20);     // Master: vectors 0x20-0x27        │  |
|  │  io_wait();                                                      │  |
|  │  outb(PIC2_DATA, 0x28);     // Slave: vectors 0x28-0x2F         │  |
|  │  io_wait();                                                      │  |
|  │                                                                  │  |
|  │  // ICW3: Tell master about slave on IRQ2                        │  |
|  │  outb(PIC1_DATA, 0x04);     // Slave on IR2 (bit 2 set)         │  |
|  │  io_wait();                                                      │  |
|  │  outb(PIC2_DATA, 0x02);     // Slave cascade identity (2)       │  |
|  │  io_wait();                                                      │  |
|  │                                                                  │  |
|  │  // ICW4: Set mode                                               │  |
|  │  outb(PIC1_DATA, 0x01);     // 8086/88 mode                     │  |
|  │  io_wait();                                                      │  |
|  │  outb(PIC2_DATA, 0x01);     // 8086/88 mode                     │  |
|  │  io_wait();                                                      │  |
|  │                                                                  │  |
|  │  // Restore saved masks (or set new ones)                        │  |
|  │  outb(PIC1_DATA, mask1);    // Restore master mask              │  |
|  │  outb(PIC2_DATA, mask2);    // Restore slave mask               │  |
|  │                                                                  │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  ICW (Initialization Command Word) breakdown:                         |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ ICW1 (0x11):                                                    │  |
|  │   Bit 0 = 1: ICW4 needed                                        │  |
|  │   Bit 1 = 0: Cascade mode                                       │  |
|  │   Bit 4 = 1: Initialization                                     │  |
|  │                                                                  │  |
|  │ ICW2: Vector offset (must be multiple of 8)                     │  |
|  │                                                                  │  |
|  │ ICW3: Cascade configuration                                      │  |
|  │   Master: Bit mask of IRQs with slaves (0x04 = IR2)             │  |
|  │   Slave:  Slave ID (0x02 = cascade on IR2)                      │  |
|  │                                                                  │  |
|  │ ICW4 (0x01):                                                    │  |
|  │   Bit 0 = 1: 8086/88 mode (vs MCS-80/85)                        │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

5. Implementation Guide

5.1 Development Environment Setup

Ensure you have the cross-compiler and tools from Project 6:

# Verify cross-compiler
i686-elf-gcc --version

# Verify NASM
nasm --version

# Verify QEMU
qemu-system-i386 --version

5.2 Project Structure

interrupt-kernel/
├── Makefile
├── linker.ld               # Linker script (from Project 6)
├── boot.asm                # Boot sector / multiboot header (from Project 6)
├── src/
│   ├── kernel.c            # Main kernel entry
│   ├── idt.c               # IDT management
│   ├── idt.h               # IDT structures and function declarations
│   ├── isr.asm             # ISR stubs in assembly
│   ├── pic.c               # 8259 PIC programming
│   ├── pic.h               # PIC port definitions and functions
│   ├── handlers.c          # C interrupt handlers
│   ├── handlers.h          # Handler declarations and interrupt_frame
│   ├── io.h                # Port I/O inline functions
│   └── vga.c / vga.h       # VGA text output (from Project 6)
└── README.md

5.3 The Core Question You’re Answering

“How does the CPU know where to go when an interrupt occurs, and how does your handler preserve the interrupted program’s state so it can resume correctly?”

This involves understanding:

  • The IDT as a lookup table: interrupt number → handler address
  • The automatic state saving by the CPU (EFLAGS, CS, EIP, possibly error code)
  • The additional state saving by your ISR stub (general registers, segment registers)
  • The return path via IRET (and how it differs from RET)
  • The PIC’s role in hardware interrupt delivery

5.4 Concepts You Must Understand First

Concept Self-Assessment Question Book/Reference
IDT Entry Format Can you explain each field in an 8-byte IDT entry? Intel SDM Vol 3, Ch 6
Interrupt vs Trap Gates What’s the difference? When would you use each? OSDev Wiki - IDT
CPU Exception Types What’s the difference between faults, traps, and aborts? Intel SDM Vol 3, Ch 6
Stack During Interrupt What does the CPU push? What must you push? Intel SDM Vol 3, Ch 6
8259 PIC Why must you remap it? What’s EOI? OSDev Wiki - PIC
IRET Instruction How does it differ from RET? Intel SDM Vol 2

5.5 Questions to Guide Your Design

IDT Design:

  • How many entries do you need? (256 for all possible vectors)
  • What selector value for all entries? (0x08 = kernel code segment)
  • What type_attr for exception handlers vs IRQ handlers?
  • How will you handle the fact that only some exceptions push error codes?

ISR Stub Design:

  • How will you generate 256 ISR stubs without writing them manually?
  • How will you pass the interrupt number to the C handler?
  • How will you create a uniform stack frame despite some exceptions pushing error codes and others not?
  • What must happen before calling C? After returning from C?

PIC Handling:

  • To what vectors should you remap the PICs? (Usually 0x20-0x2F)
  • When must you send EOI? To which PIC?
  • Should you mask any IRQs initially?

Handler Design:

  • How will you dispatch from the common handler to specific handlers?
  • What information should exception handlers print?
  • What should happen after handling an exception? (halt? continue? depends on type)

5.6 Thinking Exercise

Before coding, trace through what happens when the keyboard is pressed:

1. User presses 'A' key

2. Keyboard controller generates electrical signal
   ↓
3. Signal reaches 8259 master PIC on IR1 (IRQ1)
   ↓
4. PIC checks if IRQ1 is masked → if yes, ignore
   ↓
5. PIC asserts INT signal to CPU
   ↓
6. CPU checks IF flag → if 0, ignore until STI
   ↓
7. CPU finishes current instruction
   ↓
8. CPU acknowledges interrupt, PIC provides vector number
   (After remapping: IRQ1 = vector 33)
   ↓
9. CPU saves state: EFLAGS, CS, EIP → stack
   ↓
10. CPU reads IDT entry at index 33
    ↓
11. CPU loads handler address from IDT entry
    ↓
12. CPU jumps to your ISR stub (irq1)
    ↓
13. Your stub:
    - Push dummy error code (0)
    - Push interrupt number (33)
    - PUSHA
    - Push segment registers
    - Call isr_handler
    ↓
14. C handler:
    - Check interrupt number (33 = IRQ1)
    - Call keyboard_handler()
    - Read scancode from port 0x60
    - Send EOI to master PIC
    - Return
    ↓
15. Your stub:
    - Pop segment registers
    - POPA
    - ADD ESP, 8
    - IRET
    ↓
16. CPU restores EIP, CS, EFLAGS from stack
    ↓
17. Interrupted program continues as if nothing happened

Questions to answer:

  1. At step 9, is the stack pointer the user’s stack or kernel’s stack? (Hint: depends on privilege level)
  2. At step 11, what happens if the IDT entry has Present=0?
  3. At step 15, why ADD ESP, 8? What values are being skipped?
  4. What happens if you forget the EOI at step 14?

5.7 Hints in Layers

Hint 1: Starting Point (Conceptual Direction)

You need:

  1. 256 ISR stubs - Assembly routines that save state and call C
  2. IDT with 256 entries - One for each possible interrupt vector
  3. PIC remapping - Move IRQs to vectors 32-47
  4. C handler - Dispatches to specific handlers based on interrupt number
  5. Specific handlers - Timer, keyboard, exceptions, etc.

The key insight: Use assembly macros to generate ISR stubs without manual repetition.

Hint 2: Next Level (More Specific Guidance)

ISR stub macro pattern (isr.asm):

; For exceptions that DON'T push error code
%macro ISR_NOERR 1
global isr%1
isr%1:
    push dword 0        ; Dummy error code
    push dword %1       ; Interrupt number
    jmp isr_common
%endmacro

; For exceptions that DO push error code
%macro ISR_ERR 1
global isr%1
isr%1:
    push dword %1       ; Interrupt number (error code already pushed)
    jmp isr_common
%endmacro

; Generate stubs
ISR_NOERR 0   ; Divide Error
ISR_NOERR 1   ; Debug
; ...
ISR_ERR 8     ; Double Fault
; ...
ISR_ERR 13    ; General Protection Fault
ISR_ERR 14    ; Page Fault
; ...

IDT setup pattern (idt.c):

void idt_set_gate(uint8_t num, uint32_t handler, uint16_t sel, uint8_t flags) {
    idt_entries[num].offset_low = handler & 0xFFFF;
    idt_entries[num].selector = sel;
    idt_entries[num].zero = 0;
    idt_entries[num].type_attr = flags;
    idt_entries[num].offset_high = (handler >> 16) & 0xFFFF;
}

Hint 3: Technical Details (Approach/Pseudocode)

Common ISR stub (isr.asm):

extern isr_handler

isr_common:
    pusha               ; Save EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI

    mov ax, ds
    push eax            ; Save data segment
    push word 0         ; Align (push es, fs, gs too if needed)

    mov ax, 0x10        ; Load kernel data segment
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    push esp            ; Pass pointer to stack as argument
    call isr_handler
    add esp, 4          ; Remove argument

    pop eax             ; Restore segment (reverse of above)
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    popa                ; Restore general registers
    add esp, 8          ; Remove error code and interrupt number
    iret                ; Return from interrupt

IRQ handler with EOI (handlers.c):

void irq_handler(interrupt_frame_t* frame) {
    uint32_t irq_num = frame->int_no - 32;  // IRQ number (0-15)

    // Call specific handler if registered
    if (irq_handlers[irq_num] != NULL) {
        irq_handlers[irq_num](frame);
    }

    // Send EOI
    if (irq_num >= 8) {
        outb(PIC2_COMMAND, 0x20);  // EOI to slave
    }
    outb(PIC1_COMMAND, 0x20);      // EOI to master
}

Hint 4: Tools/Debugging (Verification Methods)

# Debug with QEMU monitor
qemu-system-i386 -kernel kernel.elf -monitor stdio

# In QEMU monitor:
info idt        # Show IDT entries
info irq        # Show IRQ state
info registers  # Show CPU registers

# Debug with GDB
qemu-system-i386 -kernel kernel.elf -s -S &
gdb -ex "target remote :1234" \
    -ex "set architecture i386" \
    -ex "break isr_handler" \
    -ex "continue"

# In GDB:
(gdb) x/10x $esp    # Examine stack
(gdb) info registers
(gdb) print/x frame->int_no

# Test specific interrupts
(gdb) call *(void(*)())trigger_divide_by_zero
# Or in kernel: asm volatile("int $0");

5.8 The Interview Questions They’ll Ask

  1. “Explain the difference between interrupt gates and trap gates.”
    • Interrupt gates: CPU automatically clears IF flag (disables interrupts)
    • Trap gates: IF flag remains unchanged
    • Use interrupt gates for hardware IRQs (prevent nesting)
    • Use trap gates for exceptions (allow debugger intervention)
  2. “Why must the PIC be remapped?”
    • Default vectors conflict with CPU exceptions
    • Master PIC: IRQ0-7 → 0x08-0x0F overlaps with #DF, #TS, etc.
    • Must move to unused range (typically 0x20+)
    • Without remapping, timer interrupt looks like Double Fault!
  3. “What happens if you forget to send EOI?”
    • PIC thinks you’re still handling the interrupt
    • All IRQs at that priority or lower are blocked
    • No more interrupts from that line until EOI
    • System appears to freeze (no keyboard, timer stops)
  4. “Walk through what happens on a page fault.”
    • CPU detects page not present (or permission error)
    • CPU pushes error code (with specific bits for cause)
    • CPU pushes EIP, CS, EFLAGS
    • CPU reads IDT entry 14
    • Jumps to handler
    • Handler can: allocate page, map it, return (fault is “fixed”)
    • Or: print error and halt (can’t recover)
  5. “How would you implement nested interrupts?”
    • Allow higher-priority interrupts to preempt lower
    • Re-enable interrupts in handler (careful!)
    • Requires priority tracking
    • Must save complete state on each nesting level
    • EOI must be sent in correct order
  6. “What’s the difference between INT instruction and hardware IRQ?”
    • INT is synchronous (happens at specific instruction)
    • IRQ is asynchronous (can happen anytime)
    • INT error code behavior depends on instruction
    • Both use IDT for dispatch
    • DPL in IDT controls who can trigger INT (not relevant for IRQs)

5.9 Books That Will Help

Topic Book Chapter/Section
x86 Interrupts Intel SDM Volume 3 Chapter 6
Exception Types Intel SDM Volume 3 Chapter 6.4-6.5
IDT Format Intel SDM Volume 3 Chapter 6.10-6.11
8259 PIC OSDev Wiki PIC section
OS Interrupt Handling “Operating Systems: Three Easy Pieces” Chapter 33 (Mechanism: Limited Direct Execution)
Real-time Interrupts “Making Embedded Systems” Chapter 7

5.10 Implementation Phases

Phase 1: IDT Infrastructure (2-3 days)

  • Create IDT entry structure
  • Implement idt_set_gate() function
  • Create idt_ptr structure
  • Implement idt_load() assembly (LIDT)
  • Verify IDT is loaded (QEMU monitor)

Phase 2: Exception ISR Stubs (3-4 days)

  • Create ISR_NOERR and ISR_ERR macros
  • Generate stubs for vectors 0-31
  • Implement isr_common stub
  • Create basic isr_handler in C
  • Test with intentional divide-by-zero

Phase 3: PIC Remapping (1-2 days)

  • Implement port I/O functions (inb, outb)
  • Implement pic_remap()
  • Implement pic_send_eoi()
  • Verify with QEMU monitor (info irq)

Phase 4: IRQ Handling (2-3 days)

  • Create IRQ stubs (irq0-irq15)
  • Extend isr_handler for IRQ dispatch
  • Implement timer_handler
  • Verify timer interrupts working

Phase 5: Keyboard Handler (1-2 days)

  • Implement keyboard_handler
  • Read scancodes from port 0x60
  • Display key presses
  • Handle key release (bit 7)

Phase 6: Polish and Testing (2-3 days)

  • Create comprehensive test suite
  • Handle all common exceptions gracefully
  • Add debug output (register dumps)
  • Document everything

5.11 Key Implementation Decisions

Decision Option A Option B Recommendation
Gate type for exceptions Interrupt Gate (0x8E) Trap Gate (0x8F) Trap Gate for most exceptions (allows debugging)
Gate type for IRQs Interrupt Gate (0x8E) Trap Gate (0x8F) Interrupt Gate (auto-disable interrupts)
Stack for interrupt frame Use current stack Dedicated kernel stack Current stack for now; dedicated for user mode
Error code handling Check per-vector Always push dummy Always push dummy for uniform frame
EOI timing Before handler After handler After handler (handler might need to read device)

6. Testing Strategy

6.1 Unit Tests

// test_idt.c - Verify IDT setup

void test_idt_gate_format(void) {
    // Set a test gate
    idt_set_gate(42, 0xDEADBEEF, 0x08, 0x8E);

    // Verify fields
    assert(idt_entries[42].offset_low == 0xBEEF);
    assert(idt_entries[42].offset_high == 0xDEAD);
    assert(idt_entries[42].selector == 0x08);
    assert(idt_entries[42].type_attr == 0x8E);
    assert(idt_entries[42].zero == 0);

    print("test_idt_gate_format: PASSED\n");
}

void test_all_gates_present(void) {
    for (int i = 0; i < 256; i++) {
        // Check Present bit is set
        assert(idt_entries[i].type_attr & 0x80);
    }
    print("test_all_gates_present: PASSED\n");
}

6.2 Integration Tests

// test_interrupts.c

volatile int divide_error_count = 0;
volatile int timer_tick_count = 0;
volatile int keyboard_event_count = 0;

void test_divide_error(void) {
    print("[TEST] Triggering divide by zero...\n");
    divide_error_count = 0;

    // Trigger divide error
    volatile int a = 1;
    volatile int b = 0;
    volatile int c = a / b;  // Should trigger #DE
    (void)c;

    // Handler should have been called
    assert(divide_error_count == 1);
    print("[TEST] Divide error: PASSED\n");
}

void test_timer_interrupts(void) {
    print("[TEST] Waiting for 3 timer ticks...\n");
    timer_tick_count = 0;

    // Wait for 3 ticks
    while (timer_tick_count < 3) {
        asm volatile ("hlt");  // Halt until interrupt
    }

    print("[TEST] Timer interrupts: PASSED\n");
}

void test_software_interrupt(void) {
    print("[TEST] Triggering INT 0x80...\n");

    asm volatile ("int $0x80");

    // Verify handler was called (check some flag)
    print("[TEST] Software interrupt: PASSED\n");
}

6.3 Verification Commands

#!/bin/bash
# test_interrupts.sh

# Build
make clean && make

# Test 1: Kernel boots with IDT
echo "Test 1: Boot with IDT"
timeout 5 qemu-system-i386 -kernel kernel.elf -nographic &
PID=$!
sleep 2
if ps -p $PID > /dev/null; then
    echo "PASS: Kernel running"
    kill $PID
else
    echo "FAIL: Kernel crashed"
    exit 1
fi

# Test 2: Check IDT with QEMU monitor
echo "Test 2: IDT verification"
echo "info idt" | qemu-system-i386 -kernel kernel.elf -monitor stdio -nographic &
sleep 2
kill %1

# Test 3: Debug session
echo "Test 3: GDB verification"
qemu-system-i386 -kernel kernel.elf -s -S -nographic &
sleep 1
gdb -batch -ex "target remote :1234" \
    -ex "break isr_handler" \
    -ex "continue" \
    -ex "info frame" || true
kill %1

echo "All tests completed"

6.4 GDB Debug Session for Interrupts

# Terminal 1: Start QEMU
qemu-system-i386 -kernel kernel.elf -s -S -nographic

# Terminal 2: GDB
gdb kernel.elf

(gdb) target remote :1234
(gdb) set architecture i386

# Break on interrupt handler entry
(gdb) break isr_handler

# Continue and trigger an interrupt (keyboard, timer, etc.)
(gdb) continue

# When it breaks, examine the interrupt frame
(gdb) print *((interrupt_frame_t*)$esp)

# See the interrupt number
(gdb) print frame->int_no

# Step through handler
(gdb) step
(gdb) next

# Examine IDT
(gdb) x/8bx &idt_entries[0]   # First IDT entry
(gdb) x/8bx &idt_entries[14]  # Page fault entry

7. Common Pitfalls & Debugging

Pitfall 1: Forgetting to Push Dummy Error Code

Symptom: Stack misaligned, C handler receives wrong values.

Cause: Some exceptions push error code, some don’t. If you don’t push a dummy for those that don’t, your stack frame is off by 4 bytes.

Fix:

; Exceptions WITHOUT error code need dummy
ISR_NOERR 0   ; Divide Error
ISR_NOERR 1   ; Debug
ISR_NOERR 2   ; NMI
; ... (most exceptions)

; Exceptions WITH error code - no dummy
ISR_ERR 8     ; Double Fault
ISR_ERR 10    ; Invalid TSS
ISR_ERR 11    ; Segment Not Present
ISR_ERR 12    ; Stack-Segment Fault
ISR_ERR 13    ; General Protection
ISR_ERR 14    ; Page Fault
ISR_ERR 17    ; Alignment Check

Exceptions that push error codes: 8, 10, 11, 12, 13, 14, 17, 21 (newer CPUs)

Pitfall 2: Not Remapping PIC

Symptom: Timer interrupt triggers “Double Fault” handler, or system immediately triple faults after STI.

Cause: Master PIC IRQ0 is vector 0x08, which is the Double Fault exception vector.

Fix: Always remap PIC before enabling interrupts:

void pic_remap(uint8_t offset1, uint8_t offset2) {
    // offset1 typically 0x20, offset2 typically 0x28
    // See implementation details in Section 4.4
}

// In init:
pic_remap(0x20, 0x28);  // IRQs now at 0x20-0x2F
idt_init();             // Set up IDT
asm volatile ("sti");   // NOW safe to enable interrupts

Pitfall 3: Forgetting EOI

Symptom: First interrupt works, then no more interrupts ever.

Cause: PIC requires acknowledgment (EOI) before it will send more interrupts.

Fix:

void irq_handler(interrupt_frame_t* frame) {
    uint32_t irq = frame->int_no - 32;

    // Handle the IRQ...

    // CRITICAL: Send EOI
    if (irq >= 8) {
        outb(0xA0, 0x20);  // EOI to slave
    }
    outb(0x20, 0x20);      // EOI to master (always)
}

Pitfall 4: Using RET Instead of IRET

Symptom: System crashes after first interrupt.

Cause: RET only pops EIP. IRET pops EIP, CS, and EFLAGS.

Fix:

isr_common:
    ; ... save state ...
    call isr_handler
    ; ... restore state ...
    add esp, 8      ; Remove error code and int number
    iret            ; NOT ret!

Pitfall 5: Wrong Segment Selector in IDT Entries

Symptom: Triple fault when interrupt occurs.

Cause: IDT entry points to wrong code segment or non-present segment.

Fix:

// Selector should be kernel code segment from GDT
#define KERNEL_CS 0x08

void idt_init(void) {
    idt_set_gate(0, (uint32_t)isr0, KERNEL_CS, 0x8E);
    // ...
}

Pitfall 6: Not Saving All Registers

Symptom: Interrupted code behaves strangely after interrupt.

Cause: Interrupt handler modified a register that the interrupted code was using.

Fix: Save ALL general purpose registers and segment registers:

isr_common:
    pusha           ; Save EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI

    ; Also save segment registers
    push ds
    push es
    push fs
    push gs

    mov ax, 0x10    ; Load kernel data segment
    mov ds, ax
    mov es, ax

    ; ... call handler ...

    pop gs          ; Restore in reverse order
    pop fs
    pop es
    pop ds

    popa            ; Restore general registers

Pitfall 7: Spurious IRQ7 / IRQ15

Symptom: Unexpected IRQ7 or IRQ15 with no apparent cause.

Cause: Electrical noise or race condition in PIC causes spurious interrupt.

Fix:

void irq7_handler(interrupt_frame_t* frame) {
    // Check if this is a real IRQ7 or spurious
    outb(0x20, 0x0B);  // Read In-Service Register
    if ((inb(0x20) & 0x80) == 0) {
        // Spurious! Don't send EOI
        return;
    }
    // Real IRQ7 - handle and send EOI
    outb(0x20, 0x20);
}

Debugging with QEMU

# See interrupt activity
qemu-system-i386 -kernel kernel.elf -d int 2>&1 | head -100

# Get detailed CPU exceptions
qemu-system-i386 -kernel kernel.elf -d int,cpu_reset

# Interactive monitor
qemu-system-i386 -kernel kernel.elf -monitor stdio
(qemu) info idt      # Show IDT
(qemu) info irq      # Show IRQ activity
(qemu) info registers

8. Extensions & Challenges

Extension 1: Keyboard Driver with Scancode to ASCII (Medium)

// Extend keyboard handler to convert scancodes to characters
static char scancode_to_ascii[128] = {
    0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
    '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
    0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
    0, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0,
    '*', 0, ' ', /* ... */
};

void keyboard_handler(interrupt_frame_t* frame) {
    uint8_t scancode = inb(0x60);

    if (!(scancode & 0x80)) {  // Key press (not release)
        char c = scancode_to_ascii[scancode];
        if (c) {
            print_char(c);
        }
    }
}

Extension 2: Stack Trace on Exception (Advanced)

void exception_handler(interrupt_frame_t* frame) {
    print_exception_info(frame);

    // Walk the stack for return addresses
    print("Stack trace:\n");
    uint32_t* ebp = (uint32_t*)frame->ebp;
    for (int i = 0; i < 10 && ebp; i++) {
        uint32_t eip = ebp[1];  // Return address
        print("  [%d] 0x%x\n", i, eip);
        ebp = (uint32_t*)ebp[0];  // Previous EBP
        if ((uint32_t)ebp < 0x1000) break;  // Sanity check
    }
}

Extension 3: Programmable Interval Timer (PIT) Configuration (Medium)

// Configure PIT for specific frequency (default ~18.2 Hz)
#define PIT_FREQ 1193182  // Base frequency in Hz

void pit_set_frequency(uint32_t freq) {
    uint16_t divisor = PIT_FREQ / freq;

    outb(0x43, 0x36);  // Channel 0, lobyte/hibyte, rate generator
    outb(0x40, divisor & 0xFF);         // Low byte
    outb(0x40, (divisor >> 8) & 0xFF);  // High byte
}

// In init:
pit_set_frequency(100);  // 100 Hz = 10ms per tick

Extension 4: Nested Interrupt Support (Expert)

// Allow higher-priority interrupts during handling
void irq_handler_with_nesting(interrupt_frame_t* frame) {
    uint32_t irq = frame->int_no - 32;

    // Send EOI early (allows same-priority or lower to queue)
    if (irq >= 8) outb(0xA0, 0x20);
    outb(0x20, 0x20);

    // Re-enable interrupts (DANGEROUS - must be careful!)
    asm volatile ("sti");

    // Now higher-priority IRQs can interrupt us
    if (irq_handlers[irq]) {
        irq_handlers[irq](frame);
    }

    // Disable interrupts before returning
    asm volatile ("cli");
}

Extension 5: System Call Interface (Advanced)

// Implement INT 0x80 as system call entry
// IDT entry with DPL=3 so user code can call it

void syscall_handler(interrupt_frame_t* frame) {
    uint32_t syscall_num = frame->eax;
    uint32_t arg1 = frame->ebx;
    uint32_t arg2 = frame->ecx;
    uint32_t arg3 = frame->edx;

    switch (syscall_num) {
        case 1:  // sys_write
            frame->eax = sys_write(arg1, arg2, arg3);
            break;
        case 2:  // sys_read
            frame->eax = sys_read(arg1, arg2, arg3);
            break;
        // ... more syscalls ...
        default:
            frame->eax = -1;  // Unknown syscall
    }
}

9. Real-World Connections

9.1 How This Relates to Production Systems

This Project Real-World Equivalent
IDT setup Linux arch/x86/kernel/idt.c
ISR stubs Linux arch/x86/entry/entry_32.S
PIC handling Linux arch/x86/kernel/i8259.c (legacy), APIC for modern
Exception handlers Linux arch/x86/mm/fault.c (page fault), etc.
Timer interrupt Linux kernel/time/clockevents.c
Keyboard driver Linux drivers/input/keyboard/atkbd.c

9.2 Industry Applications

+-----------------------------------------------------------------------+
|                    INTERRUPT HANDLING IN INDUSTRY                      |
+-----------------------------------------------------------------------+
|                                                                        |
|  Operating Systems:                                                    |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ Linux:    IDT, APIC, softirqs, tasklets, workqueues            │  |
|  │ Windows:  IDT, HAL, Interrupt Request Levels (IRQLs)           │  |
|  │ macOS:    Mach interrupt handling, I/O Kit                     │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Real-Time Systems:                                                    |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ - Deterministic interrupt latency (microseconds)               │  |
|  │ - Priority-based interrupt handling                            │  |
|  │ - Hard deadlines (automotive, aerospace, medical)              │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Embedded Systems:                                                     |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ - Direct interrupt vector tables (ARM NVIC)                    │  |
|  │ - Minimal latency for sensor processing                        │  |
|  │ - Power management via interrupt-driven sleep                   │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Security Research:                                                    |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ - SMM (System Management Mode) interrupts                      │  |
|  │ - Rootkits hooking IDT entries                                 │  |
|  │ - Hypervisor interrupt virtualization                          │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
|  Virtualization:                                                       |
|  ┌─────────────────────────────────────────────────────────────────┐  |
|  │ - Virtual APIC emulation                                        │  |
|  │ - Posted interrupts (Intel VT-x)                               │  |
|  │ - Interrupt injection into guests                               │  |
|  └─────────────────────────────────────────────────────────────────┘  |
|                                                                        |
+-----------------------------------------------------------------------+

9.3 Career Relevance

Understanding interrupt handling is essential for:

  • Kernel developers: All OS kernels handle interrupts
  • Embedded engineers: Direct hardware interrupt handling
  • Systems programmers: Understanding system call entry
  • Security researchers: Rootkit analysis, vulnerability research
  • Performance engineers: Interrupt latency optimization
  • Hypervisor developers: Interrupt virtualization

10. Resources

10.1 Official Documentation

10.2 OSDev Wiki Articles

10.3 Tutorials

10.4 Source Code Examples

10.5 Books

Book Relevant Chapter
“Operating Systems: Three Easy Pieces” Ch. 33 - Limited Direct Execution
“Intel SDM Volume 3” Chapter 6 - Interrupt and Exception Handling
“Linux Kernel Development” by Love Ch. 7 - Interrupts and Interrupt Handlers
“Making Embedded Systems” by White Ch. 7 - Interrupts
“Write Great Code, Vol 2” by Hyde Ch. 17 - Interrupt Processing

11. Self-Assessment Checklist

Understanding

  • I can explain each field in an 8-byte IDT entry
  • I understand the difference between interrupt gates and trap gates
  • I know which exceptions push error codes and which don’t
  • I can trace through the complete interrupt handling flow
  • I understand why PIC must be remapped
  • I can explain what EOI is and why it’s necessary
  • I know the difference between IRET and RET

Implementation

  • My IDT has 256 valid entries
  • ISR stubs correctly save and restore all registers
  • PIC is remapped to non-conflicting vectors (0x20+)
  • Exception handlers print useful debugging information
  • Timer interrupt increments a counter
  • Keyboard interrupt reads and displays scancodes
  • All IRQs properly send EOI to PIC

Skills

  • I can debug interrupt issues with QEMU monitor
  • I can set breakpoints in interrupt handlers with GDB
  • I understand the assembly syntax for ISR stubs
  • I can add new interrupt handlers to my kernel
  • I can explain my implementation to others

12. Submission / Completion Criteria

Your project is complete when:

12.1 Build Requirements

  • Builds with make using i686-elf-gcc and NASM
  • Produces a bootable kernel.elf or kernel.bin
  • All source files are organized and documented

12.2 Functional Requirements

  • IDT loaded: 256 entries, verified with info idt in QEMU
  • Exceptions work: Divide by zero is caught and handled gracefully
  • Timer works: IRQ0 fires regularly, tick count increments
  • Keyboard works: IRQ1 fires on keypress, scancode displayed
  • EOI works: Multiple interrupts can occur (not stuck after first)

12.3 Code Quality

  • Clear separation: idt.c, isr.asm, pic.c, handlers.c
  • Comments explain IDT entry format, PIC initialization sequence
  • Exception names table for readable debug output
  • Consistent coding style

12.4 Documentation

  • README explains how to build and run
  • Vector assignments documented (which vector = which handler)
  • Known limitations noted

12.5 Verification Commands

# Build
make clean && make

# Run and observe output
qemu-system-i386 -kernel kernel.elf

# Verify IDT in QEMU monitor
(qemu) info idt

# Trigger divide by zero (in kernel code)
# Should see exception message, not crash

# Press keyboard keys
# Should see scancode output

# Watch timer ticks accumulate

12.6 Expected Output Example

================================================================================
                    x86 Kernel - Interrupt System v1.0
================================================================================

[INIT] IDT: 256 entries at 0x00001000
[INIT] PIC: Remapped to 0x20-0x2F
[INIT] Enabling interrupts... done.

[IRQ 0] Timer tick: 1
[IRQ 0] Timer tick: 2
[IRQ 0] Timer tick: 3

[KEYBOARD] Press any key...
[IRQ 1] Scancode: 0x1E (A pressed)
[IRQ 1] Scancode: 0x9E (A released)

[TEST] Triggering divide by zero...
[EXCEPTION 0] Divide Error (#DE)
  EIP=0x00100234 CS=0x0008 EFLAGS=0x00000202
  EAX=0x00000001 EBX=0x00000000 ECX=0x00000000 EDX=0x00000000
  Error Code: 0x00000000
[Halted - cannot continue after #DE]

System halted after 47 timer ticks.

Congratulations! Completing this project means you understand one of the most fundamental aspects of operating system design. Your kernel can now:

  • Respond to hardware events (keyboard, timer)
  • Handle CPU exceptions gracefully
  • Serve as a foundation for preemptive multitasking (Project 10)
  • Serve as a foundation for system calls (user programs calling kernel)

You’ve built the nervous system of your operating system. Next up: Project 8 (Memory Management - Paging) to give your kernel virtual memory!