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.
60 lines
2.4 KiB
Zig
60 lines
2.4 KiB
Zig
//! The user-space process entry shim. Every user binary roots `_start` here (via
|
|
//! `entry = _start` in build.zig) and forces this file to be analysed with
|
|
//! `comptime { _ = &runtime.start._start; }`, so the whole runtime is linked in.
|
|
|
|
const std = @import("std");
|
|
const system = @import("system.zig");
|
|
|
|
/// The kernel enters at `_start` with rsp 16-aligned, pointing at the System V
|
|
/// process-entry block it built: argc, argv pointers, NULL, envp terminator, the
|
|
/// auxiliary vector, then the strings (see system/kernel/process.zig,
|
|
/// `buildEntryStack`). Capture that address in rdi — the first SysV argument —
|
|
/// before `call` disturbs the stack; the call's pushed return address also puts
|
|
/// rsp ≡ 8 (mod 16), satisfying the ABI before any Zig frame runs. The `ud2` is a
|
|
/// safety net if `rt_start` ever returns.
|
|
pub export fn _start() callconv(.naked) noreturn {
|
|
asm volatile (
|
|
\\mov %%rsp, %%rdi
|
|
\\call rt_start
|
|
\\ud2
|
|
);
|
|
}
|
|
|
|
// The process-entry block, recorded by `rt_start` for the accessors below.
|
|
var argument_count: usize = 0;
|
|
var argument_vector: [*]const u64 = undefined;
|
|
|
|
/// The first Zig frame, entered with `stack` pointing at the kernel-built entry
|
|
/// block. Record argc/argv for the accessors, then hand control to the program's
|
|
/// `main`. The heap is lazy (first alloc grows it), so there is no other runtime
|
|
/// init to order here.
|
|
export fn rt_start(stack: [*]const u64) callconv(.c) noreturn {
|
|
argument_count = stack[0];
|
|
argument_vector = stack + 1;
|
|
const root = @import("root"); // the user binary's root source file
|
|
root.main();
|
|
system.exit(0);
|
|
}
|
|
|
|
/// Number of process arguments (argc). At least 1: argument 0 is the path or
|
|
/// name this binary was spawned as.
|
|
pub fn argumentCount() usize {
|
|
return argument_count;
|
|
}
|
|
|
|
/// Process argument `index` (0 = the program's own path/name), or an empty slice
|
|
/// if out of range. The bytes live in the entry block at the top of the stack
|
|
/// page, NUL-terminated, valid for the process's lifetime.
|
|
pub fn argument(index: usize) []const u8 {
|
|
if (index >= argument_count) return "";
|
|
const string: [*:0]const u8 = @ptrFromInt(argument_vector[index]);
|
|
return std.mem.span(string);
|
|
}
|
|
|
|
/// No runtime to unwind into — report a panic as a nonzero exit code.
|
|
pub const panic = std.debug.FullPanic(struct {
|
|
fn panic(_: []const u8, _: ?usize) noreturn {
|
|
system.exit(127);
|
|
}
|
|
}.panic);
|