Lesson 09-Interrupts and Exceptions

Interrupt Vector Table Architecture

Interrupt Descriptor Table (IDT)

In x86 protected mode (32/64-bit), the IDT replaces the real-mode IVT, providing more flexible interrupt management through Gate Descriptors.

IDT Structure:

  • 256 gate descriptors (vectors 0–255)
  • Each descriptor is 8 bytes (in 32-bit mode) or 16 bytes (in 64-bit mode)
  • Stored in linear address space of memory
  • Located by the IDTR register

Location:
Set by the operating system kernel, typically in a fixed memory region (e.g., idt_table in Linux).

Size:
256 interrupt vectors (0–255), each pointing to an 8-byte (32-bit) or 16-byte (64-bit) gate descriptor.

Gate Types:

  • Task Gate: Used for task switching (rare; modern systems prefer interrupt gates).
  • Interrupt Gate: Disables interrupt nesting (clears the interrupt flag IF during handling).
  • Trap Gate: Allows interrupt nesting (preserves the IF flag during handling).

Loading IDTR:

lidt [idtr_struct] ; Load IDT base address and limit

IDT Gate Descriptor Types:

TypeValueDescription
Task Gate0x5Task switching
Interrupt Gate0xEInterrupt handling
Trap Gate0xFException handling

Key Fields of a 32-bit Gate Descriptor:

FieldLengthDescription
Segment Selector16 bitsPoints to a segment in GDT/LDT, determining code segment privilege.
Offset (low 16 bits)16 bitsLow 16 bits of the interrupt handler entry address.
P (Present bit)1 bit1 = valid, 0 = invalid (access triggers an exception).
DPL (Descriptor Privilege Level)2 bitsPrivilege level (0–3) of the handler; restricts who can invoke it.
Segment Present Bit1 bitIndicates whether the segment is present in memory (usually 1 in modern systems).
D/B (Default Operation Size)1 bit1 = 32-bit operands, 0 = 16-bit (default 1 in 32-bit mode).
G (Granularity)1 bit1 = offset aligned to 4-byte boundaries (32-bit mode); 0 = byte-aligned (rare).
Offset (high 16 bits)16 bitsHigh 16 bits of the interrupt handler entry address (32-bit mode).

Example: IDT Initialization in Linux
The Linux kernel initializes the IDT at boot via the setup_idt() function, setting a gate descriptor for each vector. For example, the timer interrupt (IRQ0) corresponds to vector 0x20, whose gate descriptor points to the timer_interrupt handler.

Interrupt Vector Table (IVT)

In real mode (16-bit), the IVT is a 1KB region at memory addresses 0x00000–0x003FF, directly storing 256 interrupt vectors as segment:offset pairs (2 bytes each).

IVT Structure

  • Interrupt Vector: Each interrupt number (0–255) maps to a 4-byte entry (segment + offset).
  • Default Handlers: BIOS initializes the IVT at startup. Examples:
    • Interrupt 0x00 (divide error) → divide_error handler.
    • Interrupt 0x10 (video services) → BIOS video routines.

Real-Mode IVT Characteristics:

  • Fixed at memory address 0x0000:0x0000
  • Each entry is 4 bytes (segment:offset)
  • Supports interrupts 0–255

IVT Initialization Example:

; Set handler for interrupt 0 (divide-by-zero exception)
mov word [0x00], handler_offset
mov word [0x02], handler_segment

Interrupt Handling Mechanism

Interrupt handling is the core process for responding to asynchronous events, involving four stages: hardware trigger, CPU response, kernel processing, and return to the original program.

Hardware Interrupt Architecture

PIC (Programmable Interrupt Controller)

  • Master PIC (IRQ0–7): Maps to interrupt vectors 08h–0Fh
  • Slave PIC (IRQ8–15): Maps to interrupt vectors 70h–77h
  • Cascaded: IRQ2 of the master PIC connects to the slave PIC

APIC (Advanced Programmable Interrupt Controller) Features

  • Supports 24+ interrupt lines
  • Distributes interrupts across multiple processors
  • Enables Message Signaled Interrupts (MSI)

