//! /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);