This commit is contained in:
2026-07-03 15:09:16 +01:00
parent df774691c8
commit 31bf86af56
12 changed files with 492 additions and 41 deletions
+108 -8
View File
@@ -15,6 +15,7 @@ const arch = @import("arch");
const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const sched = @import("sched.zig");
const ipc = @import("ipc.zig");
/// Formatted write straight to serial, independent of the framebuffer console.
fn log(comptime fmt: []const u8, args: anytype) void {
@@ -60,6 +61,12 @@ pub fn run(case: []const u8, boot_info: *const BootInfo) void {
schedTest();
} else if (eql(case, "priority")) {
priorityTest();
} else if (eql(case, "sleep")) {
sleepTest();
} else if (eql(case, "event")) {
eventTest();
} else if (eql(case, "ipc")) {
ipcTest();
} else if (eql(case, "fault-ud")) {
faultInvalidOpcode();
} else if (eql(case, "fault-pf")) {
@@ -211,27 +218,46 @@ fn heapTest() void {
result();
}
/// Verify the calibrated clock: a plausible measured LAPIC frequency, the
/// configured tick rate, and monotonic uptime that advances with real ticks.
/// Verify the calibrated clocks: sane measured frequencies, monotonic uptime that
/// advances with real ticks, and — the point of the TSC clock — nanosecond
/// resolution far finer than the 1 ms tick, with the unit functions consistent.
fn clock() void {
log("DANOS-TEST-BEGIN: clock\n", .{});
// Calibration produced a sane LAPIC frequency (roughly 1 MHz .. 100 GHz).
const lapic = arch.lapicHz();
check("LAPIC frequency measured", lapic > 1_000_000 and lapic < 100_000_000_000);
const tsc = arch.tscHz();
check("TSC frequency measured", tsc > 100_000_000 and tsc < 100_000_000_000);
// Wait for ~5 real ticks and confirm uptime advanced by about that many ms
// (tick rate is 1000 Hz, so 1 tick == 1 ms).
// Uptime advances over ~5 real ticks (1000 Hz => 1 tick == 1 ms).
const start_ticks = arch.ticks();
const start_ms = arch.uptimeMs();
const start_ms = arch.millis();
var spins: u64 = 0;
while (arch.ticks() < start_ticks + 5 and spins < 5_000_000_000) spins +%= 1;
const elapsed_ms = arch.uptimeMs() - start_ms;
const elapsed_ms = arch.millis() - start_ms;
check("uptime advances with ticks", elapsed_ms >= 5 and elapsed_ms < 100);
// Sub-millisecond resolution: spin until nanos() first advances, then confirm
// that first step happened within a millisecond — so nanos() resolves finer
// than the 1 ms tick (a tick clock's smallest step *is* 1 ms). Spinning to the
// first change is robust to QEMU's coarse TSC update granularity.
const n1 = arch.nanos();
var s2: u64 = 0;
while (arch.nanos() == n1 and s2 < 10_000_000) s2 +%= 1;
const n2 = arch.nanos();
check("nanos() has sub-millisecond resolution", n2 > n1 and (n2 - n1) < 1_000_000);
// The unit functions agree (within rounding).
const ns = arch.nanos();
check("nanos/micros/millis are consistent", diffWithin(arch.micros(), ns / 1000, 1000) and diffWithin(arch.millis(), ns / 1_000_000, 2));
result();
}
fn diffWithin(a: u64, b: u64, tol: u64) bool {
return if (a > b) a - b <= tol else b - a <= tol;
}
// --- scheduler tests ------------------------------------------------------
var counters = [_]u64{0} ** 3;
@@ -292,7 +318,7 @@ fn taskLow() void {
fn priorityTest() void {
log("DANOS-TEST-BEGIN: priority\n", .{});
sched.setPreemption(false);
sched.setPriority(0); // run this observer task last, after all workers
sched.setPriority(1); // above the idle task (0), below the workers — runs last
run_n = 0;
sched.spawn(taskLow, 2);
@@ -308,6 +334,80 @@ fn priorityTest() void {
result();
}
var event_wq: sched.WaitQueue = .{};
var event_stage: u32 = 0;
fn eventWaiter() void {
event_stage = 1; // reached the wait
sched.wait(&event_wq); // block until woken
event_stage = 3; // woken and resumed
sched.exit();
}
/// Event-based blocking: a task blocks on a wait queue and is woken. The waiter is
/// higher priority, so waking it preempts us and it runs to completion at once.
fn eventTest() void {
log("DANOS-TEST-BEGIN: event\n", .{});
event_stage = 0;
sched.spawn(eventWaiter, 6); // higher priority than this task (4)
var spins: u64 = 0;
while (event_stage != 1 and spins < 1_000_000_000) : (spins += 1) sched.yield();
check("waiter reached the wait and blocked", event_stage == 1);
sched.wake(&event_wq);
check("wake resumed the blocked waiter (preempting)", event_stage == 3);
result();
}
var channel: ipc.Channel(u64, 4) = .{};
var recv_sum: u64 = 0;
var recv_count: u64 = 0;
fn producer() void {
var i: u64 = 1;
while (i <= 100) : (i += 1) channel.send(i);
sched.exit();
}
fn consumer() void {
var n: u64 = 0;
while (n < 100) : (n += 1) {
recv_sum += channel.recv();
recv_count += 1;
}
sched.exit();
}
/// IPC: a producer and consumer pass 100 messages through a 4-slot channel. The
/// small buffer forces the channel full and empty repeatedly, exercising both the
/// blocking-send and blocking-recv paths. The messages must arrive intact.
fn ipcTest() void {
log("DANOS-TEST-BEGIN: ipc\n", .{});
channel = .{};
recv_sum = 0;
recv_count = 0;
sched.spawn(consumer, 5); // above this task (4) so they run and we observe after
sched.spawn(producer, 5);
var spins: u64 = 0;
while (recv_count < 100 and spins < 2_000_000_000) : (spins += 1) sched.yield();
check("all 100 messages received", recv_count == 100);
check("messages arrived intact (sum 1..100 == 5050)", recv_sum == 5050);
result();
}
/// Blocking: sleep(50) should block this task for about 50 ms (measured on the
/// calibrated clock) — not busy-wait — while the idle task runs.
fn sleepTest() void {
log("DANOS-TEST-BEGIN: sleep\n", .{});
const t0 = arch.millis();
sched.sleep(50);
const elapsed = arch.millis() - t0;
check("sleep(50) blocked for ~50 ms", elapsed >= 50 and elapsed <= 70);
result();
}
fn faultInvalidOpcode() void {
log("DANOS-TEST-BEGIN: fault-ud\n", .{});
asm volatile ("ud2");