Files
danos/library/runtime/system.zig
T
Daniel Samson a5fe63c1dd Pass argv to processes on a SysV entry stack; grow the user stack to 32 KiB
Processes now start with C-compatible arguments: the kernel builds the
System V AMD64 entry block (argc, argv, empty envp, auxiliary vector)
at the top of the stack, argv[0] is the path or initial-ramdisk name
the process was spawned as, and system_spawn carries an optional
NUL-separated blob that becomes argv[1..]. The runtime parses the block
(runtime.argumentCount/argument) and its spawn wrappers pass arguments
through. The name is also recorded on the task, so a fault report says
which binary died, not just its id.

The user stack grows from one page to eight (32 KiB,
parameters.user_stack_pages), with the page below left unmapped as a
guard so an overflow faults into a clean process kill rather than
corrupting the image. Task.name_buffer is zero-initialised, not
undefined: an undefined default is materialised as a 0xAA fill that
moved the static task pool out of .bss and made the whole kernel ~7x
slower under QEMU TCG (caught by the affinity test).

Proven end to end by the new args test: args-echo respawns itself with
arguments via the syscall blob, burns more stack than one page could
hold, and echoes its argv intact. Full suite: 44/44.
2026-07-11 08:33:12 +01:00

91 lines
3.6 KiB
Zig

//! Typed system_call surface for user space — thin wrappers over the raw `system_call`
//! stubs, one per kernel call. Numbers come from `abi.SystemCall`, the single
//! source of truth shared with the kernel dispatcher.
const abi = @import("abi");
const sc = @import("system-call.zig");
/// `mmap` protection flags (matching the usual C bit values). Grants are always
/// readable+writable today; the kernel does not yet honour finer prot.
pub const PROT_READ: usize = abi.prot_read;
pub const PROT_WRITE: usize = abi.prot_write;
pub const PROT_EXEC: usize = abi.prot_exec;
/// Give up the rest of this quantum.
pub fn yield() void {
_ = sc.systemCall0(.yield);
}
/// Write raw bytes to the kernel log (a bring-up diagnostic; real output goes
/// through the console/VFS later). Returns the byte count, or a wrapped -1.
pub fn write(message: []const u8) usize {
return sc.systemCall2(.debug_write, @intFromPtr(message.ptr), message.len);
}
/// Block the caller for `ms` milliseconds.
pub fn sleep(ms: usize) void {
_ = sc.systemCall1(.sleep, ms);
}
/// Monotonic nanoseconds since boot — a time source for timeouts and short delays. It
/// only ever moves forward. This is *not* wall-clock time (no date, no timezone — that
/// is a user-space service layered on top). Deadline pattern for a bounded poll loop:
///
/// const deadline = clock() + timeout_ns;
/// while (clock() < deadline) { ... }
pub fn clock() u64 {
return @intCast(sc.systemCall0(.clock));
}
/// End the process. Never returns.
pub fn exit(code: usize) noreturn {
_ = sc.systemCall1(.exit, code);
unreachable; // the kernel never returns from exit
}
/// Start the binary bundled in the initial-ramdisk under `name` as a new ring-3
/// process, returning true on success. The child's argv[0] is `name`. This is how
/// a supervisor (the device manager) launches a driver it matched — danos-native,
/// not POSIX (a spawn/exec family comes with the process work later).
pub fn spawn(name: []const u8) bool {
return sc.systemCall4(.system_spawn, @intFromPtr(name.ptr), name.len, 0, 0) == 0;
}
/// Like `spawn`, but hands the child command-line arguments: they arrive as
/// argv[1..] on its System V entry stack (argv[0] is still `name`). Marshalled to
/// the kernel as one NUL-separated blob; the combined arguments must fit
/// `blob` (the kernel caps the blob at 256 bytes and argc at 8 anyway).
pub fn spawnWithArguments(name: []const u8, arguments: []const []const u8) bool {
var blob: [256]u8 = undefined;
var len: usize = 0;
for (arguments, 0..) |argument, i| {
if (i != 0) {
if (len >= blob.len) return false;
blob[len] = 0;
len += 1;
}
if (len + argument.len > blob.len) return false;
@memcpy(blob[len..][0..argument.len], argument);
len += argument.len;
}
return sc.systemCall4(.system_spawn, @intFromPtr(name.ptr), name.len, @intFromPtr(&blob), len) == 0;
}
/// Grant `len` bytes (rounded up to whole pages) of fresh, zeroed, writable
/// memory and return the base virtual address. On failure returns a value in the
/// top page (see `mmapFailed`). The user heap grows through this call.
pub fn mmap(len: usize, prot: usize) usize {
return sc.systemCall2(.mmap, len, prot);
}
/// Release a range previously handed out by `mmap`.
pub fn munmap(base: usize, len: usize) usize {
return sc.systemCall2(.munmap, base, len);
}
/// Whether an `mmap` return value is an error (the kernel returns a wrapped
/// -errno, which lands in the top page — no real grant base is ever that high).
pub inline fn mmapFailed(ret: usize) bool {
return ret > ~@as(usize, 0) - 4095;
}