IPC
This commit is contained in:
+108
-8
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user