feat: add SigInfo

This commit is contained in:
dualfroz committed 2026-09-02 16:09:04 +02:00
1 parent 2b2252dac5
commit 8d2b1df100
1 file changed
+158
+158
View File
@@ -0,0 +1,158 @@
#ifndef FROSTBYTE_SIGNALNUM_H
#define FROSTBYTE_SIGNALNUM_H
/*
* Plain numeric constants shared verbatim between the kernel (signal.h/
* signal.c) and userspace (user/ulib.h/ulib.c) -- exactly like syscall.h
* already is for syscall numbers. Split out from signal.h specifically so
* user code never has to include signal.h itself, which pulls in sched.h's
* Thread struct and isr.h's Registers -- kernel-only types a freestanding
* user program has no business seeing, let alone compiling against.
*/
/* Signal numbers. A small, real (not exhaustive) POSIX-shaped subset --
enough to exercise catchable delivery, ignore, default-terminate and the
one uncatchable case meaningfully, without pretending to implement every
job-control signal a real kernel would. Numbering matches Linux/x86-64
for anything actually used here, so an unused gap (5, 7, 16) does too. */
#define sigHup 1
#define sigInt 2
#define sigQuit 3
#define sigIll 4
#define sigAbrt 6
#define sigFpe 8
#define sigKill 9
#define sigUsr1 10
#define sigSegv 11
#define sigUsr2 12
#define sigPipe 13
#define sigAlrm 14
#define sigTerm 15
#define sigChld 17
#define sigCont 18
#define sigStop 19
#define sigTstp 20
/* Handler-slot sentinels (Thread.sigHandlers[signum] kernel-side,
userSigAction's `handler` argument userspace-side), distinct from any
real user code address since every user ELF slot starts at
userSpaceMin == 0x40000000 (syscall.c), far above 1. */
#define sigHandlerDefault 0ull
#define sigHandlerIgnore 1ull
/* sigSetHandler's/userSigAction's failure return -- mirrors real
signal(2)'s SIG_ERR, distinguishable from any real sentinel/handler
address a caller could have gotten back as the *previous* disposition.
Written without a uint64 cast (unlike the kernel-only constants in
signal.h) so this header stays includable from userspace, which never
sees types.h -- see fs.h's FbStat for the same convention. */
#define sigSetFailed (~0ull)
/* sigProcMask/userSigProcMask "how" values (mirrors POSIX SIG_BLOCK/
SIG_UNBLOCK/SIG_SETMASK by shape, not by number). */
#define sigMaskBlock 0
#define sigMaskUnblock 1
#define sigMaskSetMask 2
/* sigSetHandler's/userSigAction's `flags` argument (mirrors POSIX
sigaction's sa_flags by shape, not by bit position -- there is only one
flag here). Default (0, what every handler got before this bit existed)
is real POSIX's own default: a signal delivered while a thread sits
blocked inside a slow syscall (read/write/wait -- see signal.c's
sigInRetryableSyscall) does NOT transparently resume that syscall once
the handler returns, it makes the syscall return errInterrupted instead,
exactly like an EINTR on real Unix. Passing sigFlagRestart opts back into
the OTHER behavior (real SIG_RESTART/BSD's original signal(3) semantics):
the interrupted syscall is retried, invisibly to the caller, once the
handler returns. Only meaningful for a signal that actually interrupts a
genuinely retryable syscall; for anything else (plain running code, a
sleep, a signal that does not even install a handler) it is silently
irrelevant, same as real SA_RESTART. */
#define sigFlagRestart (1ull << 0)
/* Mirrors POSIX sigaction's SA_NODEFER: by default (this bit clear), a
signal's own number is added to sigBlocked for as long as ITS handler is
running (signal.h's sigConsumePending, "self-blocked like real Unix's
implicit default") -- so the same signal raised again while its handler
is still active stays merely pending, delivered only once that handler
returns, never nested into itself. Passing sigFlagNoDefer for a signal
disables exactly that one self-block (nothing else -- sa_mask/other
signals' self-blocks are untouched): a second raise of that same signal
while its own handler is active is then genuinely deliverable, entering
a NESTED instance of its own handler, the same mechanism a completely
different unmasked signal already uses to nest (see signal.h's
sa_mask paragraph) -- SA_NODEFER only widens which signal numbers that
mechanism applies to, it does not introduce a new one. Irrelevant for a
signal that never raises itself from inside its own handler. */
#define sigFlagNoDefer (1ull << 1)
/* Mirrors POSIX sigaction's SA_SIGINFO: by default (this bit clear) a
handler is called with exactly one argument, the signal number (this
kernel's ONLY shape until now, `regs->rdi = signum` in signal.c's
sigConsumePending). Passing sigFlagSigInfo for a signal tells
sigConsumePending to ALSO populate a real SigInfo (below) describing this
specific delivery -- who/what raised it, real Unix's siginfo_t.si_pid/
si_code/si_status -- and hand the handler a pointer to it as a SECOND
argument (rsi), plus a third (rdx, always 0/NULL here: this kernel has no
ucontext_t of its own to expose, unlike a real kernel's SA_SIGINFO third
argument). A handler registered with this flag must actually be declared
to take (int, SigInfo*, void*) -- see ulib.h's SigInfoHandler/
userSigActionInfo -- exactly like real sigaction()'s sa_sigaction union
member only being valid to read when SA_SIGINFO was actually passed. */
#define sigFlagSigInfo (1ull << 2)
/* si_code values SigInfo.siCode below can hold -- a small, real (not
exhaustive) subset of POSIX's SI_USER/SI_KERNEL/CLD_EXITED/CLD_KILLED/
CLD_STOPPED/CLD_CONTINUED, numbered independently of them (this kernel's
own small namespace, not a Linux ABI match, unlike the signal numbers
above which deliberately do match). siCodeUser: signum was raised by
another process's real userSigSend/kill(2)-shaped syscall (siPid is the
SENDER's own pid). siCodeKernel: raised by the kernel itself with no
specific sending process (SIGALRM's real timer expiry; also sigRaise's
own thin-wrapper default for any caller that supplies no detail at all).
siCodeChld{Exited,Killed,Stopped,Continued}: sigChld specifically,
distinguishing WHY a child's status changed (siPid is the CHILD's pid,
siStatus its real exit code for Exited, threadKilledExitCode for Killed,
or the job-control signal number for Stopped/Continued) -- real Unix's
own WIFEXITED/WIFSIGNALED/WIFSTOPPED/WIFCONTINUED distinction, exposed
directly instead of needing a separate macro to decode a packed status
word the way real wait(2)'s plain `int status` still does. */
#define siCodeUser 0
#define siCodeKernel 1
#define siCodeChldExited 2
#define siCodeChldKilled 3
#define siCodeChldStopped 4
#define siCodeChldContinued 5
/* Real siginfo_t, cut down to exactly the fields this kernel ever actually
populates (see siCode* above for what each one means per code) -- shared
verbatim between kernel and userspace like every other type in this
header, which is exactly why it is built from plain `int`, not a
types.h-only typedef (see this header's own file comment). siSignum
duplicates the handler's own first argument (signum) -- real siginfo_t
keeps si_signo too, for the same reason: a single handler function
registered against more than one signal number can tell them apart from
*info alone, without needing its own first argument at all. Handed to a
sigFlagSigInfo handler as a real pointer (signal.c's sigConsumePending)
into a small kernel-owned, user-readable (never user-writable) page
mapped at a fixed address -- see signal.h's sigInfoVirtualAddr -- the
same "one shared page, like a vDSO" trick this kernel's trampoline page
already uses, not memory the handler owns or must free. */
typedef struct SigInfo {
int siSignum;
int siCode;
int siPid;
int siStatus;
} SigInfo;
/* A retryable syscall (sysRead/sysWrite/sysWait -- see syscall.c) that got
interrupted by a signal without sigFlagRestart returns this instead of
retrying. Mirrors a raw Linux syscall's own convention of returning
-errno directly (here, -EINTR, whose real errno value is 4) rather than
a separate errno variable, since this kernel has no per-thread errno of
its own to set. Written without a uint64 cast, like every other
userspace-visible constant in this header, so it stays includable from
freestanding user code. */
#define errInterrupted (-4)
#endif