kernel/sched/
scheduler.rs

1//! Scheduler module
2//!
3//! The scheduler module is responsible for scheduling tasks on the CPU.
4//! Currently, the scheduler is a simple round-robin scheduler with separate
5//! queues for different task states to improve efficiency:
6//!
7//! - `ready_queue`: Tasks that are ready to run
8//! - `blocked_queue`: Tasks waiting for I/O or other events
9//! - `zombie_queue`: Finished tasks waiting to be cleaned up
10//!
11//! This separation avoids unnecessary iteration over blocked/zombie tasks
12//! during normal scheduling operations.
13//!
14//! # TaskPool Safety
15//!
16//! The global `TaskPool` stores tasks in a fixed-size array indexed by task_id.
17//! This design avoids HashMap-related issues and provides stable memory locations:
18//!
19//! - **Fixed Array**: `tasks[task_id]` ensures stable addresses (no reallocation)
20//! - **Direct Indexing**: task_id == index for O(1) access without hash lookup
21//! - **ID Recycling**: Free list reuses task IDs to avoid exhaustion
22//!
23//! The pool provides `get_task()` and `get_task_mut()` which return `&'static`
24//! references using raw pointers. This is **unsafe but practical** because:
25//!
26//! 1. Tasks are stored at fixed addresses (task_id == index)
27//! 2. The scheduler never removes running tasks
28//! 3. Single-core execution prevents concurrent access
29//! 4. Context switches never invalidate the current task's reference
30//!
31//! **IMPORTANT**: Never access `TaskPool::tasks` directly. Always use the
32//! provided methods which document and enforce safety invariants.
33
34extern crate alloc;
35
36use core::panic;
37
38use alloc::{boxed::Box, collections::vec_deque::VecDeque, string::ToString};
39
40use crate::print;
41use crate::println;
42use crate::{
43    arch::{
44        Arch, Trapframe, get_cpu, get_user_trap_handler, instruction::idle, set_next_mode,
45        set_trapvector, trap::user::arch_switch_to_user_space,
46    },
47    environment::MAX_NUM_CPUS,
48    task::{TaskState, new_kernel_task, wake_parent_waiters, wake_task_waiters},
49    timer::get_kernel_timer,
50    vm::get_trampoline_trap_vector,
51};
52
53use crate::task::Task;
54
55/// Task pool that stores tasks in fixed positions
56/// With each Task being 824 bytes, 1024 tasks consume approximately 824 KiB of memory,
57/// which is very reasonable for general-purpose systems.
58/// TODO: Refactor Task struct to use fine-grained Mutex on individual fields
59///       (e.g., state: Mutex<TaskState>, time_slice: Mutex<usize>) and change
60///       TaskPool to use Arc<Task> for safe sharing across threads/contexts.
61///       This would also eliminate the fixed-size limitation.
62const MAX_TASKS: usize = 1024;
63
64/// Global task pool storing all tasks
65/// Using spin::Once with Box-ed tasks array to avoid large stack usage.
66static TASK_POOL: spin::Once<TaskPool> = spin::Once::new();
67
68/// Get the global task pool (lazy initialization on first call)
69pub fn get_task_pool() -> &'static TaskPool {
70    TASK_POOL.call_once(|| TaskPool::new())
71}
72
73/// Global task pool storing all tasks in a Box-ed fixed-size array
74///
75/// # Safety
76///
77/// This struct provides unsafe access to tasks through `get_task()` and `get_task_mut()`
78/// which return `&'static` references without holding locks. This is safe because:
79///
80/// 1. **Stable Box Memory**: Tasks are stored in `Box<[Option<Task>; MAX_TASKS]>`.
81///    Box guarantees the underlying array pointer **never moves** after allocation,
82///    making `&'static` references safe in practice.
83///
84/// 2. **Direct Indexing**: `task_id == index` provides O(1) access without HashMap
85///    overhead. No rehashing or reallocation can occur.
86///
87/// 3. **Scheduler Control**: The scheduler controls all task removal and ensures
88///    that the currently running task is never removed during context switches.
89///
90/// 4. **Single-Core Execution**: Current implementation is single-core, preventing
91///    concurrent access during context switches.
92///
93/// **IMPORTANT**: Do NOT directly access the `tasks` array. Always use:
94/// - `TaskPool::get_task()` for immutable references
95/// - `TaskPool::get_task_mut()` for mutable references
96/// - `Scheduler::get_task_by_id()` which is the preferred public API
97///
98/// Direct array access could violate safety assumptions and cause undefined behavior.
99///
100/// # Memory Layout
101///
102/// The tasks array is allocated directly on heap via Vec→Box conversion:
103/// - No intermediate stack allocation (824KB never touches stack)
104/// - Box<[T]> provides stable pointer for &'static references
105/// - Array size is fixed at compile time (MAX_TASKS = 1024)
106pub struct TaskPool {
107    // Box-ed fixed-length array allocated on heap
108    // Pointer is stable for the lifetime of the program
109    //
110    // ⚠️ DO NOT ACCESS DIRECTLY - Use get_task() or get_task_mut() methods
111    tasks: spin::Mutex<Box<[Option<Task>; MAX_TASKS]>>,
112
113    // Free list of recyclable task IDs
114    // IDs are added here when tasks are removed
115    free_ids: spin::Mutex<VecDeque<usize>>,
116
117    // Next ID to allocate when free list is empty
118    // Atomic is sufficient for lock-free allocation
119    next_id: core::sync::atomic::AtomicUsize,
120}
121
122impl TaskPool {
123    fn new() -> Self {
124        crate::early_println!("[SCHED] TaskPool::new() starting...");
125
126        // Allocate uninitialized Box array directly on heap
127        // No stack usage, no Vec overhead
128        let mut tasks: Box<[core::mem::MaybeUninit<Option<Task>>; MAX_TASKS]> =
129            unsafe { Box::new_uninit().assume_init() };
130
131        // Initialize all elements to None
132        for i in 0..MAX_TASKS {
133            tasks[i].write(None);
134        }
135
136        // Convert to initialized Box<[Option<Task>]>
137        // SAFETY: All elements have been initialized with None
138        let tasks: Box<[Option<Task>; MAX_TASKS]> = unsafe { core::mem::transmute(tasks) };
139
140        crate::early_println!("[SCHED] TaskPool created (heap allocation, stable pointers)");
141
142        TaskPool {
143            tasks: spin::Mutex::new(tasks),
144            free_ids: spin::Mutex::new(VecDeque::new()),
145            next_id: core::sync::atomic::AtomicUsize::new(1), // Start from 1, ID 0 is invalid
146        }
147    }
148
149    /// Allocate a new task ID
150    /// Tries to reuse freed IDs first, then allocates new ones sequentially
151    /// Uses atomic operations for lock-free allocation
152    pub fn allocate_id(&self) -> Option<usize> {
153        // Try to reuse freed IDs first
154        {
155            let mut free_ids = self.free_ids.lock();
156            if let Some(id) = free_ids.pop_front() {
157                return Some(id);
158            }
159        }
160
161        // Allocate new ID using atomic fetch_add
162        let id = self
163            .next_id
164            .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
165        if id >= MAX_TASKS {
166            // Rollback on overflow
167            self.next_id
168                .fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
169            None
170        } else {
171            Some(id)
172        }
173    }
174
175    /// Add a task to the pool
176    /// Allocates an ID, sets it on the task, and returns the ID
177    fn add_task(&self, mut task: Task) -> Result<usize, &'static str> {
178        // Allocate ID for this task
179        let task_id = self.allocate_id().ok_or("Task pool exhausted")?;
180
181        // Add to the pool at the allocated index BEFORE registering namespace mapping
182        if task_id >= MAX_TASKS {
183            return Err("Task ID out of bounds");
184        }
185
186        let mut tasks = self.tasks.lock();
187
188        if tasks[task_id].is_some() {
189            return Err("Task ID slot already occupied");
190        }
191
192        // Allocate namespace ID AFTER checking slot availability
193        let namespace_id = task.get_namespace().allocate_task_id_for(task_id);
194
195        // Set IDs on the task
196        task.set_id(task_id);
197        task.set_namespace_id(namespace_id);
198
199        tasks[task_id] = Some(task);
200        Ok(task_id)
201    }
202
203    /// Get a task reference by ID
204    /// Returns a static reference using raw pointer for lifetime extension
205    ///
206    /// # Safety
207    ///
208    /// This function is safe to use under the following conditions:
209    /// - The task must not be removed while the returned reference is in use
210    /// - In context switching scenarios, the currently running task is never removed
211    /// - Single-core execution ensures no concurrent removal during context switch
212    ///
213    /// The returned reference points to a fixed location in the TaskPool's
214    /// Box-ed array (task_id == index), so the address is **stable**:
215    /// - Box guarantees the underlying array never moves
216    /// - Unlike Vec or HashMap, no reallocation can occur
217    /// - The pointer remains valid for the lifetime of the program
218    ///
219    /// **Important**: Do NOT directly access `TaskPool::tasks` array.
220    /// Always use this method or `get_task_mut()` to ensure proper safety.
221    pub fn get_task(task_id: usize) -> Option<&'static Task> {
222        if task_id >= MAX_TASKS {
223            return None;
224        }
225
226        let pool = get_task_pool();
227        let tasks = pool.tasks.lock();
228
229        // SAFETY: The Box<[T]> ensures the array pointer is stable.
230        // Once a Box is allocated, its underlying pointer never changes.
231        // Combined with scheduler guarantees (no removal of running task),
232        // this provides a de-facto &'static reference.
233        tasks[task_id].as_ref().map(|task| {
234            let ptr = task as *const Task;
235            unsafe { &*ptr }
236        })
237    }
238
239    /// Get a mutable task reference by ID
240    /// Returns a static mutable reference using raw pointer for lifetime extension
241    ///
242    /// # Safety
243    ///
244    /// This function is safe to use under the following conditions:
245    /// - The task must not be removed while the returned reference is in use
246    /// - In context switching scenarios, the currently running task is never removed
247    /// - Single-core execution ensures no concurrent access during context switch
248    ///
249    /// The returned reference points to a fixed location in the TaskPool's
250    /// Box-ed array (task_id == index), so the address is **stable**:
251    /// - Box guarantees the underlying array never moves
252    /// - Unlike Vec or HashMap, no reallocation can occur
253    /// - The pointer remains valid for the lifetime of the program
254    ///
255    /// **Important**: Do NOT directly access `TaskPool::tasks` array.
256    /// Always use this method or `get_task()` to ensure proper safety.
257    ///
258    /// # Note
259    /// This is technically UB in Rust (returning &'static mut without holding lock),
260    /// but safe in practice because:
261    /// - Box<[T]> provides stable memory location (pointer never changes)
262    /// - The scheduler ensures exclusive access during context switches
263    /// - Single-core execution prevents concurrent mutable access
264    /// - Currently running task is never removed
265    pub fn get_task_mut(task_id: usize) -> Option<&'static mut Task> {
266        if task_id >= MAX_TASKS {
267            return None;
268        }
269
270        let pool = get_task_pool();
271        let mut tasks = pool.tasks.lock();
272
273        // SAFETY: The Box<[T]> ensures the array pointer is stable.
274        // Once a Box is allocated, its underlying pointer never changes.
275        // Combined with scheduler guarantees (no removal of running task),
276        // this provides a de-facto &'static mut reference.
277        tasks[task_id].as_mut().map(|task| {
278            let ptr = task as *mut Task;
279            unsafe { &mut *ptr }
280        })
281    }
282
283    /// Remove a task from the pool
284    ///
285    /// # Safety
286    ///
287    /// **CRITICAL**: This method invalidates all `&'static` references returned by
288    /// `get_task()` and `get_task_mut()` for this task_id. The scheduler must ensure:
289    ///
290    /// 1. The task being removed is NOT currently running on any CPU
291    /// 2. No context switch is in progress for this task
292    /// 3. No references to this task are held elsewhere
293    ///
294    /// The scheduler enforces this by:
295    /// - Only removing tasks from zombie_queue (already exited)
296    /// - Never removing the currently running task
297    /// - Ensuring the task is not in ready/blocked queues before removal
298    fn remove_task(&self, task_id: usize) -> Option<Task> {
299        if task_id >= MAX_TASKS {
300            return None;
301        }
302
303        let mut tasks = self.tasks.lock();
304        let task = tasks[task_id].take()?;
305
306        // Add ID to free list for reuse
307        let mut free_ids = self.free_ids.lock();
308        free_ids.push_back(task_id);
309
310        Some(task)
311    }
312
313    #[allow(dead_code)]
314    fn contains_task(&self, task_id: usize) -> bool {
315        if task_id >= MAX_TASKS {
316            return false;
317        }
318
319        let tasks = self.tasks.lock();
320        tasks[task_id].is_some()
321    }
322
323    /// Reset the task pool to initial state (test-only)
324    ///
325    /// Clears all tasks, resets ID allocation, and clears free list.
326    /// This should ONLY be called in tests to clean up state between test cases.
327    ///
328    /// # Safety
329    ///
330    /// This function INVALIDATES all existing `&'static` references to tasks.
331    /// It must ONLY be called when:
332    /// - No tasks are currently running
333    /// - No task references are held elsewhere
334    /// - Called from test code only
335    #[cfg(test)]
336    pub fn reset(&self) {
337        // Clear all task slots
338        let mut tasks = self.tasks.lock();
339        for i in 0..MAX_TASKS {
340            tasks[i] = None;
341        }
342        drop(tasks);
343
344        // Reset ID allocation to start from 1
345        self.next_id.store(1, core::sync::atomic::Ordering::Relaxed);
346
347        // Clear free list
348        self.free_ids.lock().clear();
349    }
350}
351
352static mut SCHEDULER: Option<Scheduler> = None;
353
354pub fn get_scheduler() -> &'static mut Scheduler {
355    unsafe {
356        match SCHEDULER {
357            Some(ref mut s) => s,
358            None => {
359                SCHEDULER = Some(Scheduler::new());
360                get_scheduler()
361            }
362        }
363    }
364}
365
366pub struct Scheduler {
367    /// Queue for ready-to-run task IDs
368    ready_queue: [VecDeque<usize>; MAX_NUM_CPUS],
369    /// Queue for blocked task IDs (waiting for I/O, etc.)
370    blocked_queue: [VecDeque<usize>; MAX_NUM_CPUS],
371    /// Queue for zombie task IDs (finished but not yet cleaned up)
372    zombie_queue: [VecDeque<usize>; MAX_NUM_CPUS],
373    current_task_id: [Option<usize>; MAX_NUM_CPUS],
374}
375
376impl Scheduler {
377    pub fn new() -> Self {
378        Scheduler {
379            ready_queue: [const { VecDeque::new() }; MAX_NUM_CPUS],
380            blocked_queue: [const { VecDeque::new() }; MAX_NUM_CPUS],
381            zombie_queue: [const { VecDeque::new() }; MAX_NUM_CPUS],
382            current_task_id: [const { None }; MAX_NUM_CPUS],
383        }
384    }
385
386    pub fn add_task(&mut self, task: Task, cpu_id: usize) -> usize {
387        // Add task to the global task pool and get the allocated ID
388        let task_id = match get_task_pool().add_task(task) {
389            Ok(id) => id,
390            Err(e) => panic!("Failed to add task: {}", e),
391        };
392        // Add task state info to ready queue
393        self.ready_queue[cpu_id].push_back(task_id);
394        task_id
395    }
396
397    /// Determines the next task to run and returns current and next task IDs
398    ///
399    /// This method performs the core scheduling algorithm and task state management
400    /// without performing actual context switches or hardware setup.
401    ///
402    /// # Arguments
403    /// * `cpu` - The CPU architecture state (for CPU ID)
404    ///
405    /// # Returns
406    /// * `(old_task_id, new_task_id)` - Tuple of old and new task IDs
407    fn run(&mut self, cpu: &Arch) -> (Option<usize>, Option<usize>) {
408        let cpu_id = cpu.get_cpuid();
409        let old_current_task_id = self.current_task_id[cpu_id];
410
411        // IMPORTANT: If there's a current running task, re-queue it BEFORE scheduling
412        // This ensures it's available as a fallback if no other tasks are ready
413        if let Some(current_id) = old_current_task_id {
414            // Check if current task is still in ready_queue (it shouldn't be if it's running)
415            if !self.ready_queue[cpu_id].iter().any(|&id| id == current_id) {
416                // Current task is not in ready_queue (it's running), add it back
417                // Only add if the task is in a valid state to be scheduled
418                if let Some(task) = self.get_task_by_id(current_id) {
419                    match task.state.load(core::sync::atomic::Ordering::SeqCst) {
420                        TaskState::Ready | TaskState::Running => {
421                            self.ready_queue[cpu_id].push_back(current_id);
422                        }
423                        _ => {
424                            // Task is in Zombie, Terminated, Blocked, or NotInitialized state
425                            // Don't re-queue it
426                        }
427                    }
428                }
429            }
430        }
431
432        // Continue trying to find a suitable task to run
433        loop {
434            let task_id = self.ready_queue[cpu_id].pop_front();
435
436            /* If there are no subsequent tasks */
437            if self.ready_queue[cpu_id].is_empty() {
438                match task_id {
439                    Some(task_id) => {
440                        let t = self
441                            .get_task_by_id(task_id)
442                            .expect("Task must exist in task pool");
443                        match t.state.load(core::sync::atomic::Ordering::SeqCst) {
444                            TaskState::NotInitialized => {
445                                panic!("Task must be initialized before scheduling");
446                            }
447                            TaskState::Zombie => {
448                                let task_id = t.get_id();
449                                let parent_id = t.get_parent_id();
450                                self.zombie_queue[cpu_id].push_back(task_id);
451                                self.current_task_id[cpu_id] = None;
452                                // Wake up any processes waiting for this specific task
453                                wake_task_waiters(task_id);
454                                // Also wake up parent process for waitpid(-1)
455                                if let Some(parent_id) = parent_id {
456                                    wake_parent_waiters(parent_id);
457                                }
458                                continue;
459                            }
460                            TaskState::Terminated => {
461                                panic!("At least one task must be scheduled");
462                            }
463                            TaskState::Blocked(_) => {
464                                // Reset current_task_id since this task is no longer current
465                                if self.current_task_id[cpu_id] == Some(task_id) {
466                                    self.current_task_id[cpu_id] = None;
467                                }
468                                // Put blocked task to blocked queue without running it
469                                self.blocked_queue[cpu_id].push_back(task_id);
470                                continue;
471                            }
472                            TaskState::Ready | TaskState::Running => {
473                                t.state.store(
474                                    TaskState::Running,
475                                    core::sync::atomic::Ordering::SeqCst,
476                                );
477                                // Task is ready to run
478                                t.time_slice.store(1, core::sync::atomic::Ordering::SeqCst); // Reset time slice on dispatch
479                                let next_task_id = t.get_id();
480                                self.current_task_id[cpu_id] = Some(next_task_id);
481                                self.ready_queue[cpu_id].push_back(task_id);
482                                return (old_current_task_id, Some(next_task_id));
483                            }
484                        }
485                    }
486                    // If no tasks are ready, create an idle task
487                    None => {
488                        panic!("At least one task must be scheduled");
489                    }
490                }
491            } else {
492                match task_id {
493                    Some(task_id) => {
494                        let t = self
495                            .get_task_by_id(task_id)
496                            .expect("Task must exist in task pool");
497                        match t.state.load(core::sync::atomic::Ordering::SeqCst) {
498                            TaskState::NotInitialized => {
499                                panic!("Task must be initialized before scheduling");
500                            }
501                            TaskState::Zombie => {
502                                let task_id = t.get_id();
503                                let parent_id = t.get_parent_id();
504                                self.zombie_queue[cpu_id].push_back(task_id);
505                                // Wake up any processes waiting for this specific task
506                                wake_task_waiters(task_id);
507                                // Also wake up parent process for waitpid(-1)
508                                if let Some(parent_id) = parent_id {
509                                    wake_parent_waiters(parent_id);
510                                }
511                                continue;
512                            }
513                            TaskState::Terminated => {
514                                get_task_pool().remove_task(task_id);
515                                continue;
516                            }
517                            TaskState::Blocked(_) => {
518                                // Reset current_task_id since this task is no longer current
519                                if self.current_task_id[cpu_id] == Some(task_id) {
520                                    self.current_task_id[cpu_id] = None;
521                                }
522                                // Put blocked task back to the end of queue without running it
523                                self.blocked_queue[cpu_id].push_back(task_id);
524                                continue;
525                            }
526                            TaskState::Ready | TaskState::Running => {
527                                t.time_slice.store(1, core::sync::atomic::Ordering::SeqCst); // Reset time slice on dispatch
528                                let next_task_id = t.get_id();
529                                self.current_task_id[cpu_id] = Some(next_task_id);
530                                self.ready_queue[cpu_id].push_back(task_id);
531                                return (old_current_task_id, Some(next_task_id));
532                            }
533                        }
534                    }
535                    None => return (old_current_task_id, self.current_task_id[cpu_id]),
536                }
537            }
538        }
539    }
540
541    /// Called every timer tick. Decrements the current task's time_slice.
542    /// If time_slice reaches 0, triggers a reschedule.
543    pub fn on_tick(&mut self, cpu_id: usize, trapframe: &mut Trapframe) {
544        // crate::early_println!("[SCHED] CPU{}: on_tick called", cpu_id);
545        if let Some(task_id) = self.get_current_task_id(cpu_id) {
546            if let Some(task) = TaskPool::get_task_mut(task_id) {
547                let current_slice = task.time_slice.load(core::sync::atomic::Ordering::SeqCst);
548                if current_slice > 0 {
549                    task.time_slice
550                        .store(current_slice - 1, core::sync::atomic::Ordering::SeqCst);
551                }
552                let new_slice = task.time_slice.load(core::sync::atomic::Ordering::SeqCst);
553                if new_slice == 0 {
554                    // crate::early_println!(
555                    //     "[SCHED] CPU{}: Time slice expired for Task {}",
556                    //     cpu_id,
557                    //     task_id
558                    // );
559                    // Time slice expired, trigger reschedule
560                    self.schedule(trapframe);
561                }
562            }
563        } else {
564            self.schedule(trapframe);
565        }
566    }
567
568    /// Schedule tasks on the CPU with kernel context switching
569    ///
570    /// This function performs cooperative scheduling by switching between task
571    /// kernel contexts. It returns to the caller, allowing the trap handler
572    /// to handle user space return.
573    ///
574    /// # Arguments
575    /// * `cpu` - The CPU architecture state
576    pub fn schedule(&mut self, trapframe: &mut Trapframe) {
577        let cpu = get_cpu();
578        let cpu_id = cpu.get_cpuid();
579
580        // Step 1: Run scheduling algorithm to get current and next task IDs
581        let (current_task_id, next_task_id) = self.run(cpu);
582
583        // Debug output for monitoring scheduler behavior
584        // if let Some(current_id) = current_task_id {
585        //     if let Some(next_id) = next_task_id {
586        //         if current_id != next_id {
587        //             crate::println!("[SCHED] CPU{}: Task {} -> Task {}", cpu_id, current_id, next_id);
588        //         }
589        //     } else {
590        //         crate::println!("[SCHED] CPU{}: Task {} -> idle", cpu_id, current_id);
591        //     }
592        // } else if let Some(next_id) = next_task_id {
593        //     crate::println!("[SCHED] CPU{}: idle -> Task {}", cpu_id, next_id);
594        // }
595
596        // Step 2: Check if a context switch is needed
597        if next_task_id.is_some() && current_task_id != next_task_id {
598            let next_task_id = next_task_id.expect("Next task ID should be valid");
599
600            // Store current task's user state to VCPU
601            if let Some(current_task_id) = current_task_id {
602                let current_task = self.get_task_by_id(current_task_id).unwrap();
603                current_task.vcpu.lock().store(trapframe);
604
605                // Perform kernel context switch
606                self.kernel_context_switch(cpu_id, current_task_id, next_task_id);
607                // NOTE: After this point, the current task will not execute until it is scheduled again
608
609                // Restore trapframe of same task
610                let current_task = self.get_task_by_id(current_task_id).unwrap();
611                Self::setup_task_execution(get_cpu(), current_task);
612            } else {
613                // No current task (e.g., first scheduling), just switch to next task
614                let next_task = self.get_task_by_id(next_task_id).unwrap();
615                // crate::println!("[SCHED] Setting up task {} for execution", next_task_id);
616                Self::setup_task_execution(get_cpu(), next_task);
617                arch_switch_to_user_space(next_task.get_trapframe()); // Force switch to user space
618            }
619        }
620
621        // Step 3: Setup task execution and process events (after context switch)
622        if let Some(current_task) = self.get_current_task(cpu_id) {
623            // Process pending events before dispatching task
624            let _ = current_task.process_pending_events();
625        }
626        // Schedule returns - trap handler will call arch_switch_to_user_space()
627    }
628
629    /// Start the scheduler and return the first runnable task ID (if any).
630    ///
631    /// This function intentionally avoids performing the initial user-mode transition.
632    /// The very first switch is architecture-specific and should be performed by
633    /// `crate::arch::first_switch_to_user()` from the boot path.
634    pub fn start_scheduler(&mut self) -> Option<usize> {
635        let cpu = get_cpu();
636        let cpu_id = cpu.get_cpuid();
637        let timer = get_kernel_timer();
638        timer.stop(cpu_id);
639
640        // Program the periodic timer, but do not force/require the first switch via IRQ.
641        timer.set_interval_us(cpu_id, crate::timer::TICK_INTERVAL_US);
642        timer.start(cpu_id);
643
644        let (_current_task_id, next_task_id) = self.run(cpu);
645        next_task_id
646    }
647
648    pub fn get_current_task(&mut self, cpu_id: usize) -> Option<&Task> {
649        match self.current_task_id[cpu_id] {
650            Some(task_id) => TaskPool::get_task(task_id),
651            None => None,
652        }
653    }
654
655    /// Get a mutable reference to the current task on the specified CPU
656    ///
657    /// # Safety
658    /// This function returns a mutable reference to the current task,
659    /// which can lead to undefined behavior if misused. The caller must ensure:
660    /// - No other references (mutable or immutable) to the same task exist
661    /// - The task is not concurrently accessed from other contexts
662    /// - The scheduler's invariants are maintained
663    ///
664    pub unsafe fn get_current_task_mut(&mut self, cpu_id: usize) -> Option<&mut Task> {
665        match self.current_task_id[cpu_id] {
666            Some(task_id) => TaskPool::get_task_mut(task_id),
667            None => None,
668        }
669    }
670
671    pub fn get_current_task_id(&self, cpu_id: usize) -> Option<usize> {
672        self.current_task_id[cpu_id]
673    }
674
675    /// Returns a mutable reference to the task with the specified ID, if found.
676    ///
677    /// This method searches the TaskPool to find the task with the specified ID.
678    /// This is needed for Waker integration.
679    ///
680    /// # Arguments
681    /// * `task_id` - The ID of the task to search for.
682    ///
683    /// # Returns
684    /// A mutable reference to the task if found, or None otherwise.
685    pub fn get_task_by_id(&mut self, task_id: usize) -> Option<&mut Task> {
686        TaskPool::get_task_mut(task_id)
687    }
688
689    /// Move a task from blocked queue to ready queue when it's woken up
690    ///
691    /// This method is called by Waker when a blocked task needs to be woken up.
692    ///
693    /// # Arguments
694    /// * `task_id` - The ID of the task to move to ready queue
695    ///
696    /// # Returns
697    /// true if the task was found and moved, false otherwise
698    pub fn wake_task(&mut self, task_id: usize) -> bool {
699        // Search for the task in blocked queues
700        for cpu_id in 0..self.blocked_queue.len() {
701            if let Some(pos) = self.blocked_queue[cpu_id]
702                .iter()
703                .position(|&id| id == task_id)
704            {
705                // Remove from blocked queue
706                self.blocked_queue[cpu_id].remove(pos);
707
708                // Get task from TaskPool and set state to Running
709                if let Some(task) = TaskPool::get_task_mut(task_id) {
710                    task.state
711                        .store(TaskState::Running, core::sync::atomic::Ordering::SeqCst);
712                    // Memory barrier to ensure state change is visible
713                    core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
714                    // Move to ready queue
715                    self.ready_queue[cpu_id].push_back(task_id);
716                    return true;
717                }
718            }
719        }
720        // Not found in blocked queues. This can happen if a wake occurs between
721        // a task marking itself Blocked and the scheduler moving it to the
722        // blocked_queue. In that case, ensure the task state is set back to
723        // Running so that the scheduler does not park it.
724        if let Some(task) = TaskPool::get_task_mut(task_id) {
725            let task_state = task.state.load(core::sync::atomic::Ordering::SeqCst);
726            if let TaskState::Blocked(_) = task_state {
727                task.state
728                    .store(TaskState::Running, core::sync::atomic::Ordering::SeqCst);
729                // Memory barrier to ensure state change is visible
730                core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
731                // CRITICAL FIX: Check if task is in ready_queue, add if not
732                // This prevents tasks from being permanently unscheduled
733                let cpu_id = if let Some(current_id) = self.current_task_id[0] {
734                    if current_id == task_id {
735                        0 // Current task, no need to enqueue
736                    } else {
737                        // Find which CPU's ready_queue should contain this task
738                        // For simplicity, use CPU 0 (can be improved for multi-CPU)
739                        0
740                    }
741                } else {
742                    0 // Default to CPU 0
743                };
744
745                // Only add if not already in ready_queue to avoid duplicates
746                if !self.ready_queue[cpu_id].contains(&task_id) {
747                    self.ready_queue[cpu_id].push_back(task_id);
748                }
749                return true;
750            }
751        }
752        false
753    }
754
755    /// Clean up a zombie task after it has been waited on
756    ///
757    /// This removes the task from zombie_queue and task_pool, freeing all resources.
758    /// Should only be called from Task::wait() after confirming the task is a zombie.
759    ///
760    /// # Arguments
761    /// * `task_id` - The ID of the zombie task to clean up
762    pub fn cleanup_zombie_task(&mut self, task_id: usize) {
763        // Remove from zombie queue
764        for cpu_id in 0..MAX_NUM_CPUS {
765            if let Some(pos) = self.zombie_queue[cpu_id]
766                .iter()
767                .position(|&id| id == task_id)
768            {
769                self.zombie_queue[cpu_id].remove(pos);
770                crate::println!("[Scheduler] Removed task {} from zombie_queue", task_id);
771                break;
772            }
773        }
774
775        // Remove from task pool (this frees all task resources)
776        if let Some(_task) = get_task_pool().remove_task(task_id) {
777            crate::println!("[Scheduler] Cleaned up zombie task {}", task_id);
778        }
779    }
780
781    /// Get IDs of all tasks across ready, blocked, and zombie queues
782    ///
783    /// This helper is used by subsystems (e.g., event broadcast) that need
784    /// to target every task in the system without holding a mutable
785    /// reference to the scheduler during delivery.
786    pub fn get_all_task_ids(&self) -> alloc::vec::Vec<usize> {
787        let mut ids = alloc::vec::Vec::new();
788        // Ready tasks
789        for q in &self.ready_queue {
790            for t in q.iter() {
791                ids.push(*t);
792            }
793        }
794        // Blocked tasks
795        for q in &self.blocked_queue {
796            for t in q.iter() {
797                ids.push(*t);
798            }
799        }
800        // Zombie tasks
801        for q in &self.zombie_queue {
802            for t in q.iter() {
803                ids.push(*t);
804            }
805        }
806        ids
807    }
808
809    /// Perform kernel context switch between tasks
810    ///
811    /// This function handles the low-level kernel context switching between
812    /// the current task and the next selected task. It also saves/restores
813    /// FPU/SIMD/Vector context for user-space tasks.
814    ///
815    /// # Arguments
816    /// * `cpu_id` - The CPU ID
817    /// * `from_task_id` - Current task ID
818    /// * `to_task_id` - Next task ID
819    fn kernel_context_switch(&mut self, cpu_id: usize, from_task_id: usize, to_task_id: usize) {
820        // crate::println!("[SCHED] CPU{}: Switching kernel context from Task {} to Task {}", cpu_id, from_task_id, to_task_id);
821        if from_task_id != to_task_id {
822            // Find tasks in all queues (ready, blocked, zombie)
823            let mut from_ctx_ptr: *mut crate::arch::KernelContext = core::ptr::null_mut();
824            let mut to_ctx_ptr: *const crate::arch::KernelContext = core::ptr::null();
825
826            {
827                if let Some(from_task) = TaskPool::get_task_mut(from_task_id) {
828                    from_ctx_ptr = &mut *from_task.kernel_context.lock();
829
830                    #[cfg(feature = "user-fpu")]
831                    crate::arch::fpu::kernel_switch_out_user_fpu(&mut *from_task.vcpu.lock());
832
833                    #[cfg(feature = "user-vector")]
834                    crate::arch::fpu::kernel_switch_out_user_vector(
835                        cpu_id,
836                        from_task_id,
837                        &mut *from_task.vcpu.lock(),
838                    );
839                }
840                if let Some(to_task) = TaskPool::get_task_mut(to_task_id) {
841                    to_ctx_ptr = &*to_task.kernel_context.lock();
842                }
843            }
844
845            if !from_ctx_ptr.is_null() && !to_ctx_ptr.is_null() {
846                // Perform kernel context switch
847                unsafe {
848                    crate::arch::switch::switch_to(from_ctx_ptr, to_ctx_ptr);
849                }
850
851                // Execution resumes here when this task is rescheduled
852                if let Some(from_task) = TaskPool::get_task_mut(from_task_id) {
853                    #[cfg(feature = "user-fpu")]
854                    crate::arch::fpu::kernel_switch_in_user_fpu(&mut *from_task.vcpu.lock());
855                }
856            } else {
857                // crate::println!("[SCHED] ERROR: Context pointers not found - from: {:p}, to: {:p}", from_ctx_ptr, to_ctx_ptr);
858            }
859        }
860    }
861
862    /// Setup task execution by configuring hardware and user context
863    ///
864    /// This replaces the old dispatcher functionality with a more direct approach.
865    ///
866    /// # Arguments
867    /// * `cpu` - The CPU architecture state
868    /// * `task` - The task to setup for execution
869    pub fn setup_task_execution(cpu: &mut Arch, task: &mut Task) {
870        // crate::early_println!("[SCHED] Setting up Task {} for execution", task.get_id());
871        // crate::early_println!("[SCHED]   before CPU {:#x?}", cpu);
872        // let trapframe = cpu.get_trapframe();
873        // crate::early_println!("[SCHED]   before Trapframe {:#x?}", trapframe);
874
875        // Prefer the high-VA kernel stack window if available
876        let sp = if let Some((_slot, base)) = task.get_kernel_stack_window_base() {
877            // top = base + guard + TASK_KERNEL_STACK_SIZE
878            (base + crate::environment::PAGE_SIZE + crate::environment::TASK_KERNEL_STACK_SIZE)
879                as u64
880        } else {
881            task.get_kernel_stack_bottom_paddr()
882        };
883
884        // crate::early_println!("[SCHED]   Setting kernel stack to {:#x}", sp);
885        cpu.set_kernel_stack(sp);
886
887        // Handle trapframe and vcpu switching - use raw pointer to avoid borrow checker issues
888        // This is safe because we're accessing different fields of the same struct
889        let task_ptr = task as *mut Task;
890        unsafe {
891            let trapframe = (*task_ptr).get_trapframe();
892            (*task_ptr).vcpu.lock().switch(trapframe);
893        }
894
895        cpu.set_trap_handler(get_user_trap_handler());
896        cpu.set_next_address_space(task.vm_manager.get_asid());
897        set_next_mode(task.vcpu.lock().get_mode());
898        // Setup trap vector
899        set_trapvector(get_trampoline_trap_vector());
900
901        // crate::early_println!("[SCHED]   after  CPU {:#x?}", cpu);
902        // crate::early_println!("[SCHED]   after  Trapframe {:#x?}", cpu.get_trapframe());
903
904        // Note: User context (VCPU) will be restored in schedule() after run() returns
905    }
906
907    /// Reset the scheduler to initial state (test-only)
908    ///
909    /// Clears all queues, resets current task IDs, and resets the task pool.
910    /// This should ONLY be called in tests to clean up state between test cases.
911    #[cfg(test)]
912    pub fn reset(&mut self) {
913        // Clear all queues
914        for cpu_id in 0..MAX_NUM_CPUS {
915            self.ready_queue[cpu_id].clear();
916            self.blocked_queue[cpu_id].clear();
917            self.zombie_queue[cpu_id].clear();
918            self.current_task_id[cpu_id] = None;
919        }
920
921        // Reset the task pool
922        get_task_pool().reset();
923    }
924}
925
926pub fn make_test_tasks() {
927    println!("Making test tasks...");
928    let sched = get_scheduler();
929    let mut task0 = new_kernel_task("Task0".to_string(), 0, || {
930        println!("Task0");
931        let mut counter: usize = 0;
932        loop {
933            if counter % 500000 == 0 {
934                print!("\nTask0: ");
935            }
936            if counter % 10000 == 0 {
937                print!(".");
938            }
939            counter += 1;
940            if counter >= 100000000 {
941                break;
942            }
943        }
944        println!("");
945        println!("Task0: Done");
946        idle();
947    });
948    task0.init();
949    sched.add_task(task0, 0);
950
951    let mut task1 = new_kernel_task("Task1".to_string(), 0, || {
952        println!("Task1");
953        let mut counter: usize = 0;
954        loop {
955            if counter % 500000 == 0 {
956                print!("\nTask1: {} %", counter / 1000000);
957            }
958            counter += 1;
959            if counter >= 100000000 {
960                break;
961            }
962        }
963        println!("\nTask1: 100 %");
964        println!("Task1: Completed");
965        idle();
966    });
967    task1.init();
968    sched.add_task(task1, 0);
969
970    let mut task2 = new_kernel_task("Task2".to_string(), 0, || {
971        println!("Task2");
972        /* Fizz Buzz */
973        for i in 1..=1000000 {
974            if i % 1000 > 0 {
975                continue;
976            }
977            let c = i / 1000;
978            if c % 15 == 0 {
979                println!("FizzBuzz");
980            } else if c % 3 == 0 {
981                println!("Fizz");
982            } else if c % 5 == 0 {
983                println!("Buzz");
984            } else {
985                println!("{}", c);
986            }
987        }
988        println!("Task2: Done");
989        idle();
990    });
991    task2.init();
992    sched.add_task(task2, 0);
993}
994
995// late_initcall!(make_test_tasks);
996
997#[cfg(test)]
998mod tests {
999    use crate::task::TaskType;
1000
1001    use super::*;
1002
1003    #[test_case]
1004    fn test_add_task() {
1005        let mut scheduler = Scheduler::new();
1006        let task = Task::new("TestTask".to_string(), 1, TaskType::Kernel);
1007        scheduler.add_task(task, 0);
1008        assert_eq!(scheduler.ready_queue[0].len(), 1);
1009    }
1010}