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. A larger alignment is needed if larger arguments
232 // are passed to the function, which is not the case for the
233 // `kernel_task_entry` function because it does not have any arguments. So
234 // we only need to align the stack pointer to 16 bytes.
235 ctx.set_stack_pointer(kstack.end_vaddr() - 16);
236
237 let new_task = Task {
238 func: ForceSync::new(Cell::new(self.func)),
239 data: self.data.unwrap_or_else(|| Box::new(())),
240 local_data: ForceSync::new(self.local_data.unwrap_or_else(|| Box::new(()))),
241 ctx: SyncUnsafeCell::new(ctx),
242 kstack,
243 switched_to_cpu: AtomicBool::new(false),
244 schedule_info: TaskScheduleInfo {
245 cpu: AtomicCpuId::default(),
246 },
247 };
248
249 Ok(new_task)
250 }
251
252 /// Builds a new task and runs it immediately.
253 #[track_caller]
254 pub fn spawn(self) -> Result<Arc<Task>> {
255 let task = Arc::new(self.build()?);
256 task.run();
257 Ok(task)
258 }
259}
260
261/// The current task.
262///
263/// This type is not `Send`, so it cannot outlive the current task.
264///
265/// This type is also not `Sync`, so it can provide access to the local data of the current task.
266#[derive(Debug)]
267pub struct CurrentTask(NonNull<Task>);
268
269// The intern `NonNull<Task>` contained by `CurrentTask` implies that `CurrentTask` is `!Send` and
270// `!Sync`. But it is still good to do this explicitly because these properties are key for
271// soundness.
272impl !Send for CurrentTask {}
273impl !Sync for CurrentTask {}
274
275impl CurrentTask {
276 /// # Safety
277 ///
278 /// The caller must ensure that `task` is the current task.
279 unsafe fn new(task: NonNull<Task>) -> Self {
280 Self(task)
281 }
282
283 /// Returns the local data of the current task.
284 ///
285 /// Note that the local data is only accessible in the task context. Although there is a
286 /// current task in the non-task context (e.g. IRQ handlers), access to the local data is
287 /// forbidden as it may cause soundness problems.
288 ///
289 /// # Panics
290 ///
291 /// This method will panic if called in a non-task context.
292 pub fn local_data(&self) -> &(dyn Any + Send) {
293 assert!(InterruptLevel::current().is_task_context());
294
295 let local_data = &self.local_data;
296
297 // SAFETY: The `local_data` field will only be accessed by the current task in the task
298 // context, so the data won't be accessed concurrently.
299 &**unsafe { local_data.get() }
300 }
301
302 /// Returns a cloned `Arc<Task>`.
303 pub fn cloned(&self) -> Arc<Task> {
304 let ptr = self.0.as_ptr();
305
306 // SAFETY: The current task is always a valid task and it is always contained in an `Arc`.
307 unsafe { Arc::increment_strong_count(ptr) };
308
309 // SAFETY: We've increased the reference count in the current `Arc<Task>` above.
310 unsafe { Arc::from_raw(ptr) }
311 }
312}
313
314impl Deref for CurrentTask {
315 type Target = Task;
316
317 fn deref(&self) -> &Self::Target {
318 // SAFETY: The current task is always a valid task.
319 unsafe { self.0.as_ref() }
320 }
321}
322
323impl AsRef<Task> for CurrentTask {
324 fn as_ref(&self) -> &Task {
325 self
326 }
327}
328
329impl Borrow<Task> for CurrentTask {
330 fn borrow(&self) -> &Task {
331 self
332 }
333}
334
335/// A trait that provides methods to manipulate the task context.
336pub(crate) trait TaskContextApi {
337 /// Sets the instruction pointer.
338 fn set_instruction_pointer(&mut self, ip: usize);
339
340 /// Sets the stack pointer.
341 fn set_stack_pointer(&mut self, sp: usize);
342}
343
344#[cfg(ktest)]
345mod test {
346 use crate::prelude::*;
347
348 #[ktest]
349 fn create_task() {
350 #[expect(clippy::eq_op)]
351 let task = || {
352 assert_eq!(1, 1);
353 };
354 let task = Arc::new(
355 crate::task::TaskOptions::new(task)
356 .data(())
357 .build()
358 .unwrap(),
359 );
360 task.run();
361 }
362
363 #[ktest]
364 fn spawn_task() {
365 #[expect(clippy::eq_op)]
366 let task = || {
367 assert_eq!(1, 1);
368 };
369 let _ = crate::task::TaskOptions::new(task).data(()).spawn();
370 }
371}