feat: build out syscall

This commit is contained in:
dualfroz committed 2026-09-08 17:40:09 +02:00
1 parent aad9d4ece8
commit 8f295c1d20
1 file changed
+173
+173
View File
@@ -617,6 +617,179 @@ uint64 syscallDispatch(Registers* regs)
int64 rawPid = (int64)regs->rdi;
bool ok;
if (rawPid > 0)
ok = threadKill((uint32)rawPid);
else
ok = threadKillGroup(rawPid == 0 ? currentThread()->pgid : (uint32)(-rawPid));
regs->rax = ok ? 0 : (uint64)-1;
return (uint64)regs;
}
case sysSigSend: {
/* Same pid>0/pid==0/pid<0 convention as sysKill just above, this
time through sigRaiseGroup (signal.h) for the two group cases --
see that function's own doc comment for why every per-target
special case (SIGKILL/SIGSTOP/SIGCONT bypasses, early-wake,
SigInfo) still applies exactly as it already does for a plain,
single-target send. */
int64 rawPid = (int64)regs->rdi;
int signum = (int)regs->rsi;
uint32 senderPid = currentThreadId();
bool isGroup = (rawPid <= 0);
uint32 targetPgid = isGroup ? (rawPid == 0 ? currentThread()->pgid : (uint32)(-rawPid)) : 0;
/* siCodeUser/senderPid: a real userSigSend always has a genuine
sending process, unlike sched.c's own kernel-internal raises
(SIGALRM/SIGCHLD) or keyboard.c's terminal-generated ones --
exactly real Unix's own SI_USER, si_pid the caller's own pid,
exactly like real kill(2). */
bool ok = isGroup
? sigRaiseGroup(targetPgid, signum, siCodeUser, (int)senderPid)
: sigRaiseInfo((uint32)rawPid, signum, siCodeUser, (int)senderPid, 0);
regs->rax = ok ? 0 : (uint64)-1;
/* Every OTHER outcome of sigRaise/sigRaiseGroup either changes
nothing about the CALLING thread's own state (a signal aimed at
someone else) or, for an ordinary catchable signal aimed at
self, is already caught by isrDispatch's unconditional
deliverPendingSignals call right after this handler returns
(see isr.c) -- self-signaling sigUsr1 and having its handler
fire before ever reaching userspace again already works today
without any special case here. SIGSTOP against the caller's own
pid (or a group that includes it) is the one exception:
threadStop (called from sigRaise above, once per group member
for the group case) already flipped this very thread's state
away from Running, but sigRaise/sysSigSend never touch
sigPending for it (see signal.h), so deliverPendingSignals'
pending-bit scan would find nothing and simply hand `regs`
straight back -- returning to ring 3 and continuing to run a
thread the scheduler no longer considers Runnable, until the
next timer tick eventually notices. Explicit reschedule here
closes that gap, the same "flip state, then let schedule() do
the actual waiting" pattern sysSleep/sysExit already use for
their own self-directed state changes. The group case checks
targetPgid against the caller's OWN pgid rather than an exact id
match, since the caller itself -- not just some other member --
is who this reschedule is actually about. */
bool selfTargeted = isGroup ? (targetPgid == currentThread()->pgid) : ((uint32)rawPid == senderPid);
if (ok && signum == sigStop && selfTargeted)
return schedule((uint64)regs);
return (uint64)regs;
}
case sysSigAction:
regs->rax = sigSetHandler(currentThread(), (int)regs->rdi, regs->rsi, regs->rdx, regs->r10);
return (uint64)regs;
case sysSigProcMask:
regs->rax = sigProcMask(currentThread(), (int)regs->rdi, regs->rsi);
return (uint64)regs;
case sysSigReturn:
sigOnSigReturn(currentThread(), regs);
return (uint64)regs;
case sysAlarm:
regs->rax = threadSetAlarm(regs->rdi);
return (uint64)regs;
case sysSetpgid: {
/* Real setpgid(2)'s own "0 means self"/"0 means use pid itself as
the new group id" resolution -- threadSetPgid (sched.h) takes an
already-fully-resolved value, exactly like threadSetBrk/
threadSetAlarm above do for THEIR raw syscall arguments. */
uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi;
uint32 pgid = (regs->rsi == 0) ? pid : (uint32)regs->rsi;
regs->rax = threadSetPgid(pid, pgid) ? 0 : (uint64)-1;
return (uint64)regs;
}
case sysGetpgid: {
uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi;
uint32 pgid = threadPgid(pid);
regs->rax = (pgid == threadInvalidId) ? (uint64)-1 : (uint64)pgid;
return (uint64)regs;
}
case sysNice: {
/* Real setpriority(PRIO_PROCESS, ...)'s own "0 means the caller
itself" convention, same resolution shape as sysSetpgid's `pid`
argument just above -- rdi/pid, rsi/weight, straight through to
threadSetPriority (sched.h), which now does the SAME id==0/
out-of-range/Dead-or-Unused rejection threadSetPgid already did
(see that function's own updated doc comment for why it had to
catch up once this syscall made it reachable from ring 3). */
uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi;
uint32 weight = (uint32)regs->rsi;
regs->rax = threadSetPriority(pid, weight) ? 0 : (uint64)-1;
return (uint64)regs;
}
case sysGetPriority: {
uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi;
uint32 weight = threadPriority(pid);
regs->rax = (weight == 0) ? (uint64)-1 : (uint64)weight;
return (uint64)regs;
}
case sysTime:
/* Real time(2)'s job: seconds since the Unix epoch, off the real
CMOS RTC (rtc.c) -- the first syscall in this kernel answering
"what day is it" instead of "how long since boot" (sysSleep,
userAlarm). No arguments, nothing to validate. */
regs->rax = (uint64)rtcNow();
return (uint64)regs;
case sysSetFgPgrp:
/* Real tcsetpgrp(3)'s job -- names which process group keyboard.c's
real Ctrl-C/Ctrl-Z (SIGINT/SIGTSTP) is scoped to next. No
permission check at all (this kernel has no notion of "the
controlling terminal of MY session" to check ownership against,
the same honest, documented omission threadSetPgid's own doc
comment already gives for setpgid's session check) -- any
process can claim the foreground for any pgid, including one it
is not even a member of. */
keyboardSetForegroundPgid((uint32)regs->rdi);
regs->rax = 0;
return (uint64)regs;
case sysGetFgPgrp:
regs->rax = (uint64)keyboardForegroundPgid();
return (uint64)regs;
case sysPoll: {
/* Real poll(2): rdi/PollFd array, rsi/nfds, rdx/timeoutMs (signed,
-1 = wait forever, 0 = report current readiness without ever
blocking, like every real poll(2) implementation). Flat address
space (this kernel's honest fork()-less design, see doSpawn's own
comment) means the array can be read and written in place, the
same way sysStat writes straight into its caller's FbStat*
without a separate copy-out step. */
uint64 nfds = regs->rsi;
int64 timeoutMs = (int64)regs->rdx;
if (nfds > maxPollFds || !userRangeOk(regs->rdi, nfds * sizeof(PollFd))) {
regs->rax = (uint64)-1;
return (uint64)regs;
}
PollFd* userFds = (PollFd*)regs->rdi;
Thread* self = currentThread();
/* First entry into this call (not a retry re-executing after a
park below) picks the deadline once and remembers it in
pollDeadline -- a later retry must NOT recompute it from the
still-unchanged rdx, or a poll that keeps almost-but-not-quite
finding a ready fd could have its effective timeout pushed out
indefinitely instead of counting down real elapsed time. */
if (!self->pollActive && timeoutMs != 0) {
self->pollActive = true;
if (timeoutMs < 0) {
self->pollDeadline = 0;
} else {
uint64 ticks = ((uint64)timeoutMs * (uint64)pitFrequency()) / 1000;
if (ticks == 0)
ticks = 1;
self->pollDeadline = pitTicks() + ticks;
}
}
int64 ready = 0;
for (uint64 i = 0; i < nfds; i++) {
int16 revents = 0;
if (vfsPollReady(userFds[i].fd, userFds[i].events, &revents))
ready++;
userFds[i].revents = revents;
}
bool expired = self->pollActive && self->pollDeadline != 0 &&
pitTicks() >= self->pollDeadline;
if (ready > 0 || timeoutMs == 0 || expired) {
self->pollActive = false;
default:
regs->rax = (uint64)-1;
return (uint64)regs;