M8: initrd handoff + multi-binary shipping

Ship more than one user binary: the bootloader now ferries an initrd bundle
(the VFS server + future drivers) alongside sbin/init, and the kernel unpacks
and spawns each program.

- src/user/proto/initrd.zig: the container format (Header{magic,count} +
  Entry{name[32],offset,len} + blobs) with a validating Reader, shared by the
  kernel and the packer.
- tools/mkinitrd.py: host-side packer (Python — trivial format, and sidesteps
  the reworked Zig 0.16 std fs/args API). build.zig runs it on the built user
  binaries via addSystemCommand and installs initrd.img to zig-out/bin + the
  ESP root.
- BootInfo gains initrd_base/initrd_len; efi.zig loadInit refactored into a
  shared loadFile, and loadInitrd ferries \initrd.img into surviving
  LoaderData like init.
- main.zig startInitrdBinaries(): parse the image, spawn every entry (kernel-
  spawns-all for now; init takes over via sys_spawn later).
- sbin/vfs.zig: a heartbeat stub (the real VFS server is M9), packed into the
  initrd to prove the pipeline.
- New `initrd` test: parse + spawn + confirm the vfs stub reaches ring 3 and
  heartbeats. Harness ships initrd.img on the ESP. Suite 30/30.
This commit is contained in:
Daniel Samson
2026-07-09 07:36:25 +01:00
parent eabb81684a
commit 750a73f050
9 changed files with 272 additions and 10 deletions
+30
View File
@@ -147,6 +147,12 @@ pub fn build(b: *std.Build) void {
},
});
// The initrd container format, shared by the kernel (unpacks it) and the
// build-time packer tools/mkinitrd.zig (produces it). No dependencies.
const initrd_mod = b.addModule("initrd", .{
.root_source_file = b.path("src/user/proto/initrd.zig"),
});
// Compile-time config the kernel reads as `@import("build_options")`. The
// QEMU test harness sets -Dtest-case=<name> to run one self-test at boot.
const test_case = b.option([]const u8, "test-case", "Kernel self-test case to run at boot (see src/kernel/tests.zig)");
@@ -182,6 +188,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "platform", .module = platform_mod },
.{ .name = "config", .module = config_mod },
.{ .name = "build_options", .module = build_options_mod },
.{ .name = "initrd", .module = initrd_mod },
},
}),
});
@@ -206,6 +213,26 @@ pub fn build(b: *std.Build) void {
const init_exe = addUserBinary(b, kernel_target, rt_mod, "init", "sbin/init.zig");
b.installArtifact(init_exe);
// --- initrd: a bundle of extra user binaries (VFS server + drivers) ---
// Each is built by the same user-binary recipe, then packed into one image by
// the host-side mkinitrd tool. The bootloader ferries the image to the kernel,
// which unpacks it and spawns each program (src/user/proto/initrd.zig).
const vfs_exe = addUserBinary(b, kernel_target, rt_mod, "vfs", "sbin/vfs.zig");
// Pack the user binaries into the initrd image with the host-side Python tool
// (the container format is trivial, and Python sidesteps std API churn). Args:
// mkinitrd.py <out> [<name> <file>]... — one name/file pair per binary.
const mk_run = b.addSystemCommand(&.{"python3"});
mk_run.addFileArg(b.path("tools/mkinitrd.py"));
const initrd_img = mk_run.addOutputFileArg("initrd.img");
mk_run.addArg("vfs");
mk_run.addFileArg(vfs_exe.getEmittedBin());
// Install the image to zig-out/bin (so the QEMU test harness picks it up like
// the other binaries). The run-x86-64 ESP install is added below.
const initrd_install = b.addInstallFile(initrd_img, "bin/initrd.img");
b.getInstallStep().dependOn(&initrd_install.step);
// Boot methods live in src/boot/, one per way of getting the kernel running.
// Each is its own binary/entry (a loader is built for its own target); today
// that's UEFI for x86-64, with room for e.g. a device-tree path for the Pis.
@@ -268,6 +295,8 @@ pub fn build(b: *std.Build) void {
const init_install = b.addInstallArtifact(init_exe, .{
.dest_dir = .{ .override = .{ .custom = "esp/sbin" } },
});
// ...and the initrd (VFS server + drivers) from the volume root.
const initrd_esp_install = b.addInstallFile(initrd_img, "esp/initrd.img");
// The firmware needs to write NVRAM, so give it a writable copy of the vars.
const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_vars });
@@ -305,6 +334,7 @@ pub fn build(b: *std.Build) void {
run_efi.step.dependOn(&efi_install.step);
run_efi.step.dependOn(&kernel_install.step);
run_efi.step.dependOn(&init_install.step);
run_efi.step.dependOn(&initrd_esp_install.step);
const run_efi_step = b.step("run-x86-64", "Boot the x86-64 kernel in QEMU (UEFI/OVMF); serial0 is logged to zig-out/run-x86-64-serial0-<timestamp>.log");
run_efi_step.dependOn(&run_efi.step);