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:
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user