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
@@ -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.