summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTEXT.md17
-rw-r--r--docs/adr/0003-preemptive-kernel-tasks.md97
2 files changed, 113 insertions, 1 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index aa2392e..65475ce 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -32,7 +32,9 @@ structs are hand-defined (not from `uefi-rs` crate) to stay lightweight.
32 32
33Long mode mandates paging (`CR0.PG = 1`), so we accept that tax but keep it 33Long mode mandates paging (`CR0.PG = 1`), so we accept that tax but keep it
34simple. Identity-mapped 4-level tables, no higher-half, no demand paging, 34simple. Identity-mapped 4-level tables, no higher-half, no demand paging,
35no guard pages. `paging.rs` provides identity map/unmap/query helpers. At 35no guard pages except task stacks — each task's stack gets one unmapped
36guard page below it, and a page-fault handler turns an overflow into a
37task panic. `paging.rs` provides identity map/unmap/query helpers. At
36boot the kernel runs on UEFI's tables; `adopt_tables()` rebuilds that map 38boot the kernel runs on UEFI's tables; `adopt_tables()` rebuilds that map
37into the kernel's own tables and switches CR3 — needed because UEFI maps its 39into the kernel's own tables and switches CR3 — needed because UEFI maps its
38page-table pages read-only, so the firmware tree can't be modified in place. 40page-table pages read-only, so the firmware tree can't be modified in place.
@@ -80,6 +82,19 @@ linear frame buffer is system memory that we own once boot services are gone.
80Scancode set 1, US QWERTY layout. Hardcoded scancode→ASCII table. 82Scancode set 1, US QWERTY layout. Hardcoded scancode→ASCII table.
81Other layouts can be added later by swapping the translation table. 83Other layouts can be added later by swapping the translation table.
82 84
85## Task
86
87A unit of concurrent execution in the kernel: its own kernel stack
88(with a guard page below it), scheduler bookkeeping, and no
89address-space separation — all tasks share the one identity-mapped
90space and the kernel's facilities (TempleOS-style, not a process).
91Tasks are preemptively round-robin-scheduled on the 100 Hz PIT tick;
92the shell is task 0 and an idle `hlt` task is the always-ready
93fallback. A task may sleep, yield, and log to serial/klog; only the
94shell touches the disk, filesystem, and terminal. A panicking or
95stack-overflowing task dies gracefully (logged, marked done, slot
96reaped) without taking the kernel down.
97
83## Development loop 98## Development loop
84 99
85Standalone project — no integration with the RNG initramfs build system. 100Standalone project — no integration with the RNG initramfs build system.
diff --git a/docs/adr/0003-preemptive-kernel-tasks.md b/docs/adr/0003-preemptive-kernel-tasks.md
new file mode 100644
index 0000000..ac38027
--- /dev/null
+++ b/docs/adr/0003-preemptive-kernel-tasks.md
@@ -0,0 +1,97 @@
1# ADR 0003 — Preemptive Kernel Tasks
2
3## Status
4
5Accepted.
6
7## Context
8
9The kernel is single-threaded and polling-based: a shell loop drives
10everything, drivers poll (AHCI has no interrupts), and the only
11preemption source is a 100 Hz PIT tick whose ISR currently just counts
12and acknowledges. The interrupt path already saves a complete register
13frame (`isr_common` pushes all GPRs + the iretq frame onto the
14interrupted stack), so switching tasks is "return a different stack
15pointer from the ISR dispatch" — a small asm change, not new machinery.
16
17The destination is a windowed GUI running multiple applications at
18once, which requires concurrent execution and, eventually, per-task
19input/output channels. The immediate goal is the foundation: a
20preemptive multitasking core that the GUI will be built on.
21
22Two constraints shape the design. First, all kernel driver state is
23single-user by construction: one shared AHCI DMA buffer plus a global
24"active disk" selector, a per-volume cached FAT with a global active
25volume, the VFS node table, and the shell's tty/vt terminal. Two tasks
26doing disk or filesystem work concurrently would corrupt each other.
27Second, there is no address-space separation and no user/kernel
28boundary — the kernel is one identity-mapped space (TempleOS-style), so
29"tasks" (not processes) sharing every kernel facility is the honest
30model.
31
32## Decision
33
34Preemptive, round-robin **kernel tasks** in the one shared address
35space, preempted on the 100 Hz PIT tick:
36
37- **Scheduler** — a fixed 16-slot static task table (the timer ISR
38 never allocates), round-robin across Ready tasks, one class, 10 ms
39 quantum. States: `ready`, `sleeping` (wake-at timestamp from
40 `uptime_ms`), `blocked`, `done`. The shell is task 0; a kernel idle
41 task (`hlt` loop) is the always-ready fallback. Reaping is lazy:
42 Done tasks' stacks are freed and slots reused at the next `spawn` (and
43 `jobs` sweeps).
44- **Context switch** — the timer ISR saves the running task's frame on
45 its own stack, stores the frame pointer in the task's slot, picks the
46 next Ready task, and returns its frame pointer; the asm moves `rsp`
47 there and iretq's. Yield and sleep use a software interrupt (vector
48 0x40) through the same machinery.
49- **Stacks** — each task gets 8 KiB from the page allocator plus one
50 **guard page** mapped below it (the paging code already maps/unmaps).
51 A new page-fault handler (vector 14) reads CR2; a fault inside a
52 task's guard page is a "stack overflow" task panic, any other fault
53 halts the kernel with the address.
54- **Spawn surface** — `sched::spawn(entry, name)` is general (any plain
55 `fn`), but the shell only offers safe builtins: `bg <name>`,
56 `jobs`, `sleep <ms>`, `kill <id>`, `demo`. First-slice tasks are
57 headless: they may sleep, yield, and log to serial/klog — nothing
58 else. Invariant: **only the shell task touches the disk,
59 filesystem, and terminal**; the scheduler does not enforce this (a
60 general `spawn` cannot), it is a documented boundary.
61- **Line atomicity** — a scheduler lock (preempt-disable counter, "no
62 switching while held") is held by `klog::append_fmt` and
63 `serial::write_str` so lines from different tasks never interleave
64 mid-line. The timer ISR skips switching while the lock is held.
65- **Graceful task panic** — the panic handler becomes task-aware: if
66 the scheduler is live and the panic is in a task (id ≥ 1), it logs
67 `sched: task N "name" panicked: <msg>`, marks the task Done, clears
68 the scheduler lock, and switches away via vector 0x40 — the shell
69 keeps running and `jobs` shows the corpse. Panics in ISR context,
70 before the scheduler exists, or in the shell task itself still halt.
71- **Shell surface** — `bg <name>` spawns a builtin (`spinner`,
72 `ticker`, `counter`, plus `boom` which panics on purpose), `jobs`
73 lists id/name/state, `sleep <ms>` blocks the shell task (background
74 tasks keep running — the visible proof of preemption), `kill <id>`
75 marks a task Done, `demo` spawns a showcase set at different sleep
76 rates.
77
78Explicitly **not** in this ADR: backgrounding arbitrary shell commands
79(`cmd &`), per-task I/O channels, task isolation, priorities, and the
80GUI itself. The scheduler's blocking/wake primitives are designed so
81the GUI's per-task windows and input queues can be built on them later.
82
83## Consequences
84
85- Concurrent disk/fs/tty/vt access remains unsupported: any second task
86 doing I/O needs per-task DMA buffers and real mutexes, which is
87 future work (the GUI-era step).
88- Task stacks overflow into a clean, labeled task death instead of
89 silent corruption; the glossary's "no guard pages" claim now reads
90 "except task stacks".
91- A buggy background task cannot take the kernel down (panic or stack
92 overflow), which the GUI will depend on; a buggy shell still can.
93- The serial log becomes interleaved across tasks — expected, and the
94 scheduler lock keeps it line-clean.
95- klog timestamps make task activity (`[ssss.mmm]`) traceable alongside
96 boot messages; rescan events, panic messages, and task logs all share
97 the ring.