feat: add sigRaise and sigRaiseGroup to signal
This commit is contained in:
1 parent
26476fc78b
commit
8f2554114f
1 file changed
+161
+161
@@ -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;
|
||||||
|
|
||||||
Reference in new issue
Block a user