Phase 03
Interrupts
Handle exceptions and hardware interrupts reliably enough to support timing, keyboard input, and basic fault diagnosis.
Learning goal
Understand the IDT, PIC, and interrupt stacks.
Category
foundations
Components touched
Builds on
Unlocked by this phase
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
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
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
Phase 02
CompleteMemory Basics
Move from a static early kernel into a kernel that can allocate memory safely and reason about physical and virtual address space.
Phase 04
CompleteTasking
Introduce multiple concurrent execution contexts in the kernel and make timer-driven preemption visible and understandable.