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}