feat: add SYS_FUTEX_WAIT and SYS_FUTEX_WAKE syscalls; revamp desktop application categories

This commit is contained in:
daniel committed 2026-10-13 07:11:39 +02:00
1 parent 60ca9c78c7
commit abfd9de8f6
35 files changed
+545 -70

No files matched your search

+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 296
#define MONTAUK_BUILD_NUMBER 302
+8
View File
@@ -302,6 +302,14 @@ namespace montauk::abi {
return Sched::GetCurrentTid();
}
static int Sys_FutexWait(uint64_t addr, uint32_t expected, uint64_t timeoutNs) {
return Sched::FutexWait(addr, expected, timeoutNs);
}
static int Sys_FutexWake(uint64_t addr, uint32_t count) {
return Sched::FutexWake(addr, count);
}
static int Sys_Chdir(const char* path) {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc == nullptr || path == nullptr) return InvalidArgument;
+7
View File
@@ -545,6 +545,13 @@ namespace montauk::abi {
return Sys_ThreadJoin((int)frame->arg1, (int*)frame->arg2);
case SYS_THREAD_SELF:
return Sys_ThreadSelf();
case SYS_FUTEX_WAIT:
// Validation demand-commits the page so the scheduler can
// read the word with its lock held without faulting.
if (!UserMemory::Readable<uint32_t>(frame->arg1)) return BadPointer;
return Sys_FutexWait(frame->arg1, (uint32_t)frame->arg2, frame->arg3);
case SYS_FUTEX_WAKE:
return Sys_FutexWake(frame->arg1, (uint32_t)frame->arg2);
case SYS_BTSCAN:
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
+11
View File
@@ -364,6 +364,17 @@ namespace montauk::abi {
static constexpr uint64_t SYS_GETKEY_EX = 194; // (KeyEvent*, size) -> 0; blocks like SYS_GETKEY
static constexpr uint64_t SYS_CHILDIO_WRITEKEY_EX = 195; // (childPid, const KeyEvent*, size) -> status
/* Futexes: process-private wait queues keyed by a user address. WAIT
blocks only if *addr still equals `expected` (checked atomically with
enrollment). timeoutNs: FUTEX_WAIT_FOREVER = no deadline, 0 = check
only; rounded up to the 1 ms scheduler tick. WAIT returns 0 when woken,
WouldBlock on value mismatch, TimedOut on deadline. WAKE returns the
number of threads woken (count = FUTEX_WAKE_ALL wakes every waiter). */
static constexpr uint64_t SYS_FUTEX_WAIT = 196; // (uint32_t* addr, expected, timeoutNs) -> status
static constexpr uint64_t SYS_FUTEX_WAKE = 197; // (uint32_t* addr, count) -> woken, or status
static constexpr uint64_t FUTEX_WAIT_FOREVER = ~0ULL;
static constexpr uint32_t FUTEX_WAKE_ALL = 0xFFFFFFFFu;
/* Kernel-owned process capabilities. User identities may namespace
per-user resources, but never participate in authorization decisions. */
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
+3 -1
View File
@@ -23,5 +23,7 @@ enum StatusCodes {
NoSuchPath = -11,
Busy = -12,
BufferTooSmall = -13,
OutOfMemory = -14
OutOfMemory = -14,
TimedOut = -15,
WouldBlock = -16
};
+127
View File
@@ -514,6 +514,8 @@ namespace Sched {
proc.joinerSlot = -1;
proc.exitCode = 0;
proc.joinable = false; // main thread is reaped by BSP, not joined
proc.futexAddr = 0;
proc.futexWoken = false;
proc.savedRsp = (uint64_t)sp;
proc.stackBase = (uint64_t)kernelStackBase;
proc.entryPoint = entry;
@@ -824,6 +826,8 @@ namespace Sched {
thr.exitCode = 0;
thr.joinable = true;
thr.threadArg = arg;
thr.futexAddr = 0;
thr.futexWoken = false;
thr.savedRsp = (uint64_t)sp;
thr.stackBase = (uint64_t)kernelStackBase;
thr.entryPoint = entry;
@@ -2116,6 +2120,129 @@ namespace Sched {
}
}
// Monotonic enrollment counter for FIFO futex wakeups. Only touched
// under schedLock.
static uint64_t futexNextSeq = 1;
int FutexWait(uint64_t uaddr, uint32_t expected, uint64_t timeoutNs) {
if ((uaddr & 3) != 0) return InvalidArgument;
ExitIfKilled();
auto* cpu = Smp::GetCurrentCpuData();
int slot = cpu ? cpu->currentSlot : -1;
if (slot < 0) return GenericFailure;
Process& self = processTable[slot];
// Round up so a short timeout never turns into "check only".
uint64_t timeoutMs = 0;
if (timeoutNs != FutexWaitForever) {
if (timeoutNs == 0) {
timeoutMs = 0;
} else {
timeoutMs = timeoutNs / 1000000ULL + ((timeoutNs % 1000000ULL) ? 1 : 0);
}
}
schedLock.Acquire();
// The word is read here, with schedLock held and interrupts off, so
// it must not fault: the caller validated (and demand-committed) the
// page, and the page walk below re-confirms it is still mapped. An
// aligned 32-bit word never straddles a page.
if (!Memory::VMM::Paging::IsUserRangeAccessible(self.pml4Phys, uaddr, 4, false)) {
schedLock.Release();
return BadPointer;
}
uint32_t current = __atomic_load_n((volatile uint32_t*)uaddr, __ATOMIC_SEQ_CST);
if (current != expected) {
schedLock.Release();
return WouldBlock;
}
if (timeoutNs == 0) {
schedLock.Release();
return TimedOut;
}
self.state = ProcessState::Blocked;
self.waitingForPid = -1;
self.waitingOnObject = nullptr;
self.futexAddr = uaddr;
self.futexSeq = futexNextSeq++;
self.futexWoken = false;
self.sleepUntilTick = (timeoutNs == FutexWaitForever)
? 0
: Timekeeping::GetTicks() + timeoutMs;
self.runningOnCpu = -1;
SwitchAwayFromBlockedCurrentLocked();
// Whatever made this thread runnable (FutexWake, the deadline, or a
// kill) it is no longer parked; clear the key before this thread can
// block for some other reason and be mistaken for a futex waiter.
schedLock.Acquire();
bool woken = self.futexWoken;
self.futexAddr = 0;
self.futexWoken = false;
schedLock.Release();
ExitIfKilled();
return woken ? Success : TimedOut;
}
int FutexWake(uint64_t uaddr, uint32_t count) {
if ((uaddr & 3) != 0 || uaddr == 0) return InvalidArgument;
if (count == 0) return 0;
auto* cpu = Smp::GetCurrentCpuData();
int slot = cpu ? cpu->currentSlot : -1;
if (slot < 0) return GenericFailure;
schedLock.Acquire();
int primary = processTable[slot].primarySlot;
if (primary < 0) primary = slot;
// Gather this process's waiters on uaddr. At most MaxProcesses.
int waiters[MaxProcesses];
int n = 0;
for (int i = 0; i < MaxProcesses; i++) {
Process& p = processTable[i];
if (p.state != ProcessState::Blocked) continue;
if (p.futexAddr != uaddr) continue;
if (p.primarySlot != primary) continue;
waiters[n++] = i;
}
// Waking everyone needs no ordering; otherwise pick oldest-first.
bool ordered = (uint32_t)n > count;
int woken = 0;
while (woken < n && (uint32_t)woken < count) {
if (ordered) {
int best = woken;
for (int j = woken + 1; j < n; j++) {
if (processTable[waiters[j]].futexSeq < processTable[waiters[best]].futexSeq)
best = j;
}
int tmp = waiters[woken]; waiters[woken] = waiters[best]; waiters[best] = tmp;
}
Process& p = processTable[waiters[woken]];
p.futexAddr = 0;
p.futexWoken = true;
p.sleepUntilTick = 0;
p.state = ProcessState::Ready;
readyCount++;
woken++;
}
schedLock.Release();
if (woken > 0) {
// Re-read: this thread may have migrated since `cpu` was sampled.
auto* here = Smp::GetCurrentCpuData();
KickOneIdleCpu(here ? here->cpuIndex : -1);
}
return woken;
}
bool IsAlive(int pid) {
for (int i = 0; i < MaxProcesses; i++) {
if (processTable[i].pid == pid) {
+31
View File
@@ -126,6 +126,13 @@ namespace Sched {
bool joinable = true; // Sibling thread keeps Terminated state for the joiner
uint64_t threadArg = 0; // Argument passed into entryPoint on first dispatch
// Futex wait state (see FutexWait). futexAddr is the user VA this
// thread is parked on, 0 when not in a futex wait. futexSeq orders
// waiters so FutexWake releases the longest-waiting first.
uint64_t futexAddr = 0;
uint64_t futexSeq = 0;
bool futexWoken = false;
// I/O redirection for GUI terminal
bool redirected = false;
int parentPid = -1;
@@ -270,6 +277,30 @@ namespace Sched {
// Wake any processes blocked on the given object.
void WakeObjectWaiters(void* object);
// ====================================================================
// Futexes
// ====================================================================
// Futexes are private to a process: the key is (primary slot, user VA),
// so two processes waiting on the same address never see each other.
static constexpr uint64_t FutexWaitForever = ~0ULL;
// Block the current thread on the 32-bit word at `uaddr` if it still
// holds `expected`. The comparison and the enrollment happen atomically
// under the scheduler lock, so a FutexWake issued after the waker changed
// the word can never be lost. `timeoutNs` is rounded up to the 1 ms tick;
// FutexWaitForever waits without a deadline, 0 only performs the check.
// Returns Success when woken by FutexWake, WouldBlock when the word did
// not hold `expected`, TimedOut when the deadline passed, InvalidArgument
// for a misaligned address, BadPointer when the word is not mapped.
// Spurious Success returns are not possible, but callers must still
// re-check their condition: another thread may have run in between.
int FutexWait(uint64_t uaddr, uint32_t expected, uint64_t timeoutNs);
// Wake up to `count` threads of the current process blocked on `uaddr`,
// longest-waiting first. Returns the number woken.
int FutexWake(uint64_t uaddr, uint32_t count);
// Kill a process by PID. If the process is running on another CPU,
// sets a kill-pending flag checked on the next timer tick.
// Returns 0 on success, -1 on failure.