IRQ to Interrupt Vector Mapping:

  • IRQ0 → INT 08h (timer)
  • IRQ1 → INT 09h (keyboard)
  • IRQ8 → INT 70h (real-time clock)
  • …

Hardware Interrupt System: Peripheral “Help Signals”

Hardware interrupts are requests sent by peripherals (e.g., keyboard, disk) to the CPU via an interrupt controller (e.g., 8259A PIC, APIC).

Interrupt Request (IRQ) Triggering

  • Edge-Triggered: Triggered on signal transition (e.g., low-to-high on key release).
  • Level-Triggered: Triggered while signal remains high/low (e.g., disk data ready).

Role of Interrupt Controllers

  • PIC (Programmable Interrupt Controller): The 8259A is the classic x86 real-mode controller that aggregates peripheral IRQs and sends them to the CPU’s INTR pin.
  • APIC (Advanced Programmable Interrupt Controller): Modern x86 multi-processor interrupt controller supporting distributed routing (e.g., local APIC receives requests from I/O APIC).

Interrupt Handling Flow

Protected Mode Interrupt Handling Steps

  1. CPU saves context (SS, ESP, EFLAGS, CS, EIP, Error Code)
  2. Looks up gate descriptor in IDT
  3. Performs privilege check (CPL vs. DPL)
  4. Switches to kernel stack
  5. Executes interrupt service routine (ISR)
  6. Returns via IRET

Context Save (Context Save)

  • Flags Register (EFLAGS/RFLAGS): Saves current state (e.g., interrupt flag IF, direction flag DF).
  • CS:EIP/RIP: Saves code segment and instruction pointer (return address).
  • General-Purpose Registers (EAX/EBX/…): Saved by the kernel as needed.

Interrupt Acknowledge

  • Real Mode: CPU sends INTA pulses; peripheral places vector number on data bus; CPU uses it to index IVT.
  • Protected Mode: CPU uses IDTR (which holds IDT base and limit) to locate the IDT, fetches the gate descriptor, validates privilege, then jumps.

Interrupt Service Routine (ISR)

  • Kernel Execution: Runs in kernel mode (Ring 0), with access to hardware and global data.
  • Interrupt Nesting Control: Interrupt gates clear IF (CLI) to prevent nesting; trap gates preserve IF.

Context Restore
Restores general-purpose registers, flags, CS:EIP/RIP, then executes IRET.

Interrupt Handler Template:

; 32-bit interrupt handler template
section .text
global _irq_handler
_irq_handler:
    pusha                   ; Save general-purpose registers
    push ds
    push es
    push fs
    push gs
    mov ax, 0x10            ; Kernel data segment selector
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    call irq_handler_c      ; Call C handler
    pop gs
    pop fs
    pop es
    pop ds
    popa                    ; Restore general-purpose registers
    add esp, 4              ; Skip error code (if present)
    iret                    ; Return from interrupt

Exception Handling Mechanism

Exceptions are errors detected during program execution (e.g., divide-by-zero, illegal memory access), triggered by CPU hardware or software. Their handling resembles interrupts but is more urgent.

Exception Classification

x86 defines 20 predefined exceptions (vectors 0–19). Common types include:

VectorNameTrigger Condition
0Divide ErrorDivision by zero or signed overflow
4OverflowArithmetic result exceeds data type range (e.g., 127+1 in 8-bit signed)
13General Protection FaultUnauthorized segment access (e.g., kernel accessing user memory)
14Page FaultAccessed page not in memory (requires loading or swapping)
21Alignment CheckUnaligned memory access (e.g., non-8-byte-aligned double on x86-64)

Exception Categories:

  • Fault: Recoverable (e.g., page fault); CPU state can be fixed; re-executes the faulting instruction.
  • Trap: Reported after execution (e.g., debug breakpoint); EIP points to next instruction.
  • Abort: Unrecoverable (e.g., hardware failure); terminates program.

Common Exception Handling

