Skip to main content

ostd/task/
mod.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! Tasks are the unit of code execution.
4
5pub mod atomic_mode;
6mod kernel_stack;
7mod preempt;
8mod processor;
9pub mod scheduler;
10mod utils;
11
12use core::{
13    any::Any,
14    borrow::Borrow,
15    cell::{Cell, SyncUnsafeCell},
16    ops::Deref,
17    ptr::NonNull,
18    sync::atomic::AtomicBool,
19};
20
21use kernel_stack::KernelStack;
22use processor::current_task;
23use spin::Once;
24use utils::ForceSync;
25
26pub use self::{
27    preempt::{DisabledPreemptGuard, disable_preempt, halt_cpu},
28    scheduler::info::{AtomicCpuId, TaskScheduleInfo},
29};
30use crate::{
31    arch::task::TaskContext,
32    irq::{DisabledLocalIrqGuard, InterruptLevel},
33    prelude::*,
34};
35
36static PRE_SCHEDULE_HANDLER: Once<fn(&DisabledLocalIrqGuard)> = Once::new();
37
38static POST_SCHEDULE_HANDLER: Once<fn()> = Once::new();
39
40static PRE_USER_RUN_HANDLER: Once<fn(&DisabledLocalIrqGuard)> = Once::new();
41
42/// Injects a handler to be executed before scheduling.
43pub fn inject_pre_schedule_handler(handler: fn(&DisabledLocalIrqGuard)) {
44    PRE_SCHEDULE_HANDLER.call_once(|| handler);
45}
46
47/// Injects a handler to be executed after scheduling.
48pub fn inject_post_schedule_handler(handler: fn()) {
49    POST_SCHEDULE_HANDLER.call_once(|| handler);
50}
51
52/// Injects a handler to be executed right before entering user mode.
53pub fn inject_pre_user_run_handler(handler: fn(&DisabledLocalIrqGuard)) {
54    PRE_USER_RUN_HANDLER.call_once(|| handler);
55}
56
57/// Runs the pre-user-run handler if one has been injected.
58///
59/// Called by architecture-specific `execute()` implementations
60/// right before entering user mode.
61pub(crate) fn call_pre_user_run_handler(guard: &DisabledLocalIrqGuard) {
62    if let Some(handler) = PRE_USER_RUN_HANDLER.get() {
63        handler(guard);
64    }
65}
66
67/// A task that executes a function to the end.
68///
69/// Each task is associated with per-task data and an optional user space.
70/// If having a user space, the task can switch to the user space to
71/// execute user code. Multiple tasks can share a single user space.
72#[derive(Debug)]
73pub struct Task {
74    #[expect(clippy::type_complexity)]
75    func: ForceSync<Cell<Option<Box<dyn FnOnce() + Send>>>>,
76
77    data: Box<dyn Any + Send + Sync>,
78    local_data: ForceSync<Box<dyn Any + Send>>,
79
80    ctx: SyncUnsafeCell<TaskContext>,
81    /// kernel stack, note that the top is SyscallFrame/TrapFrame
82    kstack: KernelStack,
83
84    /// If we have switched this task to a CPU.
85    ///
86    /// This is to enforce not context switching to an already running task.
87    /// See [`processor::switch_to_task`] for more details.
88    switched_to_cpu: AtomicBool,
89
90    schedule_info: TaskScheduleInfo,
91}
92
93impl Task {
94    /// Gets the current task.
95    ///
96    /// It returns `None` if the function is called in the bootstrap context.
97    pub fn current() -> Option<CurrentTask> {
98        let current_task = current_task()?;
99
100        // SAFETY: `current_task` is the current task.
101        Some(unsafe { CurrentTask::new(current_task) })
102    }
103
104    pub(super) fn ctx(&self) -> &SyncUnsafeCell<TaskContext> {
105        &self.ctx
106    }
107
108    /// Yields execution so that another task may be scheduled.
109    ///
110    /// Note that this method cannot be simply named "yield" as the name is
111    /// a Rust keyword.
112    #[track_caller]
113    pub fn yield_now() {
114        scheduler::yield_now()
115    }
116
117    /// Kicks the task scheduler to run the task.
118    ///
119    /// BUG: This method highly depends on the current scheduling policy.
120    #[track_caller]
121    pub fn run(self: &Arc<Self>) {
122        scheduler::run_new_task(self.clone());
123    }
124
125    /// Returns the task data.
126    pub fn data(&self) -> &Box<dyn Any + Send + Sync> {
127        &self.data
128    }
129
130    /// Get the attached scheduling information.
131    pub fn schedule_info(&self) -> &TaskScheduleInfo {
132        &self.schedule_info
133    }
134}
135
136/// Options to create or spawn a new task.
137pub struct TaskOptions {
138    func: Option<Box<dyn FnOnce() + Send>>,
139    data: Option<Box<dyn Any + Send + Sync>>,
140    local_data: Option<Box<dyn Any + Send>>,
141}
142
143impl TaskOptions {
144    /// Creates a set of options for a task.
145    pub fn new<F>(func: F) -> Self
146    where
147        F: FnOnce() + Send + 'static,
148    {
149        Self {
150            func: Some(Box::new(func)),
151            data: None,
152            local_data: None,
153        }
154    }
155
156    /// Sets the function that represents the entry point of the task.
157    pub fn func<F>(mut self, func: F) -> Self
158    where
159        F: Fn() + Send + 'static,
160    {
161        self.func = Some(Box::new(func));
162        self
163    }
164
165    /// Sets the data associated with the task.
166    pub fn data<T>(mut self, data: T) -> Self
167    where
168        T: Any + Send + Sync,
169    {
170        self.data = Some(Box::new(data));
171        self
172    }
173
174    /// Sets the local data associated with the task.
175    pub fn local_data<T>(mut self, data: T) -> Self
176    where
177        T: Any + Send,
178    {
179        self.local_data = Some(Box::new(data));
180        self
181    }
182
183    /// Builds a new task without running it immediately.
184    pub fn build(self) -> Result<Task> {
185        // All tasks will enter this function. It is meant to execute the `task_fn` in `Task`.
186        //
187        // We provide an assembly wrapper for this function as the end of call stack so we
188        // have to disable name mangling for it.
189        //
190        // # Safety
191        //
192        // This function must be called from `switch.S` when the context is prepared correctly.
193        // SAFETY: The name does not collide with other symbols.
194        #[unsafe(no_mangle)]
195        unsafe extern "C" fn kernel_task_entry() -> ! {
196            // SAFETY: The new task is switched on a CPU for the first time, `after_switching_to`
197            // hasn't been called yet.
198            unsafe { processor::after_switching_to() };
199
200            let current_task = Task::current()
201                .expect("no current task, it should have current task in kernel task entry");
202
203            // SAFETY: The `func` field will only be accessed by the current task in the task
204            // context, so the data won't be accessed concurrently.
205            let task_func = unsafe { current_task.func.get() };
206            let task_func = task_func
207                .take()
208                .expect("task function is `None` when trying to run");
209            task_func();
210
211            // Manually drop all the on-stack variables to prevent memory leakage!
212            // This is needed because `scheduler::exit_current()` will never return.
213            //
214            // However, `current_task` _borrows_ the current task without holding
215            // an extra reference count. So we do nothing here.
216
217            scheduler::exit_current();
218        }
219
220        let kstack = KernelStack::new_with_guard_page()?;
221
222        let mut ctx = TaskContext::new();
223        ctx.set_instruction_pointer(
224            crate::arch::task::kernel_task_entry_wrapper as *const () as usize,
225        );
226        // We should reserve space for the return address in the stack, otherwise
227        // we will write across the page boundary due to the implementation of
228        // the context switch.
229        //
230        // According to the System V AMD64 ABI, the stack pointer should be aligned
231        // to at least 16 bytes. And a larger alignment is needed if larger arguments
232        // are passed to the function. The `kernel_task_entry` function does not
233        // have any arguments, so we only need to align the stack pointer to 16 bytes.
234        ctx.set_stack_pointer(kstack.end_vaddr() - 16);
235
236        let new_task = Task {
237            func: ForceSync::new(Cell::new(self.func)),
238            data: self.data.unwrap_or_else(|| Box::new(())),
239            local_data: ForceSync::new(self.local_data.unwrap_or_else(|| Box::new(()))),
240            ctx: SyncUnsafeCell::new(ctx),
241            kstack,
242            schedule_info: TaskScheduleInfo {
243                cpu: AtomicCpuId::default(),
244            },
245            switched_to_cpu: AtomicBool::new(false),
246        };
247
248        Ok(new_task)
249    }
250
251    /// Builds a new task and runs it immediately.
252    #[track_caller]
253    pub fn spawn(self) -> Result<Arc<Task>> {
254        let task = Arc::new(self.build()?);
255        task.run();
256        Ok(task)
257    }
258}
259
260/// The current task.
261///
262/// This type is not `Send`, so it cannot outlive the current task.
263///
264/// This type is also not `Sync`, so it can provide access to the local data of the current task.
265#[derive(Debug)]
266pub struct CurrentTask(NonNull<Task>);
267
268// The intern `NonNull<Task>` contained by `CurrentTask` implies that `CurrentTask` is `!Send` and
269// `!Sync`. But it is still good to do this explicitly because these properties are key for
270// soundness.
271impl !Send for CurrentTask {}
272impl !Sync for CurrentTask {}
273
274impl CurrentTask {
275    /// # Safety
276    ///
277    /// The caller must ensure that `task` is the current task.
278    unsafe fn new(task: NonNull<Task>) -> Self {
279        Self(task)
280    }
281
282    /// Returns the local data of the current task.
283    ///
284    /// Note that the local data is only accessible in the task context. Although there is a
285    /// current task in the non-task context (e.g. IRQ handlers), access to the local data is
286    /// forbidden as it may cause soundness problems.
287    ///
288    /// # Panics
289    ///
290    /// This method will panic if called in a non-task context.
291    pub fn local_data(&self) -> &(dyn Any + Send) {
292        assert!(InterruptLevel::current().is_task_context());
293
294        let local_data = &self.local_data;
295
296        // SAFETY: The `local_data` field will only be accessed by the current task in the task
297        // context, so the data won't be accessed concurrently.
298        &**unsafe { local_data.get() }
299    }
300
301    /// Returns a cloned `Arc<Task>`.
302    pub fn cloned(&self) -> Arc<Task> {
303        let ptr = self.0.as_ptr();
304
305        // SAFETY: The current task is always a valid task and it is always contained in an `Arc`.
306        unsafe { Arc::increment_strong_count(ptr) };
307
308        // SAFETY: We've increased the reference count in the current `Arc<Task>` above.
309        unsafe { Arc::from_raw(ptr) }
310    }
311}
312
313impl Deref for CurrentTask {
314    type Target = Task;
315
316    fn deref(&self) -> &Self::Target {
317        // SAFETY: The current task is always a valid task.
318        unsafe { self.0.as_ref() }
319    }
320}
321
322impl AsRef<Task> for CurrentTask {
323    fn as_ref(&self) -> &Task {
324        self
325    }
326}
327
328impl Borrow<Task> for CurrentTask {
329    fn borrow(&self) -> &Task {
330        self
331    }
332}
333
334/// A trait that provides methods to manipulate the task context.
335pub(crate) trait TaskContextApi {
336    /// Sets the instruction pointer.
337    fn set_instruction_pointer(&mut self, ip: usize);
338
339    /// Sets the stack pointer.
340    fn set_stack_pointer(&mut self, sp: usize);
341}
342
343#[cfg(ktest)]
344mod test {
345    use crate::prelude::*;
346
347    #[ktest]
348    fn create_task() {
349        #[expect(clippy::eq_op)]
350        let task = || {
351            assert_eq!(1, 1);
352        };
353        let task = Arc::new(
354            crate::task::TaskOptions::new(task)
355                .data(())
356                .build()
357                .unwrap(),
358        );
359        task.run();
360    }
361
362    #[ktest]
363    fn spawn_task() {
364        #[expect(clippy::eq_op)]
365        let task = || {
366            assert_eq!(1, 1);
367        };
368        let _ = crate::task::TaskOptions::new(task).data(()).spawn();
369    }
370}