feat: grow syscall

This commit is contained in:
dualfroz committed 2026-09-08 18:44:16 +02:00
1 parent 8f295c1d20
commit 68fa3aca17
1 file changed
+174
+174
View File
@@ -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 = &currentThread()->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;