From f59833733fb78d63365bc9d8ffd9e04feea5a104 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 2 Sep 2026 22:28:12 +0200 Subject: [PATCH] feat: extend isr with deliverPendingSignals and tryResolveUserPageFault --- src/isr.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 11 deletions(-) diff --git a/src/isr.c b/src/isr.c index 9f83d18..4f47a66 100644 --- a/src/isr.c +++ b/src/isr.c @@ -5,6 +5,8 @@ #include "keyboard.h" #include "sched.h" #include "ports.h" +#include "signal.h" +#include "mmap.h" static const char* exceptionNames[32] = { "divide by zero", @@ -52,24 +54,136 @@ static void panicOnException(Registers* regs) __asm__ volatile("cli; hlt"); } +/* Runs at every single return-to-ring3 point this kernel has (a completed + syscall, a timer reschedule, any other IRQ resuming whoever it + interrupted) -- the one choke point isrDispatch funnels its result + through below, regardless of which of those three paths produced it. + `rsp` is whatever Registers* the caller is about to resume; if that + context is not ring 3 (a kernel thread, e.g. workerA/shell -- see + sched.h's userCodeSelector|3 vs kernelCodeSelector), signals are not a + concept that applies to it at all and this is a no-op. Otherwise + currentThread() is guaranteed to be exactly the owner of `rsp` (schedule() + already updated currentIndex before returning it; the syscall/other-IRQ + paths never changed currentIndex away from whoever trapped in), so + sigConsumePending always inspects the right thread's pending/blocked + state. A default-disposition Terminate (or job-control Stop, sigTstp's + default -- see signal.h) signal cannot be handled here directly -- it + needs a NEW thread's rsp, which only schedule() can produce -- so the + loop below reuses the exact same "flip state, then reschedule" pattern + sysExit/sysSleep already use (threadExit for Terminate, threadStop for + Stop), and loops again in case the freshly chosen thread also has a + signal of its own waiting (e.g. two threads both made Runnable by the + same timer tick and both signaled). */ +static uint64 deliverPendingSignals(uint64 rsp) +{ + for (;;) { + Registers* regs = (Registers*)rsp; + if ((regs->cs & 3) != 3) + return rsp; + + Thread* thread = currentThread(); + int signum = 0; + SigOutcome outcome = sigConsumePending(thread, regs, &signum); + + if (outcome == sigOutcomeNone) + return rsp; + + if (outcome == sigOutcomeHandler) + return (uint64)regs; + + if (outcome == sigOutcomeStop) { + kprintf("[sig] pid=%u '%s' stopped by signal %d\n", + thread->id, thread->name, signum); + threadStop(thread->id); + rsp = schedule(rsp); + continue; + } + + kprintf("[sig] pid=%u '%s' terminated by uncaught signal %d\n", + thread->id, thread->name, signum); + threadExit(128u + (uint64)signum); + rsp = schedule(rsp); + } +} + +/* Attempts to resolve a ring-3 #PF through mmap.c's own demand-paging + machinery: cr2 is the real faulting address (same register the panic + path above already reads), errCode bit 1 (0x2) is the CPU's own + "this was a write access" flag -- the exact distinction + mmapFaultHandler needs to refuse a write against a read-only mapping + instead of silently allowing it. Returns whatever mmapFaultHandler + itself returns: true means a real frame is now mapped and the faulting + instruction can simply be retried (the CPU never advanced rip past a + #PF to begin with); false means this address is not a mapping this + kernel's mmap.c even knows about, or the access violates its own prot + bits -- the caller (isrDispatch below) turns that into a real SIGSEGV + rather than the kernel-wide panic every OTHER exception still gets. */ +static bool tryResolveUserPageFault(Registers* regs) +{ + /* errCode bit 4 (I/D) is the CPU's own "this fault was an instruction + fetch" flag -- only ever set once paging.h's pageNoExecute is real + hardware enforcement (IA32_EFER.NXE, see paging.c's + pagingNxSupported/boot.S), meaning a PRESENT page's own NX bit + refused execution. This can never be something mmapFaultHandler's + demand-paging logic fixes: there is no lazy version of "make this + page executable" the way there is for a not-yet-faulted-in page, and + mmapFaultHandler's own "pagingResolve != 0 -> already resolved, just + retry" shortcut (mmap.c) would otherwise see the very same present + page, report success, and send the CPU straight back to retry the + identical faulting instruction fetch -- an infinite fault loop that + hangs the whole kernel rather than the one offending thread. Refuse + outright instead; isrDispatch below turns this into a real SIGSEGV + exactly like any other refusal, whether the address belongs to an + mmap Vma, or (mmapFind would never even find it) a stack/brk page + mapped NX by kmain.c/syscall.c/jobctl.c/sched.c. */ + if (regs->errCode & 0x10) + return false; + + uint64 faultAddress; + __asm__ volatile("mov %%cr2, %0" : "=r"(faultAddress)); + bool forWrite = (regs->errCode & 0x2) != 0; + return mmapFaultHandler(currentThread(), faultAddress, forWrite); +} + uint64 isrDispatch(Registers* regs) { + /* A ring-3 (user-mode) page fault gets one real chance at recovery + before falling through to the unconditional panic below: an + instruction inside SOME OTHER thread's buggy user program should + never be able to take the whole machine down with it, exactly like + a real kernel's own per-process fault isolation -- this kernel's + flat, single-shared-address-space design (see paging.c's own file + comments) cannot isolate a bad ACCESS the way separate page tables + would, but it can still isolate the CONSEQUENCE, by killing only the + one thread that caused it. A kernel-mode fault (ring 0, cs&3==0 -- + this kernel's own bug, not a user program's) still panics + unconditionally below, exactly as before this existed. */ + if (regs->intNo == 14 && (regs->cs & 3) == 3) { + if (!tryResolveUserPageFault(regs)) + sigRaiseInfo(currentThreadId(), sigSegv, siCodeKernel, 0, 0); + return deliverPendingSignals((uint64)regs); + } + if (regs->intNo < 32) panicOnException(regs); - if (regs->intNo == 128) - return syscallDispatch(regs); + uint64 nextRsp; - uint8 irq = (uint8)(regs->intNo - 32); - uint64 nextRsp = (uint64)regs; + if (regs->intNo == 128) { + nextRsp = syscallDispatch(regs); + } else { + uint8 irq = (uint8)(regs->intNo - 32); + nextRsp = (uint64)regs; - if (irq == 0) { - pitTick(); - nextRsp = schedule((uint64)regs); - } else if (irq == 1) { - keyboardHandler(); + if (irq == 0) { + pitTick(); + nextRsp = schedule((uint64)regs); + } else if (irq == 1) { + keyboardHandler(); + } + + picSendEoi(irq); } - picSendEoi(irq); - return nextRsp; + return deliverPendingSignals(nextRsp); }