Built paging / the kernel's own page tables (with a TSS+IST)

This commit is contained in:
2026-07-03 12:23:55 +01:00
parent 0cc71ec8aa
commit 9cf135302d
10 changed files with 283 additions and 13 deletions
+24 -3
View File
@@ -37,6 +37,25 @@ means present, ring 0, 64-bit interrupt gate. `src/arch/x86_64/idt.zig` builds t
table, points the first 32 vectors at their stubs, and loads it with `lidt`
(`idt_flush`).
## The TSS and the double-fault stack
There's one more table, the **Task State Segment**. In long mode its main
remaining job is the **Interrupt Stack Table (IST)**: an IDT gate can name an IST
slot, and when that vector fires the CPU switches to the stack recorded there —
*regardless* of what the interrupted stack looked like.
This matters most for the **double fault** (#DF, vector 8). A #DF means the CPU
hit a fault *while trying to deliver another fault* — very often because the
current stack pointer is bad, so pushing the exception frame itself faulted. If
the #DF handler then tried to push onto that same bad stack, it would fault a
third time and **triple-fault** — an instant reset. So the #DF gate is pointed at
**IST1**, a small dedicated stack (`src/arch/x86_64/tss.zig`) that's always valid.
Bringing it up: fill in the TSS's IST1 pointer, publish the TSS through a
descriptor in the GDT (`gdt.setTss`), and load it into the task register with
`ltr`. The TSS descriptor is a 16-byte system descriptor spanning two GDT slots,
which is why the GDT grew from three entries to five.
## The stubs and the trap frame
On an exception the CPU pushes a small frame (SS, RSP, RFLAGS, CS, RIP) and, for
@@ -87,11 +106,13 @@ together confirm the whole path: the GDT is active (we're still executing), the
IDT vectored to the right stub, the stub built a correct `CpuState`, and the Zig
handler read it and reported instead of triple-faulting.
Separately, pointing RSP at an unmapped address and faulting forced a **double
fault** — reported cleanly (`double fault (vector 8)`) rather than triple-faulting
into a reset, which only works because #DF ran on IST1. That's the proof the
TSS/IST is wired up: the handler survived a completely broken stack.
## What's next (not done here)
- **A TSS with an IST** (interrupt stack table) so the double-fault handler runs
on a known-good stack — important because a double fault often means the current
stack is unusable, and without an IST the handler would itself fault.
- **Device interrupts**: program the local APIC and IO-APIC, wire a timer and the
keyboard onto vectors ≥ 32, and (unlike exceptions) actually *return* from them
with `iretq` — which `isr_common` already does.