feat: add sigResetState and sigDefaultIsIgnore
This commit is contained in:
1 parent
c885f33d76
commit
26476fc78b
1 file changed
+156
+156
@@ -0,0 +1,156 @@
|
||||
#include "signal.h"
|
||||
#include "string.h"
|
||||
#include "pmm.h"
|
||||
#include "paging.h"
|
||||
|
||||
/* Machine code for the shared trampoline page, sigTrampoline.S -- position
|
||||
independent by construction (no absolute address references at all), so
|
||||
copying its bytes anywhere and mapping them there is enough. */
|
||||
extern char sigTrampolineStart[];
|
||||
extern char sigTrampolineEnd[];
|
||||
|
||||
/* Physical frame addresses backing sigInfoVirtualAddr's page(s), filled in
|
||||
by sigInstallInfoPage -- kept as separate frames (not assumed physically
|
||||
contiguous, unlike their virtual mapping, which IS contiguous by
|
||||
construction: pmmAlloc gives no contiguity guarantee across calls, so
|
||||
sigConsumePending must look up the right frame for a given slot rather
|
||||
than pointer-arithmetic across a single base). Physical frames below the
|
||||
1 GiB boot-time identity map (see sigTrampolineVirtualAddr's own comment)
|
||||
are directly dereferenceable through their own physical value from
|
||||
kernel code, exactly like sigInstallTrampoline's memcpy into `frame`
|
||||
above already relies on. */
|
||||
static uint64 sigInfoFrames[sigInfoMaxPages];
|
||||
static uint32 sigInfoPageCount = 0;
|
||||
|
||||
/* How many SigInfo structs fit in one frameSize page -- shared by
|
||||
sigInstallInfoPage's sizing loop and sigConsumePending's slot lookup, so
|
||||
the two can never disagree about the layout. */
|
||||
#define sigInfoSlotsPerPage (frameSize / sizeof(SigInfo))
|
||||
|
||||
void sigResetState(Thread* thread)
|
||||
{
|
||||
thread->sigPending = 0;
|
||||
thread->sigBlocked = 0;
|
||||
thread->sigHandlerDepth = 0;
|
||||
thread->sigInRetryableSyscall = false;
|
||||
memset(thread->sigHandlers, 0, sizeof(thread->sigHandlers));
|
||||
memset(thread->sigHandlerMasks, 0, sizeof(thread->sigHandlerMasks));
|
||||
memset(thread->sigHandlerFlags, 0, sizeof(thread->sigHandlerFlags));
|
||||
memset(thread->sigPendingInfo, 0, sizeof(thread->sigPendingInfo));
|
||||
memset(thread->sigSavedBlocked, 0, sizeof(thread->sigSavedBlocked));
|
||||
memset(thread->sigSavedRegs, 0, sizeof(thread->sigSavedRegs));
|
||||
}
|
||||
|
||||
/* Real-world default disposition for signals this kernel actually defines
|
||||
that is Ignore rather than Terminate -- SIGCHLD (no handler installed:
|
||||
nothing happens) and SIGCONT (its actual resume effect already happened
|
||||
unconditionally in sigRaise -- see file comment -- so "no handler" here
|
||||
just means no handler runs, not that nothing happened at all). SIGSTOP
|
||||
never reaches this function to begin with (sigRaise bypasses the whole
|
||||
scan for it, like sigKill), so it is deliberately absent from this list,
|
||||
the same way sigKill already was. SIGTSTP is NOT here -- its default is
|
||||
Stop, not Ignore, see sigDefaultIsStop below. Every other defined signal
|
||||
defaults to Terminate. */
|
||||
static bool sigDefaultIsIgnore(int signum)
|
||||
{
|
||||
switch (signum) {
|
||||
case sigChld:
|
||||
case sigCont:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Real-world default disposition that is Stop rather than Terminate:
|
||||
SIGTSTP only. Unlike SIGSTOP (see file comment / sigRaise), SIGTSTP is
|
||||
fully catchable/blockable/ignorable -- this default only applies when it
|
||||
is neither, exactly like sigDefaultIsIgnore's entries. */
|
||||
static bool sigDefaultIsStop(int signum)
|
||||
{
|
||||
return signum == sigTstp;
|
||||
}
|
||||
|
||||
/* Whether `signum` would genuinely be delivered (a real handler OR a
|
||||
default disposition of Terminate/Stop -- anything sigConsumePending
|
||||
itself would act on, not silently dismiss) if `thread` returned to ring 3
|
||||
right now. Deliberately mirrors sigConsumePending's own
|
||||
blocked/ignored-dismissal logic for exactly one signal number rather than
|
||||
sharing code with it -- sigConsumePending also mutates sigPending/
|
||||
sigHandlerDepth/the trapframe as it scans, none of which this pure
|
||||
predicate may do, since it runs from sigRaise against a target that is
|
||||
not currently resuming at all. Used solely to decide whether a Sleeping/
|
||||
Blocked target needs early-waking (threadWakeForSignal, sched.c) for this
|
||||
signal -- if this ever drifted out of sync with sigConsumePending's own
|
||||
logic, the failure mode is always benign either way: "wakes for a signal
|
||||
that turns out not to be delivered" is a harmless spurious wake (the
|
||||
target's own retry-syscall loop just re-blocks, exactly like a real
|
||||
spurious wakeup), and "does not wake for one that would have delivered"
|
||||
just falls back to today's behavior of waiting for a natural wake. */
|
||||
static bool sigWouldDeliver(const Thread* thread, int signum)
|
||||
{
|
||||
uint64 bit = 1ull << (uint32)signum;
|
||||
if (thread->sigBlocked & bit)
|
||||
return false;
|
||||
|
||||
uint64 handler = thread->sigHandlers[signum];
|
||||
if (handler == sigHandlerIgnore ||
|
||||
(handler == sigHandlerDefault && sigDefaultIsIgnore(signum)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sigRaiseInfo(uint32 targetId, int signum, int siCode, int siPid, int siStatus)
|
||||
{
|
||||
if (signum <= 0 || signum >= sigMaxSignals)
|
||||
return false;
|
||||
|
||||
/* SIGKILL never touches sigPending/sigBlocked at all -- it is
|
||||
delegated straight to the pre-existing unconditional termination
|
||||
path, exactly like real kill(pid, SIGKILL) bypassing every handler/
|
||||
mask a target could have installed. threadKill does its own id
|
||||
validation (range, state), so none is duplicated here. Neither it
|
||||
nor SIGSTOP just below ever reaches a sigHandlers[] entry that could
|
||||
even be sigFlagSigInfo, so the caller-supplied SigInfo fields are
|
||||
simply never used on either of these two paths. */
|
||||
if (signum == sigKill)
|
||||
return threadKill(targetId);
|
||||
|
||||
/* SIGSTOP: uncatchable, unblockable, unignorable, exactly like SIGKILL
|
||||
above -- bypasses sigPending/sigHandlers/sigBlocked entirely and acts
|
||||
on ThreadState directly. threadStop does its own id validation. */
|
||||
if (signum == sigStop)
|
||||
return threadStop(targetId);
|
||||
|
||||
if (targetId == 0 || targetId >= threadCapacity())
|
||||
return false;
|
||||
|
||||
Thread* target = threadAt(targetId);
|
||||
if (target->state == threadDead || target->state == threadUnused)
|
||||
return false;
|
||||
|
||||
/* SIGCONT's resume is likewise not something a mask or a missing
|
||||
handler can defer -- called unconditionally before the generic
|
||||
pending-bit path below, so a Stopped target is moved back to
|
||||
threadRunnable regardless of whether it also has a handler
|
||||
installed. Its return value is intentionally ignored: "target
|
||||
was not actually Stopped" is a harmless no-op at the process-state
|
||||
level (real SIGCONT to a running process), not a reason to skip
|
||||
delivering the signal itself for any installed handler. */
|
||||
if (signum == sigCont)
|
||||
threadContinue(targetId);
|
||||
|
||||
target->sigPending |= (1ull << (uint32)signum);
|
||||
|
||||
/* Attached regardless of whether signum's handler actually has
|
||||
sigFlagSigInfo set (sigConsumePending is what decides whether this
|
||||
ever gets read at all) -- cheap (4 ints), and simpler than threading
|
||||
the handler's own flags in here just to skip it, matching how
|
||||
sigHandlerMasks/sigHandlerFlags are likewise set unconditionally by
|
||||
sigSetHandler and only ever consulted conditionally. */
|
||||
target->sigPendingInfo[signum].siSignum = signum;
|
||||
target->sigPendingInfo[signum].siCode = siCode;
|
||||
target->sigPendingInfo[signum].siPid = siPid;
|
||||
target->sigPendingInfo[signum].siStatus = siStatus;
|
||||
|
||||
Reference in new issue
Block a user