Page Fault (INT 0Eh)
Trigger Condition: Program accesses an unmapped virtual address (e.g., first access to unloaded code segment).

Handling Steps:

  1. CPU halts execution, saves context, jumps to page fault handler (IDT vector 14).
  2. Kernel inspects error via CR2 register (holds faulting address) to distinguish page-not-present vs. access-violation.
  3. If page-not-present: loads page from disk (swap), updates page tables. If access violation: terminates process (sends SIGSEGV).
  4. Flushes TLB, re-executes the faulting instruction.

Divide-by-Zero Exception (INT 00h):

; Divide-by-zero exception handler example
_divide_error:
    push error_message
    call print_string
    jmp halt_system
error_message db "Divide by Zero Error!", 0

Page Fault Handler Framework:

; Page fault handler skeleton
_page_fault:
    ; Get fault address from CR2
    mov eax, cr2
    ; Check if access is valid
    test [page_table], eax
    jz _kill_process
    ; Allocate physical page and update page table
    call alloc_page
    mov [page_table], eax
    ; Resume execution
    add esp, 4      ; Skip error code
    iret

Software Interrupt Mechanism

Software interrupts are explicitly triggered by programs to implement system calls (user-to-kernel transitions) or custom event handling.

System Call Implementation

System calls (e.g., open, read) let user programs request kernel services via software interrupts.

x86 System Call Methods

  • 32-bit: Use int 0x80; eax = syscall number; ebx, ecx, etc. = arguments.
  • 64-bit: Use syscall (faster); rax = syscall number; rdi, rsi, etc. = arguments.

Example: Linux write System Call (64-bit)

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count) {
    return syscall(SYS_write, fd, buf, count); // glibc wrapper
}

At the assembly level, syscall:

  1. Saves user registers to kernel stack.
  2. Switches to Ring 0.
  3. Jumps to kernel handler (e.g., sys_write).

Windows System Calls
Uses NT system calls via ntdll.dll (Nt* functions), ultimately using syscall, but with different interfaces (e.g., NtReadFile ≈ Linux read).

Traditional int 0x80 Method:

; Linux system call example
mov eax, 4      ; sys_write
mov ebx, 1      ; stdout
mov ecx, msg    ; buffer address
mov edx, len    ; length
int 0x80

section .data
msg db "Hello", 0x0A
len equ $ - msg

Fast syscall Method:

; x86_64 syscall example
mov rax, 1      ; sys_write
mov rdi, 1      ; stdout
mov rsi, msg    ; buffer address
mov rdx, len    ; length
syscall
; Syscall numbers defined in arch/x86/entry/syscalls/syscall_64.tbl

Custom Interrupt Handling

Developers can implement custom interrupt handlers by setting interrupt vectors or using signals.

Linux Signal-Based Custom Handling
Register a signal handler (e.g., for SIGALRM) via sigaction:

#include <signal.h>
#include <unistd.h>

void alarm_handler(int sig) {
    printf("Alarm triggered!");
}

int main() {
    struct sigaction sa;
    sa.sa_handler = alarm_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGALRM, &sa, NULL); // Register SIGALRM handler
    alarm(5);                      // Trigger SIGALRM in 5 seconds
    pause();                       // Wait for signal
    return 0;
}

Custom Hardware Interrupt in Kernel (Linux)
Use request_irq in a kernel module to register a handler for a specific IRQ:

#include <linux/interrupt.h>

irqreturn_t my_irq_handler(int irq, void *dev_id) {
    printk(KERN_INFO "Custom IRQ %d triggered!", irq);
    return IRQ_HANDLED;
}

static int __init my_init(void) {
    int irq = 10; // Request IRQ10
    request_irq(irq, my_irq_handler, IRQF_SHARED, "my_irq", NULL);
    return 0;
}

Setting a Custom Software Interrupt:

; Set handler for INT 80h
mov word [idt + 80h*8], handler_offset
mov word [idt + 80h*8 + 2], handler_segment
mov word [idt + 80h*8 + 4], 0x8E00 ; 32-bit interrupt gate

