feat: add sigResetState and sigDefaultIsIgnore

This commit is contained in:
dualfroz committed 2026-09-08 14:04:23 +02:00
1 parent c885f33d76
commit 26476fc78b
1 file changed
+156
+156
View File
@@ -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;