IPC
This commit is contained in:
@@ -44,6 +44,23 @@ var ticks_per_ms: u32 = 0;
|
||||
/// The periodic-interrupt frequency the timer is armed at, once initTimer runs.
|
||||
var timer_hz: u32 = 0;
|
||||
|
||||
/// TSC (Time Stamp Counter) calibration: cycles per second, and the count at boot.
|
||||
/// The TSC is a per-core cycle counter, giving a ~nanosecond high-resolution
|
||||
/// monotonic clock — far finer than the millisecond timer tick.
|
||||
var tsc_hz: u64 = 0;
|
||||
var tsc_base: u64 = 0;
|
||||
|
||||
/// Read the 64-bit Time Stamp Counter.
|
||||
fn rdtsc() u64 {
|
||||
var low: u32 = undefined;
|
||||
var high: u32 = undefined;
|
||||
asm volatile ("rdtsc"
|
||||
: [low] "={eax}" (low),
|
||||
[high] "={edx}" (high),
|
||||
);
|
||||
return (@as(u64, high) << 32) | low;
|
||||
}
|
||||
|
||||
fn read(reg: u32) u32 {
|
||||
return @as(*volatile u32, @ptrFromInt(base + reg)).*;
|
||||
}
|
||||
@@ -78,10 +95,10 @@ pub fn init() void {
|
||||
write(reg_spurious, 0x100 | spurious_vector); // bit 8 = software enable
|
||||
}
|
||||
|
||||
/// Measure the LAPIC timer's counting rate against the PIT (channel 2, which can
|
||||
/// be polled without interrupts). We run the LAPIC timer one-shot from its max
|
||||
/// count while the PIT counts out a known 10 ms, then see how far the LAPIC got.
|
||||
/// This gives real time, which the RTOS quanta guarantees depend on.
|
||||
/// Measure the LAPIC timer's and the TSC's rates against the PIT (channel 2, which
|
||||
/// can be polled without interrupts). We run the LAPIC timer one-shot from its max
|
||||
/// count and snapshot the TSC while the PIT counts out a known 10 ms, then see how
|
||||
/// far each got. This gives real time, which the RTOS timing guarantees depend on.
|
||||
pub fn calibrate() void {
|
||||
const pit_hz = 1_193_182;
|
||||
const calib_ms = 10;
|
||||
@@ -99,13 +116,18 @@ pub fn calibrate() void {
|
||||
io.outb(0x43, 0xB0); // channel 2, lo/hi byte, mode 0
|
||||
io.outb(0x42, @truncate(pit_count));
|
||||
io.outb(0x42, @truncate(pit_count >> 8));
|
||||
io.outb(0x61, (io.inb(0x61) & 0xFC) | 0x01); // gate high -> start
|
||||
|
||||
const tsc_start = rdtsc();
|
||||
io.outb(0x61, (io.inb(0x61) & 0xFC) | 0x01); // gate high -> start
|
||||
while (io.inb(0x61) & 0x20 == 0) {} // poll channel-2 output until terminal count
|
||||
const tsc_end = rdtsc();
|
||||
|
||||
const elapsed = 0xFFFFFFFF - read(reg_timer_current);
|
||||
write(reg_timer_initial, 0); // stop the timer
|
||||
|
||||
ticks_per_ms = elapsed / calib_ms;
|
||||
tsc_hz = (tsc_end -% tsc_start) * (1000 / calib_ms); // cycles/10ms -> cycles/s
|
||||
tsc_base = rdtsc(); // the clock's zero point (boot)
|
||||
}
|
||||
|
||||
/// Arm the LAPIC timer to fire on `timer_vector` at `hz` (periodic). Requires
|
||||
@@ -128,10 +150,29 @@ pub fn lapicHz() u64 {
|
||||
return @as(u64, ticks_per_ms) * 1000;
|
||||
}
|
||||
|
||||
/// Milliseconds since the timer started (monotonic). Ticks accrue at timer_hz.
|
||||
pub fn uptimeMs() u64 {
|
||||
if (timer_hz == 0) return 0;
|
||||
return ticks() * 1000 / timer_hz;
|
||||
/// Measured TSC frequency (Hz).
|
||||
pub fn tscHz() u64 {
|
||||
return tsc_hz;
|
||||
}
|
||||
|
||||
// Monotonic high-resolution clock, from the TSC. A function per resolution, each
|
||||
// scaling the cycle delta directly at its unit (the 128-bit intermediate avoids
|
||||
// overflow across a long uptime). nanos() resolves to a few ns; millis() is what
|
||||
// the scheduler uses for sleep deadlines.
|
||||
|
||||
pub fn nanos() u64 {
|
||||
if (tsc_hz == 0) return 0;
|
||||
return @intCast(@as(u128, rdtsc() -% tsc_base) * 1_000_000_000 / tsc_hz);
|
||||
}
|
||||
|
||||
pub fn micros() u64 {
|
||||
if (tsc_hz == 0) return 0;
|
||||
return @intCast(@as(u128, rdtsc() -% tsc_base) * 1_000_000 / tsc_hz);
|
||||
}
|
||||
|
||||
pub fn millis() u64 {
|
||||
if (tsc_hz == 0) return 0;
|
||||
return @intCast(@as(u128, rdtsc() -% tsc_base) * 1_000 / tsc_hz);
|
||||
}
|
||||
|
||||
/// Acknowledge the current interrupt so the LAPIC will deliver the next one.
|
||||
|
||||
+34
-4
@@ -78,15 +78,24 @@ pub fn ticks() u64 {
|
||||
return apic.ticks();
|
||||
}
|
||||
|
||||
/// Milliseconds since the timer started (monotonic).
|
||||
pub fn uptimeMs() u64 {
|
||||
return apic.uptimeMs();
|
||||
// Monotonic high-resolution clock (from the TSC), one function per resolution.
|
||||
pub fn nanos() u64 {
|
||||
return apic.nanos();
|
||||
}
|
||||
pub fn micros() u64 {
|
||||
return apic.micros();
|
||||
}
|
||||
pub fn millis() u64 {
|
||||
return apic.millis();
|
||||
}
|
||||
|
||||
/// Measured LAPIC timer frequency in Hz (from calibration).
|
||||
/// Measured LAPIC timer / TSC frequencies in Hz (from calibration).
|
||||
pub fn lapicHz() u64 {
|
||||
return apic.lapicHz();
|
||||
}
|
||||
pub fn tscHz() u64 {
|
||||
return apic.tscHz();
|
||||
}
|
||||
|
||||
/// Unmask maskable interrupts (`sti`) so device interrupts get delivered.
|
||||
pub fn enableInterrupts() void {
|
||||
@@ -98,6 +107,27 @@ pub fn disableInterrupts() void {
|
||||
asm volatile ("cli");
|
||||
}
|
||||
|
||||
/// Disable interrupts and return the previous flags, so a nested critical section
|
||||
/// can restore the caller's state rather than blindly re-enabling. Pairs with
|
||||
/// restoreInterrupts.
|
||||
pub fn saveInterrupts() u64 {
|
||||
var flags: u64 = undefined;
|
||||
asm volatile (
|
||||
\\pushfq
|
||||
\\pop %[f]
|
||||
\\cli
|
||||
: [f] "=r" (flags),
|
||||
:
|
||||
: .{ .memory = true }
|
||||
);
|
||||
return flags;
|
||||
}
|
||||
|
||||
/// Re-enable interrupts only if they were enabled when `flags` was captured.
|
||||
pub fn restoreInterrupts(flags: u64) void {
|
||||
if (flags & 0x200 != 0) asm volatile ("sti" ::: .{ .memory = true }); // bit 9 = IF
|
||||
}
|
||||
|
||||
/// Register a callback the timer interrupt invokes each tick (e.g. the scheduler).
|
||||
pub fn setTickHook(hook: *const fn () void) void {
|
||||
apic.setTickHook(hook);
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
//! Inter-process communication: message-passing channels.
|
||||
//!
|
||||
//! IPC is the backbone of a microkernel ([vision](../docs/vision.md)): once
|
||||
//! drivers and services live in separate address spaces, a message is how they
|
||||
//! talk. This first form is a **bounded blocking channel** — a ring buffer of
|
||||
//! messages with a producer/consumer rendezvous, built on the scheduler's
|
||||
//! [wait queues](scheduling.md). `send` blocks when the channel is full, `recv`
|
||||
//! blocks when it's empty; neither busy-waits.
|
||||
//!
|
||||
//! For now both endpoints are kernel threads sharing the kernel address space.
|
||||
//! When user mode arrives, the same primitive carries messages across the
|
||||
//! isolation boundary (with the payload copied between address spaces).
|
||||
|
||||
const arch = @import("arch");
|
||||
const sched = @import("sched.zig");
|
||||
|
||||
/// A bounded blocking channel of `capacity` messages of type `T`.
|
||||
pub fn Channel(comptime T: type, comptime capacity: usize) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
buffer: [capacity]T = undefined,
|
||||
head: usize = 0, // next slot to read
|
||||
tail: usize = 0, // next slot to write
|
||||
count: usize = 0,
|
||||
not_full: sched.WaitQueue = .{}, // senders wait here
|
||||
not_empty: sched.WaitQueue = .{}, // receivers wait here
|
||||
|
||||
/// Send a message, blocking while the channel is full.
|
||||
pub fn send(self: *Self, msg: T) void {
|
||||
const flags = arch.saveInterrupts();
|
||||
// Recheck the condition in a loop: a wakeup only means "try again"
|
||||
// (another waiter may have taken the slot first).
|
||||
while (self.count == capacity) sched.waitLocked(&self.not_full);
|
||||
self.buffer[self.tail] = msg;
|
||||
self.tail = (self.tail + 1) % capacity;
|
||||
self.count += 1;
|
||||
sched.wakeLocked(&self.not_empty); // a receiver can now proceed
|
||||
arch.restoreInterrupts(flags);
|
||||
}
|
||||
|
||||
/// Receive a message, blocking while the channel is empty.
|
||||
pub fn recv(self: *Self) T {
|
||||
const flags = arch.saveInterrupts();
|
||||
while (self.count == 0) sched.waitLocked(&self.not_empty);
|
||||
const msg = self.buffer[self.head];
|
||||
self.head = (self.head + 1) % capacity;
|
||||
self.count -= 1;
|
||||
sched.wakeLocked(&self.not_full); // a sender can now proceed
|
||||
arch.restoreInterrupts(flags);
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -106,7 +106,7 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
||||
// the timer preempts among tasks.
|
||||
arch.startTimer();
|
||||
arch.enableInterrupts();
|
||||
con.print("danos: timer online ({d} Hz tick, LAPIC {d} MHz measured)\n", .{ arch.timer_hz, arch.lapicHz() / 1_000_000 });
|
||||
con.print("danos: timer online ({d} Hz tick; LAPIC {d} MHz, TSC {d} MHz measured)\n", .{ arch.timer_hz, arch.lapicHz() / 1_000_000, arch.tscHz() / 1_000_000 });
|
||||
|
||||
// In a test build (`zig build -Dtest-case=<name>`), run that case and stop.
|
||||
// Normal builds fall through to the idle halt.
|
||||
|
||||
+106
-6
@@ -21,7 +21,7 @@ const num_priorities = 8;
|
||||
const stack_size = 16 * 1024; // per-task kernel stack
|
||||
const max_tasks = 16;
|
||||
|
||||
const State = enum { free, ready, running };
|
||||
const State = enum { free, ready, running, blocked };
|
||||
|
||||
const Task = struct {
|
||||
id: u32 = 0,
|
||||
@@ -29,6 +29,7 @@ const Task = struct {
|
||||
priority: Priority = 0,
|
||||
rsp: usize = 0, // saved stack pointer, valid while not running
|
||||
stack: []u8 = &.{},
|
||||
wake_at: u64 = 0, // uptime (ms) to wake a sleeping task; 0 = not sleeping
|
||||
next: ?*Task = null, // ready-queue link
|
||||
};
|
||||
|
||||
@@ -43,14 +44,21 @@ var ready_bitmap: u8 = 0;
|
||||
|
||||
var preemption_enabled = true;
|
||||
|
||||
/// Register the currently-running kernel context as the first task, and hook the
|
||||
/// timer for preemption.
|
||||
/// Register the currently-running kernel context as the first task, spawn the
|
||||
/// idle task, and hook the timer for preemption.
|
||||
pub fn init(boot_priority: Priority) void {
|
||||
tasks[0] = .{ .id = 0, .state = .running, .priority = boot_priority };
|
||||
current = &tasks[0];
|
||||
spawn(idle, 0); // lowest priority, always runnable — runs when nothing else is
|
||||
arch.setTickHook(tick);
|
||||
}
|
||||
|
||||
/// The idle task: run when every other task is blocked or sleeping. `hlt` waits
|
||||
/// for the next interrupt at near-zero power (see docs/halting.md).
|
||||
fn idle() void {
|
||||
while (true) asm volatile ("hlt");
|
||||
}
|
||||
|
||||
fn enqueue(t: *Task) void {
|
||||
t.next = null;
|
||||
const p: usize = t.priority;
|
||||
@@ -113,13 +121,105 @@ fn schedule() void {
|
||||
|
||||
/// Voluntarily give up the CPU to the next ready task.
|
||||
pub fn yield() void {
|
||||
arch.disableInterrupts();
|
||||
const flags = arch.saveInterrupts();
|
||||
schedule();
|
||||
arch.enableInterrupts();
|
||||
arch.restoreInterrupts(flags);
|
||||
}
|
||||
|
||||
/// Called from the timer interrupt (interrupts already disabled) to preempt.
|
||||
/// Block the current task for `ms` milliseconds, then let it become runnable
|
||||
/// again. The idle task (or other work) runs in the meantime.
|
||||
pub fn sleep(ms: u64) void {
|
||||
const flags = arch.saveInterrupts();
|
||||
current.wake_at = arch.millis() + ms;
|
||||
current.state = .blocked;
|
||||
schedule(); // current is blocked, so schedule() won't re-enqueue it
|
||||
arch.restoreInterrupts(flags);
|
||||
}
|
||||
|
||||
// --- event-based blocking -------------------------------------------------
|
||||
//
|
||||
// A WaitQueue is a set of tasks blocked waiting for something (a resource, a
|
||||
// message). Tasks link into it through the same `next` field the ready queues
|
||||
// use — a task is in exactly one queue at a time. These are the primitive locks,
|
||||
// semaphores and IPC channels are built on.
|
||||
|
||||
pub const WaitQueue = struct {
|
||||
head: ?*Task = null,
|
||||
};
|
||||
|
||||
/// Block the current task on `wq` and switch away. Precondition: interrupts are
|
||||
/// disabled (the caller holds them, so a condition can be checked and the block
|
||||
/// committed atomically). On return — when woken — interrupts are still disabled.
|
||||
pub fn waitLocked(wq: *WaitQueue) void {
|
||||
current.state = .blocked;
|
||||
current.next = wq.head;
|
||||
wq.head = current;
|
||||
schedule();
|
||||
}
|
||||
|
||||
/// Move the highest-priority waiter on `wq` (if any) to the ready queue.
|
||||
/// Precondition: interrupts disabled. Does not preempt — the caller decides.
|
||||
pub fn wakeLocked(wq: *WaitQueue) void {
|
||||
// Find the highest-priority waiter (bounded scan) and unlink it.
|
||||
var best_prev: ?*Task = null;
|
||||
var best: ?*Task = null;
|
||||
var prev: ?*Task = null;
|
||||
var cur = wq.head;
|
||||
while (cur) |t| : ({
|
||||
prev = t;
|
||||
cur = t.next;
|
||||
}) {
|
||||
if (best == null or t.priority > best.?.priority) {
|
||||
best = t;
|
||||
best_prev = prev;
|
||||
}
|
||||
}
|
||||
const t = best orelse return;
|
||||
if (best_prev) |p| p.next = t.next else wq.head = t.next;
|
||||
t.state = .ready;
|
||||
enqueue(t);
|
||||
}
|
||||
|
||||
/// Block on `wq` (a self-contained critical section).
|
||||
pub fn wait(wq: *WaitQueue) void {
|
||||
const flags = arch.saveInterrupts();
|
||||
waitLocked(wq);
|
||||
arch.restoreInterrupts(flags);
|
||||
}
|
||||
|
||||
/// Wake the highest-priority waiter on `wq`, preempting if it outranks us.
|
||||
pub fn wake(wq: *WaitQueue) void {
|
||||
const flags = arch.saveInterrupts();
|
||||
wakeLocked(wq);
|
||||
// If a higher-priority task is now ready, run it immediately.
|
||||
if (highestReadyPriority()) |p| {
|
||||
if (p > current.priority) schedule();
|
||||
}
|
||||
arch.restoreInterrupts(flags);
|
||||
}
|
||||
|
||||
fn highestReadyPriority() ?Priority {
|
||||
if (ready_bitmap == 0) return null;
|
||||
return @intCast(num_priorities - 1 - @clz(ready_bitmap));
|
||||
}
|
||||
|
||||
/// Wake any sleeping task whose deadline has passed. Bounded by the task count,
|
||||
/// so it stays deterministic. Called from the timer tick (interrupts disabled).
|
||||
fn wakeExpired() void {
|
||||
const now = arch.millis();
|
||||
for (&tasks) |*t| {
|
||||
if (t.state == .blocked and t.wake_at != 0 and now >= t.wake_at) {
|
||||
t.wake_at = 0;
|
||||
t.state = .ready;
|
||||
enqueue(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Called from the timer interrupt (interrupts already disabled): wake due
|
||||
/// sleepers, then preempt.
|
||||
pub fn tick() void {
|
||||
wakeExpired();
|
||||
if (preemption_enabled) schedule();
|
||||
}
|
||||
|
||||
|
||||
+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