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