reorg: split the runtime into library/kernel concern modules (C1)

The one giant `runtime` module (with a `system.zig` that was itself a dumping
ground of unrelated syscalls) is split into directly-importable, flat concern
modules under library/kernel/:

  system-call  ipc  memory  process  thread  time  logging  file-system  service  start
  (+ the device/service clients: device, device-manager, block, display, input)

system.zig is dissolved — its functions moved to their concern home (mmap ->
memory, spawn/kill/exit -> process, sleep/clock -> time, write/klog -> logging,
fs* -> file-system). `memory` merges heap+dma+shared-memory behind one flat API
(memory.allocator/dmaAlloc/sharedCreate/mmap), keeping heap's state and malloc
export single. The memory<->thread dependency cycle (heap needs Thread.Mutex,
thread needs mmap) is broken by having thread allocate its own stack via the raw
mmap syscall, so the module graph is a DAG.

This is the atomic step: all 42 internal cross-imports flip from relative to
module imports at once. `runtime.zig` and `system.zig` become thin re-export
SHIMS so the ~38 consumers keep compiling on `runtime.*` untouched; they migrate
to direct imports in C2, after which the shims are deleted (C5).

zig build + zig build test green; 14 QEMU cases pass (smoke, process,
process-kill, thread-spawn/join, logger, vfs, fat-mount, display-native,
usb-storage, virtio-gpu, device-manager, input, power-button).
This commit is contained in:
Daniel Samson
2026-07-22 22:53:13 +01:00
parent 3e69712b97
commit 60f32ee9ff
21 changed files with 636 additions and 467 deletions
+35 -17
View File
@@ -11,7 +11,33 @@
//! CLOCK_REALTIME) layered on top later.
const std = @import("std");
const system = @import("system.zig");
const sc = @import("system-call");
// --- raw syscall wrappers, formerly in the system.zig dumping ground ---
/// Monotonic nanoseconds since boot — the raw reading; `now()` wraps it in an `Instant`.
/// Never runs backward. Not wall-clock time (see `wallClock`).
pub fn clock() u64 {
return @intCast(sc.systemCall0(.clock));
}
/// Wall-clock time in Unix epoch seconds (UTC), from the RTC — the real date/time, what a
/// filesystem stamps as an mtime. Unlike `clock` (monotonic since boot), this is calendar time.
pub fn wallClock() u64 {
return @intCast(sc.systemCall0(.wall_clock));
}
/// Block the caller for `ms` milliseconds — the raw, coarse, allocation-free form.
pub fn sleepMillis(ms: u64) void {
_ = sc.systemCall1(.sleep, ms);
}
/// Arm a one-shot timer: after `ms` the kernel posts a timer notification
/// (`ipc.Received.isTimer`) to `endpoint` (a handle from `ipc.createIpcEndpoint`). Unlike
/// `sleep`, does not block — a service keeps serving IPC while the deadline is pending.
pub fn timerOnce(endpoint: usize, ms: u64) bool {
return sc.systemCall2(.timer_bind, endpoint, ms) == 0;
}
const nanos_per_micro: u64 = 1_000;
const nanos_per_milli: u64 = 1_000_000;
@@ -90,32 +116,27 @@ pub const Instant = struct {
/// The current monotonic time.
pub fn now() Instant {
return .{ .ns = system.clock() };
return .{ .ns = clock() };
}
/// Monotonic nanoseconds since boot — the raw `clock()` reading, for callers that
/// want a plain integer instead of an `Instant`.
pub fn monotonicNanos() u64 {
return system.clock();
return clock();
}
/// Whether the monotonic clock is usable. The kernel returns 0 until the TSC is
/// calibrated (`tsc_hz == 0`); a caller that needs real time can treat that as
/// "unavailable" instead of assuming the clock advances.
pub fn available() bool {
return system.clock() != 0;
return clock() != 0;
}
/// Block the caller for at least `d`, rounded up to the kernel's millisecond
/// granularity. For sub-millisecond precision the scheduler cannot express, use
/// `spin`.
/// `spin`. (The raw millisecond form is `sleepMillis`.)
pub fn sleep(d: Duration) void {
system.sleep(d.ceilMillis());
}
/// Block the caller for `ms` milliseconds — the coarse, allocation-free form.
pub fn sleepMillis(ms: u64) void {
system.sleep(ms);
sleepMillis(d.ceilMillis());
}
/// Busy-wait until `d` has elapsed, polling the monotonic clock. This burns the CPU
@@ -126,13 +147,10 @@ pub fn spin(d: Duration) void {
while (!deadline.reached()) {}
}
/// Arm a one-shot timer against `endpoint` (a handle from `ipc.createIpcEndpoint`):
/// after `d` the kernel posts a timer notification (`ipc.Received.isTimer`) there.
/// Unlike `sleep`, this does not block — a service can keep serving IPC on the same
/// endpoint while the deadline is pending. Rounds `d` up to milliseconds; returns
/// false if the timer could not be armed. See `system.timerOnce`.
/// The ergonomic `Duration` form of `timerOnce`: arm a one-shot timer against `endpoint`
/// for `d` (rounded up to milliseconds). Returns false if the timer could not be armed.
pub fn after(endpoint: usize, d: Duration) bool {
return system.timerOnce(endpoint, d.ceilMillis());
return timerOnce(endpoint, d.ceilMillis());
}
test "Duration unit conversions round toward zero" {