m³OS docs GitHub

Phase 03

Interrupts

Handle exceptions and hardware interrupts reliably enough to support timing, keyboard input, and basic fault diagnosis.

Complete

Learning goal

Understand the IDT, PIC, and interrupt stacks.

Category

foundations

Why this phase matters

Handle exceptions and hardware interrupts reliably enough to support timing, keyboard input, and basic fault diagnosis.

How it works in m3OS

Interrupts allow the CPU to respond to asynchronous events (hardware I/O, timers) and synchronous faults (invalid memory access, division by zero). The kernel configures the Interrupt Descriptor Table (IDT) — a table of 256 handlers that the CPU indexes into when an event fires.

How it builds on earlier phases

This phase builds on Memory Basics and turns that foundation into Interrupts.

Implementation outline

  • Build the GDT, TSS, and IDT setup sequence.
  • Add exception handlers for breakpoint, page fault, and double fault.
  • Initialize the PIC with a clean vector layout.
  • Add timer and keyboard IRQ handlers.
  • Push non-trivial work out of the IRQ path into buffers or later phases.

Task progress snapshot

19 of 19 tracked tasks are complete.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • APIC and SMP interrupt routing
  • advanced driver interrupt models
  • complex deferred work queues

Source materials

Important code

Code spotlight cards

Code spotlight

Set up the GDT, TSS, and interrupt stack entries

kernel/src/arch/x86_64/gdt.rs L55-L61

Set up the GDT, TSS, and interrupt stack entries. Why it matters: The GDT defines segment descriptors for kernel and user code/data, and the TSS provides the interrupt stack table (IST) entries needed for safe double-fault handling. Focus on `GDT` and `TSS` in `kernel/src/arch/x86_64/gdt.rs` while you trace the snippet.

static GDT: Lazy<(GlobalDescriptorTable, Selectors)> = Lazy::new(|| {
    let mut gdt = GlobalDescriptorTable::new();
    let code = gdt.append(Descriptor::kernel_code_segment());
    let data = gdt.append(Descriptor::kernel_data_segment());
    let tss = gdt.append(Descriptor::tss_segment(&TSS));
    (gdt, Selectors { code, data, tss })
});

Code spotlight

Build the IDT and install exception handlers

kernel/src/arch/x86_64/interrupts.rs L14-L36

Build the IDT and install exception handlers. Why it matters: Without an IDT, any CPU exception causes a triple fault and immediate reset rather than a debuggable error message. Focus on `IDT` and `init` in `kernel/src/arch/x86_64/interrupts.rs` while you trace the snippet.

static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
    let mut idt = InterruptDescriptorTable::new();

    // CPU exceptions
    idt.breakpoint.set_handler_fn(breakpoint_handler);
    idt.page_fault.set_handler_fn(page_fault_handler);
    idt.general_protection_fault
        .set_handler_fn(general_protection_fault_handler);
    unsafe {
        idt.double_fault
            .set_handler_fn(double_fault_handler)
            .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
    }

    // Hardware IRQs
    idt[InterruptIndex::Timer as u8].set_handler_fn(timer_handler);
    idt[InterruptIndex::Keyboard as u8].set_handler_fn(keyboard_handler);

    idt
});

/// Load the IDT.
pub fn init() {

Key files

How real OSes differ

  • Modern kernels often use APICs, MSI/MSI-X, more sophisticated interrupt routing, and complex per-CPU data structures. This project should stay with the simpler PIC path until the reader understands exceptions, IRQ acknowledgment, and stack discipline.

Success criteria

  • Breakpoints and page faults produce readable diagnostics.
  • Timer interrupts occur at the expected cadence.
  • Keyboard input reaches a buffer or log output.
  • Interrupt handlers do not allocate or block.

Related phases

Keep exploring the roadmap