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.
This commit is contained in:
+48
-8
@@ -120,6 +120,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
userPfTest();
|
||||
} else if (eql(case, "fault-recovery")) {
|
||||
faultRecoveryTest(boot_information);
|
||||
} else if (eql(case, "args")) {
|
||||
argsTest(boot_information);
|
||||
} else if (eql(case, "init")) {
|
||||
initTest(boot_information);
|
||||
} else if (eql(case, "process")) {
|
||||
@@ -1162,8 +1164,8 @@ fn processTest(boot_information: *const BootInformation) void {
|
||||
scheduler.spawn(procWorker, 4); // kernel task at the processes' priority
|
||||
|
||||
var spawned: u32 = 0;
|
||||
if (process.spawnProcess(image, 4)) spawned += 1 else |_| {}
|
||||
if (process.spawnProcess(image, 4)) spawned += 1 else |_| {}
|
||||
if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}
|
||||
if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}
|
||||
|
||||
// Wait (real time) for several heartbeats across the two processes. Each
|
||||
// process sleeps ~1 s between beats, so a few seconds yields several.
|
||||
@@ -1218,9 +1220,9 @@ fn spawnFaultingProcess() bool {
|
||||
architecture.destroyAddressSpace(aspace); // frees code_frame too — it's mapped
|
||||
return false;
|
||||
};
|
||||
architecture.mapUserPageInto(aspace, process.stack_virtual, stack_frame, true, false); // RW + NX
|
||||
architecture.mapUserPageInto(aspace, process.stack_base_virtual, stack_frame, true, false); // RW + NX
|
||||
|
||||
if (!scheduler.spawnUserLocked(aspace, process.code_virtual, process.stack_virtual + abi.page_size, 4)) {
|
||||
if (!scheduler.spawnUserLocked(aspace, process.code_virtual, process.stack_base_virtual + abi.page_size, 4, "fault-probe")) {
|
||||
architecture.destroyAddressSpace(aspace);
|
||||
return false;
|
||||
}
|
||||
@@ -1243,7 +1245,7 @@ fn faultRecoveryTest(boot_information: *const BootInformation) void {
|
||||
|
||||
process.write_count = 0;
|
||||
process.fault_kill_count = 0;
|
||||
const spawned = if (process.spawnProcess(image, 4)) true else |_| false;
|
||||
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
|
||||
check("init spawned as the surviving process", spawned);
|
||||
|
||||
// A first heartbeat proves init runs before the fault.
|
||||
@@ -1287,7 +1289,7 @@ fn initTest(boot_information: *const BootInformation) void {
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
|
||||
process.write_count = 0;
|
||||
const spawned = if (process.spawnProcess(image, 4)) true else |err| blk: {
|
||||
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |err| blk: {
|
||||
log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)});
|
||||
break :blk false;
|
||||
};
|
||||
@@ -1334,7 +1336,7 @@ fn initialRamdiskTest(boot_information: *const BootInformation) void {
|
||||
var i: u32 = 0;
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
if (process.spawnProcess(item.blob, 4)) spawned += 1 else |err| {
|
||||
if (process.spawnProcess(item.blob, 4, &.{item.name})) spawned += 1 else |err| {
|
||||
log("DANOS-INITRD-ERR: {s}: {s}\n", .{ item.name, @errorName(err) });
|
||||
}
|
||||
}
|
||||
@@ -1394,6 +1396,44 @@ fn vfsTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// Process arguments, end to end: spawn args-echo bare (its argv[0] is the
|
||||
/// initial-ramdisk name). Instance 1 sees argc == 1 and respawns itself through
|
||||
/// `system_spawn` with the extra arguments "alpha beta-42" — the syscall argument
|
||||
/// blob. Instance 2 parses the kernel-built System V entry stack via the runtime
|
||||
/// and echoes its whole argv in one write, which must arrive exactly as sent.
|
||||
fn argsTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: args\n", .{});
|
||||
check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
process.setInitialRamdisk(image); // args-echo respawns itself through system_spawn
|
||||
|
||||
process.write_count = 0;
|
||||
process.write_from_user = false;
|
||||
check("args-echo spawned from the initial_ramdisk", spawnNamed(rd, "args-echo"));
|
||||
|
||||
// Wait for the *second* instance's echo (the first writes nothing).
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 8000;
|
||||
while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield();
|
||||
scheduler.setPriority(4);
|
||||
|
||||
const expected = "args: args-echo alpha beta-42\n";
|
||||
const echoed = process.write_len == expected.len and eql(process.write_buffer[0..process.write_len], expected);
|
||||
if (!echoed and process.write_len > 0) log("DANOS-ARGS: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
|
||||
check("argv arrived intact (argv[0] = name, argv[1..] = spawn arguments)", echoed);
|
||||
check("echo came from user mode (CPL 3)", process.write_from_user);
|
||||
result();
|
||||
}
|
||||
|
||||
/// Spawn the initial_ramdisk binary named `name` as a ring-3 process. Returns false if it
|
||||
/// isn't in the image or fails to load.
|
||||
fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
|
||||
@@ -1401,7 +1441,7 @@ fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
if (eql(item.name, name)) {
|
||||
return if (process.spawnProcess(item.blob, 4)) true else |_| false;
|
||||
return if (process.spawnProcess(item.blob, 4, &.{item.name})) true else |_| false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user