selecting the displays native resolution
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
# Halting
|
||||
|
||||
## Why a kernel needs to halt
|
||||
|
||||
An ordinary program ends by *returning* — `main` finishes, the C runtime calls
|
||||
`exit`, and the OS reclaims the process. A kernel has none of that. There is no
|
||||
OS underneath it, no runtime to return to, and no caller waiting. `_start` is the
|
||||
end of the line. So when the kernel has nothing left to do — whether it finished
|
||||
its work or hit a fatal error — it can't "quit". It has to explicitly park the
|
||||
CPU forever, because if execution ever ran off the end it would just keep fetching
|
||||
whatever bytes follow in memory and execute garbage.
|
||||
|
||||
That's what halting is: deliberately stopping the processor so it does nothing,
|
||||
safely, until the machine is reset or powered off.
|
||||
|
||||
## The core of it: `hlt`
|
||||
|
||||
Everything comes down to one x86 instruction. In `src/main.zig`:
|
||||
|
||||
```zig
|
||||
/// Stop the CPU. `hlt` in a loop parks the core at near-zero power until the
|
||||
/// next interrupt; we loop because `hlt` returns when one arrives.
|
||||
fn hang() noreturn {
|
||||
while (true) asm volatile ("hlt");
|
||||
}
|
||||
```
|
||||
|
||||
**`hlt`** ("halt") tells the CPU core to stop executing instructions and drop into
|
||||
a low-power idle state. It's not a busy-wait — the core genuinely stops, drawing
|
||||
almost no power and generating almost no heat, until something wakes it.
|
||||
|
||||
This is much better than the naïve alternative, a spin loop:
|
||||
|
||||
```zig
|
||||
while (true) {} // "busy-wait" — DON'T do this to idle
|
||||
```
|
||||
|
||||
A bare `while (true) {}` keeps the core running flat out, executing the jump
|
||||
back to the top of the loop billions of times a second — 100% CPU, hot, and (on a
|
||||
laptop) draining the battery, all to accomplish nothing. `hlt` achieves the same
|
||||
"do nothing" outcome while letting the core sleep.
|
||||
|
||||
## Why the loop around it?
|
||||
|
||||
Here's the subtlety the comment points at: **`hlt` is not permanent.** It halts
|
||||
the core only until the *next interrupt* arrives. An interrupt is a signal — from
|
||||
a timer, a keypress, a device — that wakes the CPU so it can respond. When one
|
||||
fires, the core comes out of `hlt` and executes the next instruction.
|
||||
|
||||
If we wrote just a single `hlt`, the very first stray interrupt would wake the
|
||||
core and execution would continue past it — falling off the end of the function
|
||||
into whatever comes next in memory. Wrapping it in `while (true)` closes that
|
||||
door: every time an interrupt wakes the core, the loop immediately runs `hlt`
|
||||
again and it goes back to sleep. The net effect is a permanent halt that still
|
||||
sleeps between the interrupts it can't prevent.
|
||||
|
||||
(At this stage danos hasn't set up an interrupt descriptor table, so most
|
||||
interrupts aren't even something we handle — but non-maskable interrupts and
|
||||
system-management interrupts can still wake a halted core regardless. The loop
|
||||
makes us robust to all of them.)
|
||||
|
||||
## The `asm volatile` part
|
||||
|
||||
`hlt` has no equivalent in plain Zig, so we drop to inline assembly:
|
||||
|
||||
- **`asm`** emits the raw instruction directly into the function.
|
||||
- **`volatile`** tells the compiler *"this has side effects you can't see — do
|
||||
not optimize it away or reorder it."* Without it, an optimizing compiler might
|
||||
reason that the assembly produces no value anyone uses and delete it, or hoist
|
||||
it somewhere wrong. `volatile` pins it exactly where we wrote it.
|
||||
|
||||
## `noreturn`: telling the compiler it's the end
|
||||
|
||||
`hang()` is typed `noreturn` — a real Zig type meaning "this function never gives
|
||||
control back to its caller." That isn't decoration; it changes how the compiler
|
||||
treats the call:
|
||||
|
||||
- Code *after* a `noreturn` call is unreachable, so the compiler needn't emit a
|
||||
return sequence, and won't warn about "missing return value" in the callers.
|
||||
- It lets `kmain` and `_start` themselves be `noreturn`, which is the honest
|
||||
signature for a kernel entry point — the bootloader jumps in and nothing ever
|
||||
jumps back out.
|
||||
|
||||
You can see the chain in `src/main.zig`: `_start` is `noreturn`, it calls
|
||||
`kmain` which is `noreturn`, which ends by calling `hang()` which is `noreturn`.
|
||||
The "never returns" property is threaded all the way down.
|
||||
|
||||
## Where danos halts
|
||||
|
||||
There are three halt sites, and they're all the same idea:
|
||||
|
||||
1. **Normal end of kernel work** — `kmain` prints its status, then calls `hang()`:
|
||||
|
||||
```zig
|
||||
con.write("\nkernel initialised; nothing left to do, halting.\n");
|
||||
hang();
|
||||
```
|
||||
|
||||
There's genuinely nothing more to do yet, so the kernel parks itself.
|
||||
|
||||
2. **Kernel panic** — the freestanding panic handler has no OS to report to, so
|
||||
it prints the message in red (if the console is up) and halts via the same
|
||||
`hang()`. A panic is unrecoverable here, so stopping the machine — rather than
|
||||
limping on with corrupted state — is the safe response.
|
||||
|
||||
3. **Bootloader failure** — in `src/efi.zig`, if `boot()` fails *before* handing
|
||||
off to the kernel, `main` logs the error and parks the machine with the same
|
||||
loop so the message stays on screen:
|
||||
|
||||
```zig
|
||||
boot() catch |err| {
|
||||
log("\r\ndanos: boot failed: ");
|
||||
logBytes(@errorName(err));
|
||||
log("\r\n");
|
||||
while (true) asm volatile ("hlt");
|
||||
};
|
||||
```
|
||||
|
||||
(Here it's an inline loop rather than `hang()` because `hang` lives in the
|
||||
kernel, which the loader is a separate binary from.)
|
||||
|
||||
## Summary
|
||||
|
||||
- A kernel can't "exit" — it must explicitly stop the CPU or it runs off into
|
||||
garbage.
|
||||
- **`hlt`** parks the core in a low-power idle until the next interrupt — far
|
||||
better than a 100%-CPU spin loop.
|
||||
- **`while (true) hlt`** makes that halt permanent, since any interrupt would
|
||||
otherwise wake the core and let execution continue.
|
||||
- **`asm volatile`** emits the instruction and forbids the compiler from removing
|
||||
it; **`noreturn`** encodes "control never comes back" into the type system.
|
||||
- danos halts on normal completion, on a kernel panic, and on a bootloader error
|
||||
— the same "stop safely and stay stopped" in all three.
|
||||
Reference in New Issue
Block a user