C2: migrate consumers off the runtime shim to direct concern-module imports

Every user binary and the two device-logic library modules (pci, usb) now
`@import` the concern modules directly instead of aliasing through `runtime`:

  runtime.ipc/process/time/service/input/block/display  -> @import("<module>")
  runtime.device / runtime.device_manager               -> @import("driver")
  runtime.fs                                             -> @import("file-system")
  runtime.Thread                                         -> @import("thread").Thread
  runtime.system.{write,writeRecord,klog*}              -> logging.*
  runtime.system.{sleep,timerOnce,wallClock,clock}     -> time.*
  runtime.system.{spawn*,kill,exit,yield,processes,...}-> process.*
  runtime.system.{mmap,munmap,PROT_*}                  -> memory.*
  runtime.dma.* / runtime.shared_memory.* / runtime.allocator -> memory.*

Each consumer keeps its own alias name (e.g. `const device = @import("driver")`),
so call sites are unchanged and there are no collisions with local `driver`
variables. build.zig now injects the concern modules into every user binary via
`default_imports`; pci/usb module import lists were updated to match.

The `runtime` and `system` shims remain for one more step (root.zig still uses
runtime); they are deleted in C5. Nothing but root.zig imports `runtime` now.

Verified: zig build, zig build test, and 17 QEMU cases (smoke, device-manager,
logger, fat-mount, fat-mutations, usb-storage, usb-hid, display-native,
virtio-gpu, input, thread-spawn, thread-mutex, process-kill, shared-memory,
driver-restart, acpi-ps2, pci-scan).
This commit is contained in:
Daniel Samson
2026-07-22 23:28:34 +01:00
parent dded46726b
commit 23bcd77c58
37 changed files with 783 additions and 692 deletions
+28 -24
View File
@@ -18,7 +18,11 @@
//! composing into a clean poweroff.
const std = @import("std");
const runtime = @import("runtime");
const ipc = @import("ipc");
const process = @import("process");
const time = @import("time");
const memory = @import("memory");
const logging = @import("logging");
const power_protocol = @import("power-protocol");
const build_options = @import("build_options");
@@ -55,7 +59,7 @@ const boot_services = if (build_options.diagnose) [_][]const u8{
var child_ids: [boot_services.len]u32 = .{0} ** boot_services.len;
var restart_counts: [boot_services.len]u32 = .{0} ** boot_services.len;
var shutting_down = false;
var supervision_endpoint: runtime.ipc.Handle = 0;
var supervision_endpoint: ipc.Handle = 0;
/// Give up restarting a service after this many crashes — a crash-loop cap, so a service
/// that faults immediately on every spawn doesn't respawn forever.
@@ -68,28 +72,28 @@ pub fn main() void {
// free it. A fault here would kill init before it heartbeats — so the init
// test doubles as the heap regression test. (C code links the same heap via
// the extern malloc/free symbols; Zig code uses this allocator.)
const gpa = runtime.allocator();
const gpa = memory.allocator();
if (gpa.alloc(u8, 64)) |buffer| {
const message = "/system/services/init: heap ok\n";
@memcpy(buffer[0..message.len], message);
_ = runtime.system.write(buffer[0..message.len]);
_ = logging.write(buffer[0..message.len]);
gpa.free(buffer);
} else |_| {}
// One endpoint carries everything init waits on: children's exit
// notifications (they are spawned supervised against it), init's own
// signals, and power events it subscribes to. All arrive in the loop below.
supervision_endpoint = runtime.ipc.createIpcEndpoint() orelse {
_ = runtime.system.write("/system/services/init: no endpoint\n");
supervision_endpoint = ipc.createIpcEndpoint() orelse {
_ = logging.write("/system/services/init: no endpoint\n");
return;
};
_ = runtime.process.bindSignals(supervision_endpoint);
_ = process.bindSignals(supervision_endpoint);
// Bring up the boot services, supervised so init can stop them cleanly.
// Best-effort and silent: each service announces its own readiness, and in
// an isolation test with no initial-ramdisk the spawns simply no-op.
for (boot_services, 0..) |service, i| {
if (runtime.system.spawnSupervised(service, &.{}, supervision_endpoint)) |id| child_ids[i] = id;
if (process.spawnSupervised(service, &.{}, supervision_endpoint)) |id| child_ids[i] = id;
}
// Subscribe to power events (retry: the power service registers well after
@@ -103,18 +107,18 @@ pub fn main() void {
// wakes only for real work (signals, power events, children's exits), never for a
// periodic beat. `build_options.serial` is comptime, so the heartbeat — its timer
// and the handler below — folds away entirely when serial is off.
if (build_options.serial) _ = runtime.system.timerOnce(supervision_endpoint, 1000);
if (build_options.serial) _ = time.timerOnce(supervision_endpoint, 1000);
var receive: [power_protocol.message_maximum]u8 = undefined;
while (true) {
const got = runtime.ipc.replyWait(supervision_endpoint, &.{}, &receive, null);
if (runtime.process.signalsFrom(got.badge)) |signals| {
const got = ipc.replyWait(supervision_endpoint, &.{}, &receive, null);
if (process.signalsFrom(got.badge)) |signals| {
if (signals.has(.terminate)) shutDown();
continue;
}
if (build_options.serial and got.isTimer()) {
_ = runtime.system.write("/system/services/init: heartbeat\n");
_ = runtime.system.timerOnce(supervision_endpoint, 1000);
_ = logging.write("/system/services/init: heartbeat\n");
_ = time.timerOnce(supervision_endpoint, 1000);
continue;
}
if (got.isMessage() and got.len >= 2 and receive[0] == @intFromEnum(power_protocol.Operation.event)) {
@@ -141,7 +145,7 @@ fn restartChild(id: u32) void {
if (child_ids[i] != id) continue;
child_ids[i] = 0;
// An unknown reason (the record aged out) is treated as a crash worth restarting.
const reason = runtime.process.exitReason(id) orelse .fault;
const reason = process.exitReason(id) orelse .fault;
if (reason == .exited) {
std.log.info("{s} exited cleanly; not restarting", .{service});
return;
@@ -152,7 +156,7 @@ fn restartChild(id: u32) void {
return;
}
std.log.info("{s} died ({s}); restarting ({d}/{d})", .{ service, @tagName(reason), restart_counts[i], maximum_restarts });
if (runtime.system.spawnSupervised(service, &.{}, supervision_endpoint)) |new_id| child_ids[i] = new_id;
if (process.spawnSupervised(service, &.{}, supervision_endpoint)) |new_id| child_ids[i] = new_id;
return;
}
// An untracked child (e.g. the log-flush one-shot): nothing to restart.
@@ -161,11 +165,11 @@ fn restartChild(id: u32) void {
/// Look up the power service and subscribe our endpoint (handed over as the
/// call's capability) so events arrive as buffered messages here.
fn subscribePower() void {
var handle: ?runtime.ipc.Handle = null;
var handle: ?ipc.Handle = null;
var tries: u32 = 0;
while (handle == null and tries < 200) : (tries += 1) {
handle = runtime.ipc.lookup(.power);
if (handle == null) runtime.system.sleep(20);
handle = ipc.lookup(.power);
if (handle == null) time.sleepMillis(20);
}
// A missing power service is not fatal — init proceeds to its heartbeat and
// a `terminate` signal still drives shutdown. Silent so the no-ramdisk init
@@ -173,7 +177,7 @@ fn subscribePower() void {
const h = handle orelse return;
const request = power_protocol.Subscribe{};
var reply: [power_protocol.message_maximum]u8 = undefined;
_ = runtime.ipc.callCap(h, std.mem.asBytes(&request), &reply, supervision_endpoint) catch {};
_ = ipc.callCap(h, std.mem.asBytes(&request), &reply, supervision_endpoint) catch {};
}
/// The stop sequence: persist the log while storage is still up, then terminate
@@ -182,20 +186,20 @@ fn subscribePower() void {
/// power service to enter S5.
fn shutDown() void {
shutting_down = true; // the stop loop below kills children — those deaths aren't crashes
_ = runtime.system.write("/system/services/init: shutting down\n");
_ = logging.write("/system/services/init: shutting down\n");
// Log persistence is the logger service's job: it is the LAST boot service,
// so the reverse-order stop below terminates it first and its final drain
// runs while the whole storage chain is still alive.
var i = boot_services.len;
while (i > 0) {
i -= 1;
if (child_ids[i] != 0) runtime.process.stop(child_ids[i], 2000, supervision_endpoint);
if (child_ids[i] != 0) process.stop(child_ids[i], 2000, supervision_endpoint);
}
if (runtime.ipc.lookup(.power)) |h| {
if (ipc.lookup(.power)) |h| {
const request = power_protocol.Shutdown{};
var reply: [power_protocol.message_maximum]u8 = undefined;
_ = runtime.ipc.call(h, std.mem.asBytes(&request), &reply) catch {};
_ = ipc.call(h, std.mem.asBytes(&request), &reply) catch {};
}
// If S5 did not take, init has nothing left to do but idle.
while (true) runtime.system.sleep(1000);
while (true) time.sleepMillis(1000);
}