]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
fix: remove killed READY processes from runqueue before marking ZOMBIE
authorTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 09:00:13 +0000 (06:00 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 09:00:13 +0000 (06:00 -0300)
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

src/kernel/scheduler.c

index 6e6925cf2110a7a5162f9b95f8ffd5777b7e2c71..c73cba2e52bd133ccef082568b88c036f0e8b912 100644 (file)
@@ -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;