feat: grow syscall
This commit is contained in:
1 parent
8f295c1d20
commit
68fa3aca17
1 file changed
+174
+174
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user