M4: /sbin/init as PID 1 — a heartbeat process

init is now a real scheduled ring-3 process: it prints a heartbeat and
sleeps, forever. The kernel spawns it at boot via spawnProcess (its own
address space, preemption on) and the boot context drops to idle — the
system's steady state is "kernel idle, init alive in ring 3", beating
~1 Hz. Adds the sleep(ms) syscall. The init/process tests are reshaped
around the heartbeat (repeated syscalls, still-alive, two concurrent
processes on distinct address spaces). Pins init to LLD and folds the
.large code model's .ltext/.lrodata/.ldata into the linker script so its
code segment is R+X. Removes the now-dead borrowed-thread ELF loader
(runInitElf); spawnProcess is the one path. Docs updated. Suite 28/28.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Samson
2026-07-09 00:19:46 +01:00
co-authored by Claude Fable 5
parent 37fb3cb0cf
commit b9d9e1e523
7 changed files with 126 additions and 157 deletions
+20 -11
View File
@@ -1,17 +1,20 @@
//! /sbin/init — the first user-space program. Built as its own freestanding
//! binary (see build.zig), shipped on the boot volume at sbin/init, loaded by
//! the bootloader, and started in ring 3 by the kernel's user-ELF loader
//! (src/kernel/usermode.zig). It talks to the kernel only through the
//! `int $0x80` syscall gate.
//! /sbin/init — the first user-space program, PID 1. Built as its own
//! freestanding binary (see build.zig), shipped on the boot volume at sbin/init,
//! loaded by the bootloader, and started in ring 3 as a scheduled process by the
//! kernel (src/kernel/usermode.zig). It talks to the kernel only through the
//! `syscall` instruction.
//!
//! Today it just proves the path — say hello, exit — and grows into the real
//! init (service supervision) once processes are schedulable (M3).
//! Today it's a heartbeat: it prints a line and sleeps, forever — enough to show
//! the system reaches user space and stays alive with a real process scheduled
//! alongside the kernel's idle loop. It grows into the real init (service
//! supervision) once there are other user programs to supervise.
const std = @import("std");
// The M1 syscall numbers (usermode.zig): 0 = exit(code), 2 = write(ptr, len).
// Syscall numbers (see src/kernel/usermode.zig):
const sys_exit = 0;
const sys_write = 2;
const sys_sleep = 3;
fn syscall2(n: u64, a: u64, b: u64) u64 {
// The `syscall` instruction clobbers RCX (return RIP) and R11 (saved RFLAGS);
@@ -28,6 +31,10 @@ fn write(msg: []const u8) void {
_ = syscall2(sys_write, @intFromPtr(msg.ptr), msg.len);
}
fn sleep(ms: u64) void {
_ = syscall2(sys_sleep, ms, 0);
}
fn exit(code: u64) noreturn {
_ = syscall2(sys_exit, code, 0);
unreachable; // the kernel never returns from exit
@@ -35,7 +42,7 @@ fn exit(code: u64) noreturn {
/// Entry. Naked: the kernel enters with rsp 16-aligned, but a SysV function
/// expects rsp ≡ 8 (mod 16) on entry (as if reached by `call`) — so re-enter
/// the ABI with an actual call. The trap after is unreachable.
/// the ABI with an actual call. The trap after is a safety net.
pub export fn _start() callconv(.naked) noreturn {
asm volatile (
\\call init_main
@@ -44,8 +51,10 @@ pub export fn _start() callconv(.naked) noreturn {
}
export fn init_main() callconv(.c) noreturn {
write("init: hello from user space\n");
exit(0);
while (true) {
write("init: heartbeat\n");
sleep(1000);
}
}
/// No runtime to unwind into — report the panic as a nonzero exit code.
+7
View File
@@ -18,22 +18,29 @@ PHDRS {
}
SECTIONS {
/* The `.large` code model (needed for the >4 GiB image base) emits code and
* data into .ltext/.lrodata/.ldata/.lbss; fold those into the matching
* permission segment alongside the normal names. */
.text ALIGN(4K) : {
*(.text .text.*)
*(.ltext .ltext.*)
} :text
.rodata ALIGN(4K) : {
*(.rodata .rodata.*)
*(.lrodata .lrodata.*)
} :rodata
.data ALIGN(4K) : {
*(.data .data.*)
*(.ldata .ldata.*)
} :data
/* .bss occupies memory but not file space; the loader zeroes the
* filesz..memsz gap. */
.bss ALIGN(4K) : {
*(.bss .bss.*)
*(.lbss .lbss.*)
*(COMMON)
} :data