From 68fa3aca177ab166e64d979c42cbbe904d6d7e1c Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 18:44:16 +0200 Subject: [PATCH] feat: grow syscall --- src/syscall.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/src/syscall.c b/src/syscall.c index d5db441..1f6b3de 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -790,6 +790,180 @@ uint64 syscallDispatch(Registers* regs) pitTicks() >= self->pollDeadline; if (ready > 0 || timeoutMs == 0 || expired) { self->pollActive = false; + self->pollDeadline = 0; + regs->rax = (uint64)ready; + return (uint64)regs; + } + + /* None ready yet, timeout not yet reached: park on the one shared + channel every poller blocks on (schedPollChannel, sched.h) and + rewind rip so the whole syscall re-executes -- the same + restart-the-syscall trick sysRead/sysWrite/sysWait already use, + except schedule()'s own expiry scan (sched.c) is what guarantees + this retry eventually happens even if no fd ever becomes ready. */ + self->sigInRetryableSyscall = true; + schedBlockCurrent(schedPollChannel()); + regs->rip -= 2; + return schedule((uint64)regs); + } + case sysMkfifo: { + char path[256]; + if (!copyUserPath(regs->rdi, path, sizeof(path))) + regs->rax = (uint64)-1; + else + regs->rax = (uint64)(int64)vfsMkfifo(path); + return (uint64)regs; + } + case sysExec: { + char path[256]; + char argvStorage[elfMaxArgs][execArgMaxLen]; + const char* argvPtrs[elfMaxArgs]; + int argc; + if (!copyUserPath(regs->rdi, path, sizeof(path)) || + !copyUserArgv(regs->rsi, argvStorage, argvPtrs, &argc) || + !doExec(path, argvPtrs, argc, regs)) + regs->rax = (uint64)-1; + return (uint64)regs; + } + case sysSpawn: { + char path[256]; + char argvStorage[elfMaxArgs][execArgMaxLen]; + const char* argvPtrs[elfMaxArgs]; + int argc; + if (!copyUserPath(regs->rdi, path, sizeof(path)) || + !copyUserArgv(regs->rsi, argvStorage, argvPtrs, &argc)) + regs->rax = (uint64)-1; + else + regs->rax = (uint64)doSpawn(path, argvPtrs, argc); + return (uint64)regs; + } + case sysWait: { + uint32 id = (uint32)regs->rdi; + if (id == waitAnyPid) { + /* waitpid(-1)-style "wait for ANY child" -- see + threadJoinAnyChild's own doc comment (sched.h) for the full + contract. regs->rsi is an optional user `long*` pointer + (userWaitAny's outPid, ulib.c) that receives which pid + actually got reaped; nullptr means the caller does not care. + Written as a full 8-byte store below to match `long`'s real + width, not truncated to sizeof(uint32) -- a narrower store + would leave the user variable's upper 4 bytes as whatever + uninitialized garbage the caller's stack already had. */ + if (regs->rsi != 0 && !userRangeOk(regs->rsi, sizeof(uint64))) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + uint32 reapedId; + int64 code; + WaitScanResult scan = threadJoinAnyChild(currentThreadId(), &reapedId, &code); + if (scan == waitScanNoChildren) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + if (scan == waitScanNoneDead) { + /* Same retry-syscall dance as the specific-pid path below, + just parked on our OWN slot instead of a named child's -- + threadExit/threadKill wake this channel for EVERY dying + child of ours, not just one in particular (see + threadJoinAnyChild's doc comment), so re-scanning from + scratch on each retry is what actually picks up whichever + one died. */ + currentThread()->sigInRetryableSyscall = true; + schedBlockCurrent(currentThread()); + regs->rip -= 2; + return schedule((uint64)regs); + } + if (regs->rsi != 0) + *(uint64*)regs->rsi = (uint64)reapedId; + regs->rax = (uint64)code; + return (uint64)regs; + } + if (id == 0 || id >= threadCapacity()) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + Thread* target = threadAt(id); + if (target->state == threadUnused) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + if (target->state != threadDead) { + /* Not dead yet: parking here and calling halt() the way + threadJoin (sched.c) does for a plain kernel thread body + would hang the whole machine, not just this thread -- this + handler runs with interrupts disabled (int 0x80 is an + interrupt gate, idt.c) on the single shared bootstrap kernel + stack (isrStubs.S/tss.c), not the calling thread's own + persistent one, so unlike kmain.c's joinerThread it cannot + literally halt in place and pick its own C call frame back + up later: hlt with IF=0 never wakes, since nothing can ever + deliver the timer interrupt that would run schedule() again. + Instead: block on the same channel threadExit wakes + (schedWakeChannel(&threads[id])), rewind rip by the 2 bytes + of the `int $0x80` opcode this trap just came from so it + re-executes once this thread is scheduled again, and hand + the CPU to schedule() -- the same "flip state, then let the + interrupt-return path do the actual waiting" technique + sysSleep already uses. The retry re-enters this exact case + with the same rax/rdi; since threadExit will have already + flipped the target to Dead before waking this thread back + up, it takes the branch below next time and completes + without blocking again. */ + currentThread()->sigInRetryableSyscall = true; + schedBlockCurrent(target); + regs->rip -= 2; + return schedule((uint64)regs); + } + regs->rax = (uint64)threadJoin(id); + return (uint64)regs; + } + case sysMmap: { + /* addrHint==0 means "kernel picks" (mmapReserve's own first-fit, + real mmap(NULL, ...) shape); a nonzero addrHint is taken as an + exact placement request, refused (not silently relocated) if it + does not fit -- there is no MAP_FIXED bit to distinguish the two, + every addrHint here behaves like real mmap's own weaker "hint" + contract already narrowed down to "exact or refused". fd/offset + are read (and validated below) even when MAP_ANONYMOUS is set, + harmlessly ignored by mmapReserve itself in that case, exactly + like real mmap(2)'s own documented behavior. */ + uint64 addrHint = regs->rdi; + uint64 length = regs->rsi; + uint32 prot = (uint32)regs->rdx; + uint32 flags = (uint32)regs->r10; + int fd = (int)regs->r8; + uint64 offset = regs->r9; + bool anonymous = (flags & mmapAnonymous) != 0; + + if (!anonymous) { + /* Same three seekable, real-data fd kinds vfsLseek itself + already restricts to (vfs.c) -- a pipe/fifo/console/pidfd fd + has no fixed byte range a page fault could ever re-read + deterministically, so mmap refuses them here rather than + letting mmapFaultHandler discover that failure lazily, one + page at a time, later. */ + if (fd < 0 || fd >= maxOpenFiles) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + FileDescriptor* fdEntry = ¤tThread()->fds[fd]; + if (fdEntry->kind != fdFile && fdEntry->kind != fdSfs && fdEntry->kind != fdBlockDev) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + } + + uint64 mappedAddr; + if (!mmapReserve(currentThread(), addrHint, length, prot, anonymous, fd, offset, &mappedAddr)) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + regs->rax = mappedAddr; + return (uint64)regs; + } + case sysMunmap: + regs->rax = mmapRelease(currentThread(), regs->rdi, regs->rsi) ? 0 : (uint64)-1; + return (uint64)regs; default: regs->rax = (uint64)-1; return (uint64)regs;