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