Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Key Learnings

Everything that actually matters from KFS_1, distilled.

The Non-Negotiables

1. Always read port 0x60 in the keyboard handler

This clears the keyboard buffer and de-asserts the IRQ line. Skipping this causes an interrupt storm — the handler runs thousands of times per second.

2. Always send EOI after handling an interrupt

The PIC won’t send any more interrupts until it receives EOI. Every interrupt handler must call pic::send_eoi(irq) before returning.

3. Read the port BEFORE sending EOI

Read port 0x60 → THEN send EOI. Sending EOI first means the PIC might fire another interrupt before you’ve cleared the source.

4. Fill the entire IDT with a default handler

Every unhandled interrupt is a potential triple fault. Install a “catch-all” handler for all 256 IDT slots, then override specific ones. This was the root cause of the reboot bug.

5. Exception handlers prevent silent reboots

Without the double fault and general protection fault handlers, the kernel silently reboots when anything goes wrong. With them, you get a readable error on screen.

6. Stack size matters

Interrupt handlers need their own stack space. We use 64KB (up from an initial 16KB). Too small and handlers corrupt the kernel stack.

7. Use addr_of_mut! for mutable statics

#![allow(unused)]
fn main() {
// Wrong - undefined behaviour
let writer = &mut WRITER;

// Correct
let writer = &mut *core::ptr::addr_of_mut!(WRITER);
}

8. Assembly wrappers are not optional for interrupt handlers

Interrupt handlers need pusha/popa to save/restore all registers, and iretd to return correctly. A plain Rust function called directly won’t do this.

Debugging Methodology

  1. Start minimal — Get the simplest possible thing working before adding features
  2. Add diagnostics — println! at each step of initialisation reveals exactly where things break
  3. Test incrementally — Add one feature at a time, verify it works, then proceed
  4. Trust the hardware spec — If port 0x60 is the data port, read from it unconditionally

Rust-Specific Lessons

  • #[no_mangle] and extern "C" are required on any function called from assembly
  • #[repr(C, packed)] is required on structures the CPU reads directly (IDT entries, GDT entries, descriptors)
  • unsafe is genuinely necessary for kernel code — don’t fight it, document why it’s safe
  • core::ptr::addr_of! / addr_of_mut! for taking addresses of statics
  • The ! return type on kernel_main is not just style — it’s required because the entry point can never return

What Comes Next

KFS_2 takes the interrupt infrastructure built here and adds memory segmentation via the Global Descriptor Table, a proper kernel stack, and a simple interactive shell. The keyboard input from KFS_1 becomes the foundation for real user interaction.