kernel/sched/
scheduler.rs

1//! The scheduler module is responsible for managing the tasks and threads in the system.
2//! It provides the necessary functions to create tasks and threads, and to switch between them.
3
4use core::{ffi::c_void, sync::atomic::AtomicBool};
5
6use super::task::{Task, TaskId};
7use crate::{
8    mem::{self, array::IndexMap, heap::BinaryHeap, queue::Queue},
9    sched::{
10        task::TaskDescriptor,
11        thread::{RunState, ThreadMap, ThreadUId, Timing},
12    },
13    sync::spinlock::SpinLocked,
14    utils,
15};
16
17/// The global scheduler instance.
18pub static SCHEDULER: SpinLocked<Scheduler> = SpinLocked::new(Scheduler::new());
19static SCHEDULER_ENABLED: AtomicBool = AtomicBool::new(false);
20
21/// The scheduler struct. It keeps track of the tasks and threads in the system.
22/// This scheduler is a simple Rate Monotonic Scheduler (RMS) implementation.
23pub struct Scheduler {
24    /// The current running thread.
25    current: Option<ThreadUId>,
26    /// Fast interval store. This gets updated every time a new thread is selected.
27    current_interval: usize,
28    /// Stores the tasks in the system.
29    user_tasks: IndexMap<usize, Task, 8>,
30    /// Stores the threads in the system.
31    threads: ThreadMap<8>,
32    /// The priority queue that yields the next thread to run.
33    queue: BinaryHeap<(usize, ThreadUId), 32>,
34    /// The callbacks queue that stores the threads that need to be fired in the future.
35    callbacks: Queue<(ThreadUId, usize), 32>,
36    /// The progression of the time interval of the scheduler.
37    time: usize,
38}
39
40impl Scheduler {
41    /// Create a new scheduler instance.
42    pub const fn new() -> Self {
43        Self {
44            current: None,
45            current_interval: 0,
46            user_tasks: IndexMap::new(),
47            threads: ThreadMap::new(),
48            queue: BinaryHeap::new(),
49            callbacks: Queue::new(),
50            time: 0,
51        }
52    }
53
54    pub fn create_task(&mut self, desc: TaskDescriptor) -> Result<TaskId, utils::KernelError> {
55        let size = mem::align_up(desc.mem_size);
56        let idx = self
57            .user_tasks
58            .find_empty()
59            .ok_or(utils::KernelError::OutOfMemory)?;
60        let task_id = TaskId::new_user(idx);
61
62        let task = Task::new(size, task_id)?;
63        self.user_tasks.insert(&idx, task)?;
64        Ok(task_id)
65    }
66
67    pub fn create_thread(
68        &mut self,
69        entry: extern "C" fn(),
70        fin: Option<extern "C" fn() -> !>,
71        timing: Timing,
72        task_id: TaskId,
73    ) -> Result<ThreadUId, utils::KernelError> {
74        let task_idx: usize = task_id.into();
75
76        if let Some(task) = self.user_tasks.get_mut(&task_idx) {
77            let desc = task.create_thread(entry, fin, timing)?;
78            let id = self.threads.create(desc)?;
79            self.queue.push((timing.period, id))?;
80            Ok(id)
81        } else {
82            Err(utils::KernelError::InvalidArgument)
83        }
84    }
85
86    /// Updates the current thread context with the given context.
87    ///
88    /// `ctx` - The new context to update the current thread with.
89    fn update_current_ctx(&mut self, ctx: *mut c_void) {
90        if let Some(id) = self.current {
91            if let Some(thread) = self.threads.get_mut(&id) {
92                thread
93                    .update_sp(ctx)
94                    .expect("Failed to update thread context");
95            }
96        }
97    }
98
99    /// Selects a new thread to run, sets the previous thread as ready, and sets the new thread as runs.
100    /// The old thread will be added to the queue to be fired in the next period.
101    /// The new thread will be selected based on the priority queue.
102    ///
103    /// Returns the context of the new thread to run, or `None` if no thread is available.
104    fn select_new_thread(&mut self) -> Option<*mut c_void> {
105        if let Some(id) = self.queue.pop().map(|(_, id)| id) {
106            // Set the previous thread as ready. And add a callback from now.
107            if let Some(id) = self.current {
108                if let Some(thread) = self.threads.get_mut(&id) {
109                    thread.update_run_state(RunState::Ready);
110                    // The delay that is already in the queue.
111                    let delay = self.callbacks.back().map(|(_, delay)| *delay).unwrap_or(0);
112                    // Check if the period is already passed.
113                    if thread.timing().period > (self.time + delay) {
114                        // Add the callback to the queue. If it fails, we can't do much.
115                        let _ = self
116                            .callbacks
117                            .push_back((id, thread.timing().period - (self.time + delay)));
118                    } else {
119                        // If the period is already passed, add it to the queue immediately.
120                        let _ = self.queue.push((thread.timing().exec_time, id));
121                    }
122                }
123            }
124
125            if let Some(thread) = self.threads.get_mut(&id) {
126                thread.update_run_state(RunState::Runs);
127
128                // Set the new thread as the current one.
129                self.current_interval = thread.timing().exec_time;
130                self.current = Some(id);
131
132                // Return the new thread context.
133                return Some(thread.sp());
134            }
135        }
136
137        None
138    }
139
140    /// Fires the thread if necessary.
141    ///
142    /// Returns `true` if a thread was fired, otherwise `false`.
143    fn fire_thread_if_necessary(&mut self) -> bool {
144        let mut found = false;
145        while let Some((id, cnt)) = self.callbacks.front().cloned() {
146            // If the delay is 0, we can fire the thread.
147            if cnt - 1 == 0 {
148                self.callbacks.pop_front();
149                if let Some(thread) = self.threads.get_mut(&id) {
150                    thread.update_run_state(RunState::Ready);
151
152                    let _ = self.queue.push((thread.timing().exec_time, id));
153                    found = true;
154                }
155            } else {
156                // If the delay is not 0, we need to update the delay and reinsert it.
157                let _ = self.callbacks.insert(0, (id, cnt - 1));
158                break;
159            }
160        }
161
162        found
163    }
164
165    /// Ticks the scheduler. This function is called every time the system timer ticks.
166    pub fn tick(&mut self) -> bool {
167        self.time += 1;
168
169        // If a thread was fired, we need to reschedule.
170        if self.fire_thread_if_necessary() {
171            return true;
172        }
173
174        // If the current thread is done, we need to reschedule.
175        if self.time >= self.current_interval {
176            self.time = 0;
177            return true;
178        }
179
180        false
181    }
182}
183
184pub fn enabled() -> bool {
185    SCHEDULER_ENABLED.load(core::sync::atomic::Ordering::Acquire)
186}
187
188pub fn set_enabled(enabled: bool) {
189    SCHEDULER_ENABLED.store(enabled, core::sync::atomic::Ordering::Release);
190}
191
192/// cbindgen:ignore
193/// cbindgen:no-export
194#[unsafe(no_mangle)]
195pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void {
196    {
197        let mut scheduler = SCHEDULER.lock();
198        // Update the current context.
199        scheduler.update_current_ctx(ctx);
200
201        // Select a new thread to run, if available.
202        scheduler.select_new_thread().unwrap_or(ctx)
203    }
204}