; Interrupt handler
handler:
    pusha
    ; Custom logic here
    popa
    iret

Advanced Interrupt Techniques

Interrupt Nesting and Priority

Modern systems support interrupt nesting, where a higher-priority interrupt can preempt a lower-priority one, controlled by IRQ priority.

APIC Priority Mechanism

  • Each IRQ assigned priority 0–15 (0 = highest).
  • CPU compares incoming priority with current:
    • If incoming > current → allow nesting.
    • If incoming ≤ current → defer until current ISR finishes.

Example: Keyboard (IRQ1) vs. Timer (IRQ0)
Timer interrupts usually have higher priority. If a timer ISR is running when a keyboard interrupt arrives, the keyboard ISR is deferred.

PIC Priority Order:
IRQ0 (highest) → IRQ7 (lowest)
IRQ8 → IRQ15

APIC Priority Control:

; Set Task Priority Register (TPR)
mov al, 0x10        ; Set priority threshold
out 0xF0, al        ; APIC_TPR port

Interrupt Masking and Control

Interrupt Masking disables interrupts via CLI (clears IF), protecting critical sections (e.g., kernel data structure updates).

Use Cases:

  • Kernel critical sections (e.g., modifying scheduler queues)
  • Real-time tasks (prevent low-priority interrupt interference)

Risks:
Long masking causes system unresponsiveness (e.g., keyboard lag). Minimize duration (e.g., use spin_lock instead of long CLI).

PIC Mask Register Operations:

; Mask IRQ2
in al, 0x21         ; Read master PIC mask
or al, 0x04         ; Set bit 2
out 0x21, al        ; Write back

; Enable slave PIC IRQ8
in al, 0xA1         ; Read slave PIC mask
and al, 0xFE        ; Clear bit 0
out 0xA1, al        ; Write back

Local APIC Control:

; Mask LINT0 interrupt
mov edx, 0x00000800 ; APIC_LVT_LINT0
mov eax, [edx]
or eax, 0x00010000  ; Set mask bit
mov [edx], eax

Debugging and Exception Analysis

Exception Diagnosis Techniques

Common Exception Symptoms:

  • Segmentation Fault: Illegal memory access (e.g., null pointer dereference).
  • Double Fault: Exception during exception handling (e.g., page fault handler accesses invalid memory).
  • Machine Check Exception: Hardware errors (e.g., memory corruption, CPU cache failure).

Diagnostic Tools and Methods:

  • GDB Debugging:
    • info registers → view register state
    • x/10i $pc → disassemble current instructions
    • bt → show call stack
  • Kernel Logs (dmesg): Linux logs exception details (e.g., fault address, vector number).
  • Core Dumps: Memory snapshots on crash; analyze with gdb core.

IDT Debugging Example:

; Dump IDT entry information
dump_idt_entry:
    mov eax, [idt + ecx*8]     ; Fetch gate descriptor
    mov ebx, [idt + ecx*8 + 4] ; High part
    ; Parse and display descriptor...

Page Fault Analysis:

; Add diagnostics in _page_fault
_page_fault:
    mov eax, cr2               ; Faulting address
    mov ebx, [page_tables]
    test ebx, eax
    jnz .valid_address
    ; Log illegal access
    call log_fault_address

Performance Optimization Tips

Interrupt Latency Optimization:

  • Minimize ISR code size
  • Use fast system calls (syscall)
  • Batch interrupts (e.g., NAPI in networking)

Interrupt Load Balancing:

; Enable interrupt load balancing in APIC
mov edx, 0xFEE00320     ; APIC_ICR high
mov eax, [edx]
and eax, 0x00FFFFFF     ; Clear destination field
or eax, 0x01000000      ; Set broadcast mode
mov [edx], eax

By deeply understanding these mechanisms, you can:

  • Develop reliable kernel-level software
  • Implement efficient interrupt handlers
  • Diagnose complex system faults
  • Optimize system responsiveness
  • Build secure exception handling frameworks

Share your love