threads(M4): futex_wait/futex_wake, the blocking primitive
New private syscalls futex_wait(addr, expected, timeout_ns)=40 and futex_wake(addr, count)=41. A waiter is a .blocked task tagged with Task.futex_addr (no queue linkage); futex_wait reads the user word under the big lock and parks only if it still equals expected, so a concurrent wake can't slip between the check and the block. futex_wake scans the task table and readies up to count waiters in the same address space. A timed wait also sets wake_at so the existing wakeExpired times it out; futex_addr staying non-zero (only futex_wake clears it) distinguishes timeout from a real wake. Waiters park in-kernel, so an idle core still halts (no busy-wait). runtime.Thread.Futex mirrors std.Thread.Futex (wait/timedWait/wake). thread-test gains a futex mode: a waiter parks, the main thread wakes it (serial order waiting/waking/woke, asserted by the case regex), and timedWait reports a timeout. Gate thread-futex PASS (3x); 18 guardrail cases green incl. sleep/event/ipc blocking paths; build + host tests clean.
This commit is contained in:
@@ -229,6 +229,8 @@ fn system_call(state: *architecture.CpuState) void {
|
||||
.shm_physical => systemShmPhysical(state),
|
||||
.thread_spawn => systemThreadSpawn(state),
|
||||
.current_core => systemCurrentCore(state),
|
||||
.futex_wait => systemFutexWait(state),
|
||||
.futex_wake => systemFutexWake(state),
|
||||
.thread_exit => {
|
||||
// A thread ends like a process exit(0), but only this task: its
|
||||
// resources are released and its address-space reference dropped (the
|
||||
@@ -692,6 +694,53 @@ fn systemCurrentCore(state: *architecture.CpuState) void {
|
||||
architecture.setSystemCallResult(state, scheduler.currentCpuIndex());
|
||||
}
|
||||
|
||||
/// futex_wait(addr, expected, timeout_ns) -> status (docs/threading.md): if the 4-byte
|
||||
/// user word at `addr` still equals `expected`, block until a futex_wake on `addr` or
|
||||
/// (if timeout_ns > 0) the deadline. The compare and the block are one critical section,
|
||||
/// so a concurrent futex_wake cannot slip between them. Returns futex_woken / mismatch /
|
||||
/// timed_out.
|
||||
fn systemFutexWait(state: *architecture.CpuState) void {
|
||||
const addr = architecture.systemCallArg(state, 0);
|
||||
const expected: u32 = @truncate(architecture.systemCallArg(state, 1));
|
||||
const timeout_ns = architecture.systemCallArg(state, 2);
|
||||
const t = scheduler.current();
|
||||
if (t.aspace == 0) return fail(state);
|
||||
if (addr == 0 or (addr & 3) != 0 or addr + 4 > user_half_end) return fail(state);
|
||||
|
||||
const flags = sync.enter();
|
||||
var word_bytes: [4]u8 = undefined;
|
||||
if (!ipc.copyFromUser(t.aspace, addr, &word_bytes)) {
|
||||
sync.leave(flags);
|
||||
return fail(state);
|
||||
}
|
||||
if (std.mem.readInt(u32, &word_bytes, .little) != expected) {
|
||||
sync.leave(flags);
|
||||
architecture.setSystemCallResult(state, abi.futex_mismatch);
|
||||
return;
|
||||
}
|
||||
const timeout_ms = if (timeout_ns == 0) 0 else (timeout_ns + 999_999) / 1_000_000;
|
||||
const result = scheduler.futexWaitLocked(addr, timeout_ms);
|
||||
sync.leave(flags);
|
||||
architecture.setSystemCallResult(state, switch (result) {
|
||||
.woken => abi.futex_woken,
|
||||
.timed_out => abi.futex_timed_out,
|
||||
});
|
||||
}
|
||||
|
||||
/// futex_wake(addr, count) -> woken: wake up to `count` tasks blocked in futex_wait on
|
||||
/// `addr` in the caller's address space.
|
||||
fn systemFutexWake(state: *architecture.CpuState) void {
|
||||
const addr = architecture.systemCallArg(state, 0);
|
||||
const count: u32 = @truncate(architecture.systemCallArg(state, 1));
|
||||
const t = scheduler.current();
|
||||
if (t.aspace == 0) return fail(state);
|
||||
if (addr == 0 or (addr & 3) != 0 or addr + 4 > user_half_end) return fail(state);
|
||||
const flags = sync.enter();
|
||||
const woken = scheduler.futexWakeLocked(t.aspace, addr, count);
|
||||
sync.leave(flags);
|
||||
architecture.setSystemCallResult(state, woken);
|
||||
}
|
||||
|
||||
/// process_enumerate(buffer, maximum) -> total: snapshot the task table into the
|
||||
/// caller's buffer (up to `maximum` `abi.ProcessDescriptor` entries), returning
|
||||
/// the total live-task count — the exact shape of `device_enumerate`, so a `ps`
|
||||
|
||||
Reference in New Issue
Block a user