Files
danos/sbin/init.zig
T
Daniel Samson 9316f9f1c3 M5: rename usermode->process, add yield + mmap/munmap syscalls
Start the user-space driver track (VFS + IPC + heap). This lays the
process/syscall foundation the runtime heap will grow on.

- Rename usermode.zig -> process.zig; drop the retired hello/ping blob and
  its `user` test (subsumed by the real /sbin/init exerciser). Keep the
  isolation-proof pf blob and the user-pf test.
- Add danos.Syscall as the single source of truth for syscall numbers, shared
  by the kernel dispatcher and (later) the user runtime lib. Dispatch on the
  enum. New calls: 1=yield, 4=mmap, 5=munmap. Widen debug_write's bounds
  check to the whole user low half so heap buffers are writable.
- mmap grants zeroed RW+NX pages from a per-process bump arena
  (Task.heap_next, PML4[224] above image+stack); munmap frees the frames.
  Add paging.translateIn / unmapInto (+ arch.translate / unmapUserPageInto)
  as the primitives munmap and future cross-AS copies need.
- New `usermem` test: grant three pages into a fresh AS, translate them,
  release via the munmap path, tear down, and assert no frames leak.
  Suite 28/28 (user -> usermem).
2026-07-09 06:55:09 +01:00

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/process.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/process.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);