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>
66 lines
2.1 KiB
Zig
66 lines
2.1 KiB
Zig
//! /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'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");
|
|
|
|
// 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);
|
|
// the kernel entry stub preserves everything else.
|
|
return asm volatile ("syscall"
|
|
: [ret] "={rax}" (-> u64),
|
|
: [n] "{rax}" (n),
|
|
[a] "{rdi}" (a),
|
|
[b] "{rsi}" (b),
|
|
: .{ .rcx = true, .r11 = true, .memory = true });
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
/// 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 a safety net.
|
|
pub export fn _start() callconv(.naked) noreturn {
|
|
asm volatile (
|
|
\\call init_main
|
|
\\ud2
|
|
);
|
|
}
|
|
|
|
export fn init_main() callconv(.c) noreturn {
|
|
while (true) {
|
|
write("init: heartbeat\n");
|
|
sleep(1000);
|
|
}
|
|
}
|
|
|
|
/// No runtime to unwind into — report the panic as a nonzero exit code.
|
|
pub const panic = std.debug.FullPanic(struct {
|
|
fn panic(_: []const u8, _: ?usize) noreturn {
|
|
exit(127);
|
|
}
|
|
}.panic);
|