feat: add sigRaise and sigRaiseGroup to signal

This commit is contained in:
dualfroz committed 2026-09-08 14:41:59 +02:00
1 parent 26476fc78b
commit 8f2554114f
1 file changed
+161
+161
View File
@@ -154,3 +154,164 @@ bool sigRaiseInfo(uint32 targetId, int signum, int siCode, int siPid, int siStat
target->sigPendingInfo[signum].siPid = siPid; target->sigPendingInfo[signum].siPid = siPid;
target->sigPendingInfo[signum].siStatus = siStatus; target->sigPendingInfo[signum].siStatus = siStatus;
/* Real Unix's interruptible sleep/EINTR (deliberately out of scope
until now, see the file comment's history): a Sleeping/Blocked
target whose sleep timer or wait-channel condition would otherwise
never naturally fire again (e.g. user/eintrtarget.c's read that no
writer will ever satisfy) must still actually receive a signal that
would genuinely be delivered, not sit on it forever. Skipped
entirely for a blocked or ignored signal -- see sigWouldDeliver --
so a masked/ignored signal never disturbs a thread's sleep/wait at
all, matching real Unix. threadWakeForSignal itself refuses anything
other than Sleeping/Blocked (Stopped is excluded on purpose -- only
threadContinue may move it), so this is safe to call unconditionally
whenever sigWouldDeliver agrees. */
if (sigWouldDeliver(target, signum))
threadWakeForSignal(targetId);
return true;
}
bool sigRaise(uint32 targetId, int signum)
{
return sigRaiseInfo(targetId, signum, siCodeKernel, 0, 0);
}
bool sigRaiseGroup(uint32 pgid, int signum, int siCode, int siPid)
{
bool anyTarget = false;
for (uint32 id = 1; id < threadCapacity(); id++) {
Thread* candidate = threadAt(id);
if (candidate->state == threadDead || candidate->state == threadUnused)
continue;
if (candidate->pgid != pgid)
continue;
anyTarget = true;
sigRaiseInfo(id, signum, siCode, siPid, 0);
}
return anyTarget;
}
uint64 sigSetHandler(Thread* thread, int signum, uint64 handlerAddr, uint64 mask, uint64 flags)
{
if (signum <= 0 || signum >= sigMaxSignals)
return sigSetFailed;
if (signum == sigKill || signum == sigStop)
return sigSetFailed;
uint64 previous = thread->sigHandlers[signum];
thread->sigHandlers[signum] = handlerAddr;
/* Same stripping sigProcMask already does to a mask it is handed: a
mask cannot make sigKill/sigStop blockable just because it asked. */
mask &= ~(1ull << sigKill);
mask &= ~(1ull << sigStop);
thread->sigHandlerMasks[signum] = mask;
thread->sigHandlerFlags[signum] = flags;
return previous;
}
uint64 sigProcMask(Thread* thread, int how, uint64 mask)
{
uint64 previous = thread->sigBlocked;
mask &= ~(1ull << sigKill);
mask &= ~(1ull << sigStop);
switch (how) {
case sigMaskBlock:
thread->sigBlocked |= mask;
break;
case sigMaskUnblock:
thread->sigBlocked &= ~mask;
break;
case sigMaskSetMask:
thread->sigBlocked = mask;
break;
default:
break;
}
return previous;
}
SigOutcome sigConsumePending(Thread* thread, Registers* regs, int* outSignum)
{
if (thread->sigHandlerDepth >= sigMaxNestDepth)
return sigOutcomeNone;
for (int signum = 1; signum < sigMaxSignals; signum++) {
uint64 bit = 1ull << (uint32)signum;
if (!(thread->sigPending & bit) || (thread->sigBlocked & bit))
continue;
thread->sigPending &= ~bit;
uint64 handler = thread->sigHandlers[signum];
if (handler == sigHandlerIgnore ||
(handler == sigHandlerDefault && sigDefaultIsIgnore(signum)))
continue;
if (outSignum)
*outSignum = signum;
if (handler == sigHandlerDefault) {
if (sigDefaultIsStop(signum))
return sigOutcomeStop;
return sigOutcomeTerminate;
}
/* SA_RESTART / EINTR (see file comment): consumed here, exactly
once, regardless of which way the decision goes -- a signal
nested INSIDE the handler this delivery is about to enter is
interrupting that handler's own code, not a retryable syscall
retry point, so it must never see this as still true. Only
rewrites `regs` (the context about to be saved below) when this
thread was genuinely mid one of sysRead/sysWrite/sysWait's own
retry-on-block pattern AND this particular signal was not
registered with sigFlagRestart -- anything else (plain running
code, a sleep, an already-nested handler, or sigFlagRestart
itself) leaves `regs` completely untouched, preserving the
unconditional-retry behavior this kernel had before the flag
existed. */
bool interruptedRetry = thread->sigInRetryableSyscall;
thread->sigInRetryableSyscall = false;
if (interruptedRetry && !(thread->sigHandlerFlags[signum] & sigFlagRestart)) {
regs->rip += 2;
regs->rax = (uint64)(int64)errInterrupted;
}
/* Push the whole interrupted trapframe (now possibly rewritten
above) onto this thread's small context stack (the only copy of
it that exists anywhere -- unlike a real kernel this never pushes
one onto the user stack) so sigOnSigReturn can restore it byte
for byte, even if a DIFFERENT, unmasked signal nests another
handler on top before this one ever returns. */
uint32 depth = thread->sigHandlerDepth;
thread->sigSavedRegs[depth] = *regs;
thread->sigSavedBlocked[depth] = thread->sigBlocked;
thread->sigHandlerDepth = depth + 1;
/* sa_mask (sigHandlerMasks[signum], set by sigSetHandler) plus the
signal's own bit (self-blocked, like real Unix's implicit
default) are ADDED to whatever was already blocked -- a union,
not a replacement, so an outer handler's own blocked set stays
blocked for this nested one too. sigFlagNoDefer (signalnum.h)
opts out of exactly the self-block half of that union: a signal
registered with it does not gain its own bit here, so a second
raise of the SAME signum while this handler is still active
finds sigBlocked genuinely clear for it and nests, instead of
merely staying pending until this handler returns. Stripped of
sigKill/sigStop for the same reason every other mask operation in
this file is: neither ever consults sigBlocked at all, so this is
purely cosmetic, but leaving either bit set here would visibly
contradict "can never be blocked" for anything that inspects
sigBlocked. */
uint64 newBlocked = thread->sigBlocked | thread->sigHandlerMasks[signum];
if (!(thread->sigHandlerFlags[signum] & sigFlagNoDefer))
newBlocked |= bit;
newBlocked &= ~(1ull << sigKill);
newBlocked &= ~(1ull << sigStop);
thread->sigBlocked = newBlocked;