@@ -28,6 +28,42 @@ use crate::cpu::gdt::{KERNEL_CS, KERNEL_DS};
2828use crate :: memory:: { global_registry_mut, is_registry_initialized, PAGE_SIZE } ;
2929use crate :: serial:: { put_hex32, put_hex64, puts} ;
3030use core:: sync:: atomic:: { AtomicU32 , AtomicU64 , Ordering } ;
31+
32+ use crate :: cpu:: per_cpu:: MAX_CPUS ;
33+
34+ // Per-AP idle contexts. When an AP descheduled a user process and nothing
35+ // else is runnable, we MUST NOT return the outgoing process's ring-3 context.
36+ // That would iretq into user code with current_pid=0, corrupting everything.
37+ // Instead we return a ring-0 context pointing at the AP idle HLT loop.
38+ static mut AP_IDLE_CTX : [ CpuContext ; MAX_CPUS ] = [ const { CpuContext :: empty ( ) } ; MAX_CPUS ] ;
39+
40+ /// Ring 0 HLT loop for idle APs. The LAPIC timer will fire and
41+ /// scheduler_tick will pick a real process if one becomes Ready.
42+ /// Never called directly — only entered via iretq from the ISR.
43+ #[ inline( never) ]
44+ unsafe fn ap_idle_hlt_loop ( ) -> ! {
45+ loop {
46+ core:: arch:: asm!( "sti; hlt; cli" , options( nostack, nomem) ) ;
47+ }
48+ }
49+
50+ /// Build and return a kernel-mode idle context for the given AP core.
51+ /// Uses the AP's boot kernel stack and jumps to ap_idle_hlt_loop.
52+ unsafe fn ap_idle_context ( core_idx : u32 ) -> & ' static CpuContext {
53+ let ctx = & mut AP_IDLE_CTX [ core_idx as usize ] ;
54+ let pcpu = crate :: cpu:: per_cpu:: current ( ) ;
55+ ctx. rip = ap_idle_hlt_loop as u64 ;
56+ ctx. rsp = pcpu. boot_kernel_rsp ;
57+ ctx. cs = KERNEL_CS as u64 ;
58+ ctx. ss = KERNEL_DS as u64 ;
59+ ctx. rflags = 0x202 ; // IF=1
60+ // zero all GPRs — clean state
61+ ctx. rax = 0 ; ctx. rbx = 0 ; ctx. rcx = 0 ; ctx. rdx = 0 ;
62+ ctx. rsi = 0 ; ctx. rdi = 0 ; ctx. rbp = 0 ;
63+ ctx. r8 = 0 ; ctx. r9 = 0 ; ctx. r10 = 0 ; ctx. r11 = 0 ;
64+ ctx. r12 = 0 ; ctx. r13 = 0 ; ctx. r14 = 0 ; ctx. r15 = 0 ;
65+ ctx
66+ }
3167// GLOBAL STATE
3268
3369/// The flat process table. Index == PID.
@@ -379,13 +415,27 @@ impl Scheduler {
379415 // TODO: Clean up after dead process :)
380416 match sig {
381417 Signal :: SIGKILL => {
382- puts ( "[SCHED] SIGKILL → PID " ) ;
383- put_hex32 ( pid) ;
384- puts ( "\n " ) ;
385- terminate_process_inner ( slot, -9 ) ;
418+ // if the target is running a syscall on another core, it holds
419+ // &mut Process via current_process_mut() WITHOUT the lock.
420+ // calling terminate_process_inner here would race on the same
421+ // Process entry. defer: set pending signal, let the target
422+ // core's scheduler_tick deliver it after saving context.
423+ if slot. running_on != u32:: MAX {
424+ slot. pending_signals . raise ( Signal :: SIGKILL ) ;
425+ } else {
426+ puts ( "[SCHED] SIGKILL → PID " ) ;
427+ put_hex32 ( pid) ;
428+ puts ( "\n " ) ;
429+ terminate_process_inner ( slot, -9 ) ;
430+ }
386431 }
387432 Signal :: SIGSTOP => {
388- slot. state = ProcessState :: Blocked ( BlockReason :: Io ) ;
433+ // same race concern as SIGKILL — defer if running on another core.
434+ if slot. running_on != u32:: MAX {
435+ slot. pending_signals . raise ( Signal :: SIGSTOP ) ;
436+ } else {
437+ slot. state = ProcessState :: Blocked ( BlockReason :: Io ) ;
438+ }
389439 }
390440 Signal :: SIGCONT => {
391441 if let ProcessState :: Blocked ( _) = slot. state {
@@ -655,14 +705,15 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
655705
656706 let next_pid = pick_next ( 0 , true , core_idx) ;
657707 if next_pid == 0 {
658- // no user process available — keep idling. null fpu ptr so ISR
659- // skips FXSAVE/FXRSTOR (AP idle loop doesn't use FPU).
708+ // no user process available — return to ring 0 idle loop.
660709 set_percpu_fpu_ptr ( 0 ) ;
661- set_percpu_next_cr3 ( 0 ) ;
710+ set_percpu_next_cr3 ( KERNEL_CR3 ) ;
711+ // restore AP's own kernel stack in TSS for future ring transitions
712+ let pcpu = crate :: cpu:: per_cpu:: current ( ) ;
713+ crate :: cpu:: gdt:: set_kernel_stack_for_core ( core_idx, pcpu. boot_kernel_rsp ) ;
714+ pcpu. kernel_syscall_rsp = pcpu. boot_kernel_rsp ;
662715 PROCESS_TABLE_LOCK . unlock ( ) ;
663- // return the ISR's own stack-saved context. AP wakes up exactly
664- // where it was (hlt loop). PID 0's context is untouched.
665- return core:: mem:: transmute :: < & CpuContext , & ' static CpuContext > ( current_ctx) ;
716+ return ap_idle_context ( core_idx) ;
666717 }
667718
668719 // found a real process — switch AP to it
@@ -684,11 +735,15 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
684735 set_percpu_fpu_ptr ( fpu_ptr) ;
685736 & next. context
686737 } else {
738+ // pick_next lied — PID doesn't exist. reset to idle.
739+ set_this_core_pid ( 0 ) ;
687740 set_percpu_fpu_ptr ( 0 ) ;
688- set_percpu_next_cr3 ( 0 ) ;
689- // transmute lifetime — pointer is on ISR stack, used immediately
741+ set_percpu_next_cr3 ( KERNEL_CR3 ) ;
742+ let pcpu = crate :: cpu:: per_cpu:: current ( ) ;
743+ crate :: cpu:: gdt:: set_kernel_stack_for_core ( core_idx, pcpu. boot_kernel_rsp ) ;
744+ pcpu. kernel_syscall_rsp = pcpu. boot_kernel_rsp ;
690745 PROCESS_TABLE_LOCK . unlock ( ) ;
691- return core :: mem :: transmute :: < & CpuContext , & ' static CpuContext > ( current_ctx ) ;
746+ return ap_idle_context ( core_idx ) ;
692747 } ;
693748
694749 PROCESS_TABLE_LOCK . unlock ( ) ;
@@ -751,13 +806,19 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
751806 let next_pid = pick_next ( cur_pid, skip_kernel, core_idx) ;
752807
753808 // AP ran a user process but nothing else is runnable — return to idle.
754- // don't let pick_next's fallback put the AP on PID 0.
809+ // CRITICAL: must NOT return the outgoing process's ring-3 context.
810+ // That would iretq into user code while AP thinks it's PID 0 — instant UB.
811+ // Instead, return a kernel-mode idle context on the AP's own stack.
755812 if next_pid == 0 && core_idx != 0 {
756813 set_this_core_pid ( 0 ) ;
757814 set_percpu_fpu_ptr ( 0 ) ;
758- set_percpu_next_cr3 ( 0 ) ;
815+ set_percpu_next_cr3 ( KERNEL_CR3 ) ;
816+ // restore AP's own kernel stack in TSS
817+ let pcpu = crate :: cpu:: per_cpu:: current ( ) ;
818+ crate :: cpu:: gdt:: set_kernel_stack_for_core ( core_idx, pcpu. boot_kernel_rsp ) ;
819+ pcpu. kernel_syscall_rsp = pcpu. boot_kernel_rsp ;
759820 PROCESS_TABLE_LOCK . unlock ( ) ;
760- return core :: mem :: transmute :: < & CpuContext , & ' static CpuContext > ( current_ctx ) ;
821+ return ap_idle_context ( core_idx ) ;
761822 }
762823
763824 set_this_core_pid ( next_pid as u32 ) ;
@@ -788,11 +849,27 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
788849
789850 & next. context
790851 } else {
791- // fallback: restore current
852+ // pick_next returned a PID that no longer exists. recover gracefully.
853+ if core_idx != 0 {
854+ // AP: go back to idle
855+ set_this_core_pid ( 0 ) ;
856+ set_percpu_fpu_ptr ( 0 ) ;
857+ set_percpu_next_cr3 ( KERNEL_CR3 ) ;
858+ let pcpu = crate :: cpu:: per_cpu:: current ( ) ;
859+ crate :: cpu:: gdt:: set_kernel_stack_for_core ( core_idx, pcpu. boot_kernel_rsp ) ;
860+ pcpu. kernel_syscall_rsp = pcpu. boot_kernel_rsp ;
861+ PROCESS_TABLE_LOCK . unlock ( ) ;
862+ return ap_idle_context ( core_idx) ;
863+ }
864+ // BSP: reset core PID and return kernel context
865+ set_this_core_pid ( 0 ) ;
866+ set_percpu_fpu_ptr ( 0 ) ;
867+ set_percpu_next_cr3 ( KERNEL_CR3 ) ;
792868 if let Some ( Some ( cur) ) = PROCESS_TABLE . get ( cur_pid) {
793869 & cur. context
794870 } else {
795- panic ! ( "scheduler: no runnable process" )
871+ // nothing — return ISR stack frame unchanged
872+ core:: mem:: transmute :: < & CpuContext , & ' static CpuContext > ( current_ctx)
796873 }
797874 } ;
798875
0 commit comments