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