From: Tulio A M Mendes Date: Fri, 13 Feb 2026 09:00:13 +0000 (-0300) Subject: fix: remove killed READY processes from runqueue before marking ZOMBIE X-Git-Url: https://projects.tadryanom.me/docs/static/gitweb.css?a=commitdiff_plain;h=7b2a137f90710449a5b16878ad019805534e4821;p=AdrOS.git fix: remove killed READY processes from runqueue before marking ZOMBIE Root cause of intermittent kernel panic (PAGE FAULT at 0x0, ESP=0): When process_kill(SIGKILL) killed a READY process (sitting in rq_active or rq_expired), it set state=ZOMBIE but did NOT remove the process from the runqueue. Later, the parent reaped the ZOMBIE via waitpid → process_reap_locked → kfree(p), freeing the struct. But the freed pointer remained in the runqueue. rq_pick_next() returned the dangling pointer, schedule() read sp=0 from freed heap memory, and context_switch loaded ESP=0 → PAGE FAULT. The 'ring3' cmdline flag masked this bug by changing scheduler timing: with ring3, the BSP entered usermode immediately via iret, altering the sequence of context switches such that the ZOMBIE was typically dequeued before being reaped. Fix: - Add rq_remove_if_queued() helper: safely searches both rq_active and rq_expired for a process at its priority level before calling rq_dequeue() - process_kill(SIGKILL): dequeue READY victims before setting ZOMBIE - process_reap_locked(): dequeue as safety net before freeing Verified: 10/10 boots without 'ring3' — zero panics (was ~50% fail). Build: clean, cppcheck: clean, smoke: 19/19 pass --- diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c index 6e6925c..c73cba2 100644 --- a/src/kernel/scheduler.c +++ b/src/kernel/scheduler.c @@ -110,6 +110,23 @@ static void rq_dequeue(struct runqueue* rq, struct process* p) { if (!pq->head) rq->bitmap &= ~(1U << prio); } +static void rq_remove_if_queued(struct process* p) { + uint8_t prio = p->priority; + struct process* it; + + it = rq_active->queue[prio].head; + while (it) { + if (it == p) { rq_dequeue(rq_active, p); return; } + it = it->rq_next; + } + + it = rq_expired->queue[prio].head; + while (it) { + if (it == p) { rq_dequeue(rq_expired, p); return; } + it = it->rq_next; + } +} + static struct process* rq_pick_next(void) { if (rq_active->bitmap) { uint32_t prio = bsf32(rq_active->bitmap); @@ -154,6 +171,9 @@ static void process_reap_locked(struct process* p) { if (!p) return; if (p->pid == 0) return; + /* Safety net: ensure process is not in any runqueue before freeing */ + rq_remove_if_queued(p); + if (p == ready_queue_head && p == ready_queue_tail) { return; } @@ -230,6 +250,12 @@ int process_kill(uint32_t pid, int sig) { } if (sig == SIG_KILL) { + /* Remove from runqueue BEFORE marking ZOMBIE to prevent + * rq_pick_next() from returning a stale/freed pointer + * after the parent reaps this process. */ + if (p->state == PROCESS_READY) { + rq_remove_if_queued(p); + } process_close_all_files_locked(p); p->exit_status = 128 + sig; p->state = PROCESS_ZOMBIE;