boot: the /system tree is the system image — loader-built ramdisk, spawn by path

Retire the build-time ramdisk packer and the packed initial-ramdisk.img.
make-fat-image.py now lays every user binary out at its FHS path on the
boot volume (system/services, system/drivers, system/tests), and the EFI
loader walks \system at boot, packing what it finds into an in-RAM v2
initial_ramdisk whose entry names are full FHS paths. init rides the
table like every other binary: its dedicated handoff fields are gone and
the kernel spawns PID 1 via the same lookup as everyone else
(process.spawnBundled).

system_spawn resolves names by exact path first, then unique basename,
and normalizes argv[0] to the stored path — so task names (and, next,
the tagged log ring's attribution) are honest binary paths everywhere.
initrd v2 rejects the old magic so a stale image fails loudly.

Groundwork for per-process logging (/var/log/<boot-stamp>/<binary-path>.log)
and the kernel-VFS /system mount.
This commit is contained in:
Daniel Samson
2026-07-21 14:47:26 +01:00
parent ec6e888076
commit 2d0858caf6
11 changed files with 413 additions and 324 deletions
+5 -9
View File
@@ -147,15 +147,11 @@ pub const BootInformation = extern struct {
/// A device-tree boot path leaves this 0 and (later) fills a `device_tree_blob`
/// field instead, so the kernel discovers devices without knowing what booted it.
acpi_rsdp: u64 = 0,
/// The raw `/system/services/init` ELF image, read off the boot volume by the loader
/// into memory that survives the handoff (classified reserved, so the kernel
/// identity-maps it and never allocates over it). 0/0 = no init found — the
/// kernel boots without user space. Grows into a full initial_ramdisk handoff later.
init_base: u64 = 0,
init_len: u64 = 0,
/// The initial_ramdisk image (a bundle of extra user binaries — the VFS server and
/// device drivers), read off the boot volume into memory that survives the
/// handoff, same as `init` above. 0/0 = no initial_ramdisk. See system/initial-ramdisk.zig.
/// The initial_ramdisk image: every user binary from the boot volume's /system
/// tree (init included), packed by the loader into memory that survives the
/// handoff (classified reserved, so the kernel identity-maps it and never
/// allocates over it). Entries are named by full FHS path. 0/0 = no binaries
/// found — the kernel boots without user space. See system/initial-ramdisk.zig.
initial_ramdisk_base: u64 = 0,
initial_ramdisk_len: u64 = 0,
};
+89 -8
View File
@@ -1,7 +1,13 @@
//! The initial_ramdisk (initial ramdisk) container format — shared by the build-time
//! packer (tools/make-initial-ramdisk.py) and the kernel that unpacks it. Deliberately
//! trivial: a header, a table of fixed-size entries, then the concatenated file
//! blobs. We own both producer and consumer, so it need be no fancier.
//! The initial_ramdisk (initial ramdisk) container format — built in RAM by the
//! bootloader (boot/efi.zig walks the boot volume's /system tree) and unpacked by
//! the kernel. Deliberately trivial: a header, a table of fixed-size entries, then
//! the concatenated file blobs. We own both producer and consumer, so it need be
//! no fancier.
//!
//! v2: entry names are full FHS paths ("/system/services/init"), 64 bytes — the
//! same limit as a task name (abi.maximum_process_name), so a path-named task is
//! never truncated. The boot volume's file tree is the single source of truth;
//! this image is only the loader→kernel handoff snapshot of it.
//!
//! Layout:
//! Header (magic, count)
@@ -10,8 +16,14 @@
const std = @import("std");
/// "DNRD" — identifies a danos initial_ramdisk image.
pub const magic: u32 = 0x444E5244;
/// "DNR2" — identifies a danos initial_ramdisk image, format v2 (path names).
/// The v1 magic ("DNRD", basename entries) is rejected: a stale image should
/// fail loudly at Reader.init, not misparse names.
pub const magic: u32 = 0x32524E44;
/// Entry name capacity. Matches abi.maximum_process_name so a spawned task can
/// always carry its full binary path as its name.
pub const maximum_name = 64;
pub const Header = extern struct {
magic: u32,
@@ -19,11 +31,17 @@ pub const Header = extern struct {
};
pub const Entry = extern struct {
name: [32]u8, // NUL-padded file name (basename)
name: [maximum_name]u8, // NUL-padded FHS path, e.g. "/system/services/init"
offset: u64, // byte offset of the blob within the image
len: u64, // blob length in bytes
};
/// The basename of a path: the final component after the last '/'.
pub fn basename(path: []const u8) []const u8 {
const i = std.mem.lastIndexOfScalar(u8, path, '/') orelse return path;
return path[i + 1 ..];
}
/// A validated view over an initial_ramdisk image. `init` checks the magic and that the
/// entry table fits; `entry` bounds-checks each blob against the image.
pub const Reader = struct {
@@ -48,11 +66,74 @@ pub const Reader = struct {
if (e.offset > self.image.len or e.len > self.image.len - e.offset) return null;
// The name is stored in the entry's fixed field; return a stable slice
// into the image (not the value copy) up to the NUL terminator.
const name_field = self.image[off .. off + 32];
const name_field = self.image[off .. off + maximum_name];
const nlen = std.mem.indexOfScalar(u8, name_field, 0) orelse name_field.len;
return .{
.name = name_field[0..nlen],
.blob = self.image[@intCast(e.offset)..][0..@intCast(e.len)],
};
}
/// Look a binary up by name: an exact path match wins; otherwise a unique
/// basename match ("fat" finds "/system/services/fat") keeps pre-path callers
/// working. The returned Item's name is always the stored full path.
pub fn find(self: Reader, name: []const u8) ?Item {
var i: u32 = 0;
while (i < self.count) : (i += 1) {
const item = self.entry(i) orelse continue;
if (std.mem.eql(u8, item.name, name)) return item;
}
i = 0;
while (i < self.count) : (i += 1) {
const item = self.entry(i) orelse continue;
if (std.mem.eql(u8, basename(item.name), name)) return item;
}
return null;
}
};
// --- tests (host) -----------------------------------------------------------
fn testImage(buffer: []u8, entries: []const struct { name: []const u8, blob: []const u8 }) []const u8 {
const table_end = @sizeOf(Header) + entries.len * @sizeOf(Entry);
var offset: usize = table_end;
std.mem.bytesAsValue(Header, buffer[0..@sizeOf(Header)]).* = .{ .magic = magic, .count = @intCast(entries.len) };
for (entries, 0..) |e, i| {
var record = Entry{ .name = @splat(0), .offset = offset, .len = e.blob.len };
@memcpy(record.name[0..e.name.len], e.name);
std.mem.bytesAsValue(Entry, buffer[@sizeOf(Header) + i * @sizeOf(Entry) ..][0..@sizeOf(Entry)]).* = record;
@memcpy(buffer[offset..][0..e.blob.len], e.blob);
offset += e.blob.len;
}
return buffer[0..offset];
}
test "find matches exact path, then unique basename; name is the stored path" {
var buffer: [1024]u8 = undefined;
const image = testImage(&buffer, &.{
.{ .name = "/system/services/init", .blob = "INIT" },
.{ .name = "/system/drivers/ps2-bus", .blob = "PS2" },
});
const rd = Reader.init(image).?;
const by_path = rd.find("/system/services/init").?;
try std.testing.expectEqualStrings("/system/services/init", by_path.name);
try std.testing.expectEqualStrings("INIT", by_path.blob);
const by_base = rd.find("ps2-bus").?;
try std.testing.expectEqualStrings("/system/drivers/ps2-bus", by_base.name);
try std.testing.expectEqualStrings("PS2", by_base.blob);
try std.testing.expect(rd.find("no-such-binary") == null);
}
test "v1 magic is rejected" {
var buffer: [64]u8 = @splat(0);
std.mem.bytesAsValue(Header, buffer[0..@sizeOf(Header)]).* = .{ .magic = 0x444E5244, .count = 0 };
try std.testing.expect(Reader.init(&buffer) == null);
}
test "basename" {
try std.testing.expectEqualStrings("fat", basename("/system/services/fat"));
try std.testing.expectEqualStrings("fat", basename("fat"));
}
+10 -14
View File
@@ -329,20 +329,16 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// service supervisor and the device manager spawns the drivers it discovers.
publishInitialRamdisk(boot_information);
// Hand over to user space: load /system/services/init (read off the boot volume by
// the loader) and spawn it as a real ring-3 process, PID 1. As the supervisor it
// brings up the system services (the VFS server, the device manager); the device
// manager then discovers the hardware and spawns each driver. init runs on its own
// address space, preemptively — this boot context becomes the BSP's idle loop.
if (boot_information.init_len != 0) {
status("/system/kernel: starting /system/services/init...\n");
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
process.spawnProcess(image, 4, &.{"/system/services/init"}) catch |err| {
statusPrint("/system/kernel: /system/services/init failed to load: {s}\n", .{@errorName(err)});
};
} else {
status("no /system/services/init on the boot volume.\n");
}
// Hand over to user space: spawn /system/services/init out of the ramdisk as a
// real ring-3 process, PID 1 — it rides the same table as every other binary.
// As the supervisor it brings up the system services (the VFS server, the device
// manager); the device manager then discovers the hardware and spawns each
// driver. init runs on its own address space, preemptively — this boot context
// becomes the BSP's idle loop.
status("/system/kernel: starting /system/services/init...\n");
process.spawnBundled("/system/services/init") catch |err| {
statusPrint("/system/kernel: /system/services/init failed to start: {s}\n", .{@errorName(err)});
};
// Become the idle task: drop below every real task and halt until an
// interrupt. The timer keeps preempting into init and any other work.
+16 -10
View File
@@ -140,6 +140,16 @@ pub fn setInitialRamdisk(image: []const u8) void {
ramdisk_image = image;
}
/// Spawn a bundled binary from the kernel by path. Used exactly once, to start
/// /system/services/init (PID 1) — every other spawn goes through the
/// `system_spawn` syscall.
pub fn spawnBundled(name: []const u8) !void {
const image = ramdisk_image orelse return error.NoInitialRamdisk;
const rd = initial_ramdisk.Reader.init(image) orelse return error.BadInitialRamdisk;
const item = rd.find(name) orelse return error.NotBundled;
try spawnProcess(item.blob, 4, &.{item.name});
}
/// The system_call surface, dispatched on the saved system_call number (`abi.SystemCall`).
/// This is the microkernel-minimal set — memory + scheduling only; file/device
/// I/O will arrive as IPC to user-space servers (docs/syscall.md). The result is
@@ -656,8 +666,11 @@ fn systemSpawn(state: *architecture.CpuState) void {
const rd = initial_ramdisk.Reader.init(image) orelse return fail(state);
const name = @as([*]const u8, @ptrFromInt(ptr))[0..len];
// Exact path first, basename fallback second; either way argv[0] (and hence
// the task name, and the log ring's attribution) is the stored full path.
const item = rd.find(name) orelse return fail(state); // no bundled binary by that name
var argv: [maximum_arguments][]const u8 = undefined;
argv[0] = name;
argv[0] = item.name;
var argc: usize = 1;
if (arguments_len != 0) {
const blob = @as([*]const u8, @ptrFromInt(arguments_ptr))[0..arguments_len];
@@ -669,15 +682,8 @@ fn systemSpawn(state: *architecture.CpuState) void {
}
}
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!std.mem.eql(u8, item.name, name)) continue;
const child = spawnProcessSupervised(item.blob, 4, argv[0..argc], t.id, exit_endpoint) catch return fail(state);
architecture.setSystemCallResult(state, child);
return;
}
fail(state); // no bundled binary by that name
const child = spawnProcessSupervised(item.blob, 4, argv[0..argc], t.id, exit_endpoint) catch return fail(state);
architecture.setSystemCallResult(state, child);
}
/// thread_spawn(entry, stack_top, arg) -> tid: start a task that shares the **caller's**
+67 -61
View File
@@ -1327,12 +1327,12 @@ fn procWorker() void {
/// strongest cheap proof of address-space isolation.
fn processTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: process\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0);
if (boot_information.init_len == 0) {
const image = bundledInit(boot_information) orelse {
check("initial_ramdisk carries /system/services/init", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
};
check("initial_ramdisk carries /system/services/init", true);
process.write_count = 0;
process.write_from_user = false;
@@ -1414,12 +1414,12 @@ fn spawnFaultingProcess() ?u32 {
/// time the harness out.
fn faultRecoveryTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: fault-recovery\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0);
if (boot_information.init_len == 0) {
const image = bundledInit(boot_information) orelse {
check("initial_ramdisk carries /system/services/init", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
};
check("initial_ramdisk carries /system/services/init", true);
process.write_count = 0;
process.fault_kill_count = 0;
@@ -1550,7 +1550,7 @@ fn threadJoinTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "join" })) true else |_| false;
break;
}
@@ -1594,7 +1594,7 @@ fn threadFutexTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "futex" })) true else |_| false;
break;
}
@@ -1642,7 +1642,7 @@ fn threadMutexTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "mutex" })) true else |_| false;
break;
}
@@ -1683,7 +1683,7 @@ fn threadIdTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "id" })) true else |_| false;
break;
}
@@ -1726,7 +1726,7 @@ fn threadAllocTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "alloc" })) true else |_| false;
break;
}
@@ -1769,7 +1769,7 @@ fn threadTlsTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "tls" })) true else |_| false;
break;
}
@@ -1811,7 +1811,7 @@ fn threadRwlockTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "rwlock" })) true else |_| false;
break;
}
@@ -1876,12 +1876,12 @@ fn taskReapTest(boot_information: *const BootInformation) void {
/// (write + sleep), and stays alive rather than exiting.
fn initTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: init\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0);
if (boot_information.init_len == 0) {
const image = bundledInit(boot_information) orelse {
check("initial_ramdisk carries /system/services/init", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
};
check("initial_ramdisk carries /system/services/init", true);
process.write_count = 0;
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |err| blk: {
log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)});
@@ -1912,12 +1912,12 @@ fn initTest(boot_information: *const BootInformation) void {
/// learns how big a buffer to bring).
fn processListTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: process-list\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0);
if (boot_information.init_len == 0) {
const image = bundledInit(boot_information) orelse {
check("initial_ramdisk carries /system/services/init", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
};
check("initial_ramdisk carries /system/services/init", true);
var spawned: u32 = 0;
if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}
@@ -1963,13 +1963,12 @@ fn processListTest(boot_information: *const BootInformation) void {
/// harness out rather than passing vacuously.
fn processKillTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: process-kill\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0);
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
const image = bundledInit(boot_information) orelse {
check("initial_ramdisk carries /system/services/init", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
};
check("initial_ramdisk carries /system/services/init", true);
const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(ramdisk) orelse {
check("initial_ramdisk image is valid", false);
@@ -2024,7 +2023,7 @@ fn processKillTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "process-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
spinner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "spinner" }, me, endpoint) catch 0;
break;
}
@@ -2041,7 +2040,7 @@ fn processKillTest(boot_information: *const BootInformation) void {
i = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "args-echo")) continue;
if (!eql(initial_ramdisk.basename(item.name), "args-echo")) continue;
clean = process.spawnProcessSupervised(item.blob, 4, &.{ "args-echo", "clean-exit" }, me, endpoint) catch 0;
break;
}
@@ -2089,12 +2088,12 @@ fn claimReleaseTest(boot_information: *const BootInformation) void {
check("cleanup released owner 222", devices_broker.ownerOf(1) == null);
// The death-path wiring: a real process dies holding a claim.
check("bootloader handed over /system/services/init", boot_information.init_len != 0);
if (boot_information.init_len == 0) {
const image = bundledInit(boot_information) orelse {
check("initial_ramdisk carries /system/services/init", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
};
check("initial_ramdisk carries /system/services/init", true);
const me = scheduler.currentId();
const endpoint = ipcsync.createIpcEndpoint() orelse {
check("exit endpoint allocated", false);
@@ -2148,7 +2147,7 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "vfs-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "vfs-test")) continue;
client = process.spawnProcessSupervised(item.blob, 4, &.{ "vfs-test", "park" }, me, endpoint) catch 0;
break;
}
@@ -2209,7 +2208,7 @@ fn signalsTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "process-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
runner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "signal-run" }, scheduler.currentId(), null) catch 0;
break;
}
@@ -2257,7 +2256,7 @@ fn driverRestartTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-restart" }, scheduler.currentId(), null) catch 0;
break;
}
@@ -2295,7 +2294,7 @@ fn usbReportTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0;
break;
}
@@ -2327,7 +2326,7 @@ fn deviceListTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0;
break;
}
@@ -2368,7 +2367,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-pci-restart" }, scheduler.currentId(), null) catch 0;
break;
}
@@ -2452,8 +2451,8 @@ fn usbStorageTest(boot_information: *const BootInformation) void {
/// the fat mount and the client's success.
fn fatMountTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: fat-mount\n", .{});
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over init and the initial_ramdisk", false);
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over the initial_ramdisk", false);
result();
return;
}
@@ -2464,8 +2463,7 @@ fn fatMountTest(boot_information: *const BootInformation) void {
return;
};
process.setInitialRamdisk(ramdisk);
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
const init_ok = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
const init_ok = if (process.spawnBundled("/system/services/init")) true else |_| false;
check("init spawned (boots the tree, incl. the fat server)", init_ok);
check("fat-test client spawned", spawnNamed(rd, "fat-test"));
result();
@@ -2473,30 +2471,28 @@ fn fatMountTest(boot_information: *const BootInformation) void {
fn bootServiceTreeTest(boot_information: *const BootInformation, comptime label: []const u8) void {
log("DANOS-TEST-BEGIN: " ++ label ++ "\n", .{});
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over init and the initial_ramdisk", false);
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over the initial_ramdisk", false);
result();
return;
}
const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
process.setInitialRamdisk(ramdisk);
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
const spawned = if (process.spawnBundled("/system/services/init")) true else |_| false;
check("init spawned (boots vfs, input, device-manager, and the USB chain)", spawned);
result();
}
fn orderlyShutdownTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: orderly-shutdown\n", .{});
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over init and the initial_ramdisk", false);
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over the initial_ramdisk", false);
result();
return;
}
const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
process.setInitialRamdisk(ramdisk);
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
const spawned = if (process.spawnBundled("/system/services/init")) true else |_| false;
check("init spawned as PID root of user space", spawned);
result();
}
@@ -2524,7 +2520,7 @@ fn acpiReportTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
_ = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
spawned = true;
break;
@@ -2562,7 +2558,7 @@ fn acpiParseTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "discovery")) continue;
if (!eql(initial_ramdisk.basename(item.name), "discovery")) continue;
_ = process.spawnProcessSupervised(item.blob, 4, &.{ "discovery", "1" }, scheduler.currentId(), null) catch 0;
spawned = true;
break;
@@ -2597,7 +2593,7 @@ fn supervisionTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "process-test")) continue;
if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "process-test", "run" })) true else |_| false;
break;
}
@@ -2921,7 +2917,7 @@ fn virtioGpuTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
break;
}
@@ -2962,7 +2958,7 @@ fn displayNativeTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
break;
}
@@ -3006,7 +3002,7 @@ fn displayReattachTest(boot_information: *const BootInformation) void {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue;
if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-scanout-restart" }, scheduler.currentId(), null) catch 0;
break;
}
@@ -3055,7 +3051,7 @@ fn argsTest(boot_information: *const BootInformation) void {
while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield();
scheduler.setPriority(4);
const expected = "args: args-echo alpha beta-42\n";
const expected = "args: /system/tests/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);
@@ -3065,11 +3061,21 @@ fn argsTest(boot_information: *const BootInformation) void {
/// 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.
/// The init ELF image out of the initial_ramdisk — init rides the table like
/// every other binary since the loader packs the whole /system tree.
fn bundledInit(boot_information: *const BootInformation) ?[]const u8 {
if (boot_information.initial_ramdisk_len == 0) return null;
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 return null;
const item = rd.find("/system/services/init") orelse return null;
return item.blob;
}
fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (eql(item.name, name)) {
if (eql(initial_ramdisk.basename(item.name), name)) {
return if (process.spawnProcess(item.blob, 4, &.{item.name})) true else |_| false;
}
}
@@ -3082,7 +3088,7 @@ fn spawnNamedWithArg(rd: initial_ramdisk.Reader, name: []const u8, arg: []const
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
if (eql(item.name, name)) {
if (eql(initial_ramdisk.basename(item.name), name)) {
return if (process.spawnProcess(item.blob, 4, &.{ item.name, arg })) true else |_| false;
}
}
@@ -56,8 +56,8 @@ const virtio_gpu_pci_class: u64 = pci_class.ClassCode.pack(.{
/// its registered id as argv[1].
fn pciDriverForIdentity(identity: u64) ?[]const u8 {
return switch (identity) {
xhci_pci_class => "usb-xhci-bus",
virtio_gpu_pci_class => "virtio-gpu",
xhci_pci_class => "/system/drivers/usb-xhci-bus",
virtio_gpu_pci_class => "/system/drivers/virtio-gpu",
else => null,
};
}
@@ -67,8 +67,8 @@ fn pciDriverForIdentity(identity: u64) ?[]const u8 {
/// nodes the kernel used to build). ps2-bus is a singleton that finds both its
/// devices by hid once spawned, so keyboard and mouse map to the same name.
fn hidDriverFor(hid: []const u8) ?[]const u8 {
if (std.mem.eql(u8, hid, "PNP0303")) return "ps2-bus"; // PS/2 keyboard
if (std.mem.eql(u8, hid, "PNP0F13")) return "ps2-bus"; // PS/2 mouse
if (std.mem.eql(u8, hid, "PNP0303")) return "/system/drivers/ps2-bus"; // PS/2 keyboard
if (std.mem.eql(u8, hid, "PNP0F13")) return "/system/drivers/ps2-bus"; // PS/2 mouse
return null;
}
@@ -95,9 +95,9 @@ fn usbDriverForIdentity(identity: u64) ?[]const u8 {
@intFromEnum(usb_ids.mass_storage.Protocol.bulk_only),
);
return switch (identity) {
keyboard => "usb-hid-keyboard",
mouse => "usb-hid-mouse",
storage => "usb-storage",
keyboard => "/system/drivers/usb-hid-keyboard",
mouse => "/system/drivers/usb-hid-mouse",
storage => "/system/drivers/usb-storage",
else => null,
};
}
@@ -429,7 +429,7 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
writeLine("/system/services/device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id });
// Resilience drill (V6): once, kill the virtio-gpu driver a moment after it hellos, so
// the normal restart policy respawns it — the compositor must survive and re-attach.
if (test_scanout_restart_mode and !test_scanout_killed and std.mem.eql(u8, driver.name(), "virtio-gpu")) {
if (test_scanout_restart_mode and !test_scanout_killed and std.mem.eql(u8, driver.name(), "/system/drivers/virtio-gpu")) {
test_scanout_killed = true;
test_kill_pid = sender;
test_kill_due_ns = system.clock() + 1_500_000_000;
@@ -498,7 +498,7 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize {
// Only the xHCI reporter is the drill's victim — pci-bus also reports
// now, and whichever finishes second must not trigger the kill.
if (driverByProcess(sender)) |driver| {
if (std.mem.eql(u8, driver.name(), "usb-xhci-bus")) {
if (std.mem.eql(u8, driver.name(), "/system/drivers/usb-xhci-bus")) {
// Delayed, not immediate: the device-list scenario's subscriber
// needs a window to enumerate and subscribe before the events.
test_usb_killed = true;
+14 -6
View File
@@ -27,11 +27,19 @@ const build_options = @import("build_options");
/// the log-flush one-shot writes it once at boot.
const log_path = "/mnt/usb/DANOS.LOG";
/// The system services init brings up at boot, in order. This is init's policy — the
/// microkernel keeps such choices in user space, not the kernel. Drivers are absent
/// on purpose: the device manager owns those. (A future init reads this from a
/// manifest under /system/services instead of a hardcoded list.)
const boot_services = [_][]const u8{ "vfs", "input", "device-manager", "fat", "display", "display-demo" };
/// The system services init brings up at boot, in order, by binary path. This is
/// init's policy — the microkernel keeps such choices in user space, not the
/// kernel. Drivers are absent on purpose: the device manager owns those. (A
/// future init reads this from a manifest under /system/services instead of a
/// hardcoded list.)
const boot_services = [_][]const u8{
"/system/services/vfs",
"/system/services/input",
"/system/services/device-manager",
"/system/services/fat",
"/system/services/display",
"/system/services/display-demo",
};
/// The live process id of each boot service (0 = not running), indexed by its position
/// in `boot_services`, plus how many times init has restarted it. init supervises these:
@@ -84,7 +92,7 @@ pub fn main() void {
// and forget: it polls for the mount itself, and is deliberately NOT one of
// init's supervised children (a transient one-shot must not be stopped-and-
// waited-for during shutdown).
_ = runtime.system.spawn("log-flush");
_ = runtime.system.spawn("/system/services/log-flush");
// Subscribe to power events (retry: the power service registers well after
// init starts). Best-effort — without it, a `terminate` signal still
@@ -147,8 +147,8 @@ pub fn main(init: runtime.process.Init) void {
const spinner = runtime.system.spawnSupervised("process-test", &.{"spinner"}, endpoint) orelse fail("spawn spinner");
runtime.system.sleep(100); // let the sleeper block and the spinner get a core
if (!listed(sleeper, "process-test")) fail("sleeper not in process_enumerate");
if (!listed(spinner, "process-test")) fail("spinner not in process_enumerate");
if (!listed(sleeper, "/system/tests/process-test")) fail("sleeper not in process_enumerate");
if (!listed(spinner, "/system/tests/process-test")) fail("spinner not in process_enumerate");
// Kills that must be refused: a kernel task (id 0), and an id that was never
// issued — both -ESRCH. (-EPERM needs a second supervisor; the kernel-level
@@ -167,8 +167,8 @@ pub fn main(init: runtime.process.Init) void {
if (!runtime.system.kill(spinner)) fail("kill spinner");
if (awaitChildExit(endpoint) != spinner) fail("spinner exit notification");
if (listed(sleeper, "process-test")) fail("sleeper still listed after kill");
if (listed(spinner, "process-test")) fail("spinner still listed after kill");
if (listed(sleeper, "/system/tests/process-test")) fail("sleeper still listed after kill");
if (listed(spinner, "/system/tests/process-test")) fail("spinner still listed after kill");
// M17.2: both children were killed by us, and the reason says so — the whole
// restart-policy input, read through the runtime like a real supervisor would.