Pass arguments to main via runtime.process.Init, dispatched on signature
This commit is contained in:
parent
d218d93f79
commit
75ccfff171
|
|
@ -90,8 +90,10 @@ process) instead of silently corrupting the image
|
|||
(`buildEntryStack` in `system/kernel/process.zig`); `argv[0]` is always the path
|
||||
or initial-ramdisk name the process was spawned as, and `system_spawn`'s optional
|
||||
argument blob becomes `argv[1..]`. The runtime's `_start`
|
||||
(`library/runtime/start.zig`) hands the block to `rt_start`, which exposes it as
|
||||
`runtime.argumentCount()` / `runtime.argument(i)`. A C runtime's `crt0` would walk
|
||||
(`library/runtime/start.zig`) hands the block to `rt_start`, which builds a
|
||||
`runtime.process.Init` from it and passes that to the program's `main`
|
||||
(`pub fn main(init: runtime.process.Init)`; a parameterless `main()` is also
|
||||
accepted). A C runtime's `crt0` would walk
|
||||
the identical layout unmodified — that's the compatibility being bought. The
|
||||
`args` test proves the round trip.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
//! Process-level runtime types: what a user program receives at entry. Mirrors
|
||||
//! the spirit of `std.process.Init.Minimal` in danos terms — std's `Args` holds
|
||||
//! no data on freestanding targets, so the type is danos's own.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// Everything a program receives at entry. Passed to
|
||||
/// `pub fn main(init: runtime.process.Init)`; programs that need nothing keep
|
||||
/// `pub fn main() void`. An `environment` field is added here once the kernel
|
||||
/// passes a non-empty envp (today it is always empty — see docs/sysv.md).
|
||||
pub const Init = struct {
|
||||
arguments: Arguments,
|
||||
};
|
||||
|
||||
/// The process arguments (argc/argv), parsed from the kernel-built System V
|
||||
/// entry block. The bytes live in the entry block at the top of the stack page,
|
||||
/// NUL-terminated, valid for the process's lifetime.
|
||||
pub const Arguments = struct {
|
||||
/// argc — at least 1: argument 0 is the path or name this binary was
|
||||
/// spawned as.
|
||||
count: usize,
|
||||
/// The argv pointers in the entry block (NULL-terminated after `count`
|
||||
/// entries).
|
||||
vector: [*]const [*:0]const u8,
|
||||
|
||||
/// Argument `index` (0 = the program's own path/name), or null if out of
|
||||
/// range.
|
||||
pub fn get(arguments: Arguments, index: usize) ?[:0]const u8 {
|
||||
if (index >= arguments.count) return null;
|
||||
return std.mem.span(arguments.vector[index]);
|
||||
}
|
||||
|
||||
pub fn iterate(arguments: Arguments) Iterator {
|
||||
return .{ .arguments = arguments };
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
arguments: Arguments,
|
||||
index: usize = 0,
|
||||
|
||||
pub fn next(iterator: *Iterator) ?[:0]const u8 {
|
||||
const argument = iterator.arguments.get(iterator.index) orelse return null;
|
||||
iterator.index += 1;
|
||||
return argument;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
//! const runtime = @import("runtime");
|
||||
//! pub const panic = runtime.panic;
|
||||
//! comptime { _ = &runtime.start._start; } // pull the entry shim in
|
||||
//! and a `pub fn main() void`.
|
||||
//! and a `pub fn main() void` or `pub fn main(init: runtime.process.Init) void`
|
||||
//! (arguments arrive via `init`).
|
||||
|
||||
pub const system = @import("system.zig");
|
||||
pub const heap = @import("heap.zig");
|
||||
|
|
@ -26,10 +27,8 @@ pub const dma = @import("dma.zig");
|
|||
/// Re-exported so a user binary can `pub const panic = runtime.panic;`.
|
||||
pub const panic = start.panic;
|
||||
|
||||
/// Process arguments (argc/argv, parsed from the kernel-built entry stack):
|
||||
/// `argument(0)` is the path or name this binary was spawned as.
|
||||
pub const argumentCount = start.argumentCount;
|
||||
pub const argument = start.argument;
|
||||
/// Process entry types: the `Init` handed to `main`, and its `Arguments`.
|
||||
pub const process = @import("process.zig");
|
||||
|
||||
/// The heap as a `std.mem.Allocator`, for Zig `std` containers in user code.
|
||||
pub const allocator = heap.allocator;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
const std = @import("std");
|
||||
const system = @import("system.zig");
|
||||
const process = @import("process.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
|
||||
|
|
@ -20,35 +21,61 @@ pub export fn _start() callconv(.naked) noreturn {
|
|||
);
|
||||
}
|
||||
|
||||
// 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.
|
||||
/// block. Build the `process.Init` from it and dispatch to the program's `main`,
|
||||
/// whose signature is inspected at comptime. 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 init: process.Init = .{ .arguments = .{
|
||||
.count = stack[0],
|
||||
.vector = @ptrCast(stack + 1),
|
||||
} };
|
||||
system.exit(callMain(init));
|
||||
}
|
||||
|
||||
/// Comptime-dispatch on root.main's signature, in the spirit of std's start.zig:
|
||||
/// zero parameters or one `process.Init`; returns void, noreturn, u8, !void, or !u8.
|
||||
fn callMain(init: process.Init) u8 {
|
||||
const root = @import("root"); // the user binary's root source file
|
||||
root.main();
|
||||
system.exit(0);
|
||||
}
|
||||
const main_information = @typeInfo(@TypeOf(root.main)).@"fn";
|
||||
|
||||
/// 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;
|
||||
}
|
||||
const call_arguments = switch (main_information.params.len) {
|
||||
0 => .{},
|
||||
1 => arguments: {
|
||||
const Parameter = main_information.params[0].type orelse
|
||||
@compileError("main's parameter must be runtime.process.Init (not anytype)");
|
||||
if (Parameter != process.Init)
|
||||
@compileError("main's parameter must be runtime.process.Init, found " ++ @typeName(Parameter));
|
||||
break :arguments .{init};
|
||||
},
|
||||
else => @compileError("main takes no parameters or a single runtime.process.Init"),
|
||||
};
|
||||
|
||||
/// 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);
|
||||
const ReturnType = main_information.return_type.?;
|
||||
switch (@typeInfo(ReturnType)) {
|
||||
.noreturn => @call(.auto, root.main, call_arguments),
|
||||
.void => {
|
||||
@call(.auto, root.main, call_arguments);
|
||||
return 0;
|
||||
},
|
||||
.int => {
|
||||
if (ReturnType != u8)
|
||||
@compileError("main's integer return type must be u8, found " ++ @typeName(ReturnType));
|
||||
return @call(.auto, root.main, call_arguments);
|
||||
},
|
||||
.error_union => {
|
||||
const payload = @call(.auto, root.main, call_arguments) catch |err| {
|
||||
var buffer: [128]u8 = undefined;
|
||||
const line = std.fmt.bufPrint(&buffer, "main returned error: {s}\n", .{@errorName(err)}) catch "main returned an error\n";
|
||||
_ = system.write(line);
|
||||
return 1; // distinct from panic's 127
|
||||
};
|
||||
if (@TypeOf(payload) == void) return 0;
|
||||
if (@TypeOf(payload) == u8) return payload;
|
||||
@compileError("main's error-union payload must be void or u8, found " ++ @typeName(@TypeOf(payload)));
|
||||
},
|
||||
else => @compileError("main must return void, noreturn, u8, !void, or !u8, found " ++ @typeName(ReturnType)),
|
||||
}
|
||||
}
|
||||
|
||||
/// No runtime to unwind into — report a panic as a nonzero exit code.
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ fn burnStack(depth: usize) u8 {
|
|||
return touch[0] +% burnStack(depth - 1);
|
||||
}
|
||||
|
||||
pub fn main() void {
|
||||
if (runtime.argumentCount() <= 1) {
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
if (init.arguments.count <= 1) {
|
||||
// First instance: spawn the second with real arguments, then exit.
|
||||
_ = runtime.system.spawnWithArguments("args-echo", &.{ "alpha", "beta-42" });
|
||||
return;
|
||||
|
|
@ -36,8 +36,8 @@ pub fn main() void {
|
|||
const prefix = "args:";
|
||||
@memcpy(buffer[0..prefix.len], prefix);
|
||||
var len: usize = prefix.len;
|
||||
for (0..runtime.argumentCount()) |i| {
|
||||
const argument = runtime.argument(i);
|
||||
var iterator = init.arguments.iterate();
|
||||
while (iterator.next()) |argument| {
|
||||
if (len + 1 + argument.len + 1 > buffer.len) break;
|
||||
buffer[len] = ' ';
|
||||
len += 1;
|
||||
|
|
|
|||
|
|
@ -48,10 +48,8 @@ fn awaitChildExit(endpoint: runtime.ipc.Handle) u32 {
|
|||
return received.childProcessId();
|
||||
}
|
||||
|
||||
pub fn main() void {
|
||||
if (runtime.argumentCount() <= 1) return; // spawned bare (ramdisk sweep): stay silent
|
||||
|
||||
const role = runtime.argument(1);
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
const role = init.arguments.get(1) orelse return; // spawned bare (ramdisk sweep): stay silent
|
||||
if (std.mem.eql(u8, role, "sleeper")) {
|
||||
while (true) runtime.system.sleep(500);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue