Skip to main content

hermit/scheduler/task/
mod.rs

1#![allow(clippy::type_complexity)]
2
3#[cfg(not(feature = "common-os"))]
4pub(crate) mod tls;
5
6use alloc::collections::{LinkedList, VecDeque};
7use alloc::rc::Rc;
8use alloc::sync::Arc;
9use core::cell::RefCell;
10use core::num::NonZeroU64;
11use core::{cmp, fmt};
12
13use ahash::RandomState;
14use crossbeam_utils::CachePadded;
15use hashbrown::HashMap;
16use hermit_sync::{OnceCell, RwSpinLock};
17use memory_addresses::VirtAddr;
18
19#[cfg(not(feature = "common-os"))]
20use self::tls::Tls;
21use super::timer_interrupts::{Source, create_timer_abs};
22use crate::arch;
23use crate::arch::core_local::*;
24use crate::arch::scheduler::TaskStacks;
25use crate::fd::{Fd, RawFd, stdio};
26use crate::scheduler::CoreId;
27
28/// Returns the most significant bit.
29///
30/// # Examples
31///
32/// ```
33/// assert_eq!(msb(0), None);
34/// assert_eq!(msb(1), 0);
35/// assert_eq!(msb(u64::MAX), 63);
36/// ```
37#[inline]
38fn msb(n: u64) -> Option<u32> {
39	NonZeroU64::new(n).map(|n| u64::BITS - 1 - n.leading_zeros())
40}
41
42/// The status of the task - used for scheduling
43#[derive(Copy, Clone, Debug, Eq, PartialEq)]
44pub(crate) enum TaskStatus {
45	Invalid,
46	Ready,
47	Running,
48	Blocked,
49	Finished,
50	Idle,
51}
52
53/// Unique identifier for a task (i.e. `pid`).
54#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
55pub struct TaskId(i32);
56
57impl TaskId {
58	pub const fn into(self) -> i32 {
59		self.0
60	}
61
62	pub const fn from(x: i32) -> Self {
63		TaskId(x)
64	}
65}
66
67impl fmt::Display for TaskId {
68	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69		write!(f, "{}", self.0)
70	}
71}
72
73/// Priority of a task
74#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
75pub struct Priority(u8);
76
77impl Priority {
78	pub const fn into(self) -> u8 {
79		self.0
80	}
81
82	pub const fn from(x: u8) -> Self {
83		Priority(x)
84	}
85}
86
87impl fmt::Display for Priority {
88	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89		write!(f, "{}", self.0)
90	}
91}
92
93#[allow(dead_code)]
94pub const HIGH_PRIO: Priority = Priority::from(3);
95pub const NORMAL_PRIO: Priority = Priority::from(2);
96#[allow(dead_code)]
97pub const LOW_PRIO: Priority = Priority::from(1);
98pub const IDLE_PRIO: Priority = Priority::from(0);
99
100/// Maximum number of priorities
101pub const NO_PRIORITIES: usize = 31;
102
103#[derive(Copy, Clone, Debug)]
104pub(crate) struct TaskHandle {
105	id: TaskId,
106	priority: Priority,
107	#[cfg(feature = "smp")]
108	core_id: CoreId,
109}
110
111impl TaskHandle {
112	pub fn new(id: TaskId, priority: Priority, #[cfg(feature = "smp")] core_id: CoreId) -> Self {
113		Self {
114			id,
115			priority,
116			#[cfg(feature = "smp")]
117			core_id,
118		}
119	}
120
121	#[cfg(feature = "smp")]
122	pub fn get_core_id(&self) -> CoreId {
123		self.core_id
124	}
125
126	pub fn get_id(&self) -> TaskId {
127		self.id
128	}
129
130	pub fn get_priority(&self) -> Priority {
131		self.priority
132	}
133}
134
135impl Ord for TaskHandle {
136	fn cmp(&self, other: &Self) -> cmp::Ordering {
137		self.id.cmp(&other.id)
138	}
139}
140
141impl PartialOrd for TaskHandle {
142	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
143		Some(self.cmp(other))
144	}
145}
146
147impl PartialEq for TaskHandle {
148	fn eq(&self, other: &Self) -> bool {
149		self.id == other.id
150	}
151}
152
153impl Eq for TaskHandle {}
154
155/// Realize a priority queue for task handles
156#[derive(Default)]
157pub(crate) struct TaskHandlePriorityQueue {
158	queues: [Option<VecDeque<TaskHandle>>; NO_PRIORITIES],
159	prio_bitmap: CachePadded<u64>,
160}
161
162impl TaskHandlePriorityQueue {
163	/// Creates an empty priority queue for tasks
164	pub const fn new() -> Self {
165		Self {
166			queues: [const { None }; NO_PRIORITIES],
167			prio_bitmap: CachePadded::new(0),
168		}
169	}
170
171	/// Checks if the queue is empty.
172	pub fn is_empty(&self) -> bool {
173		self.prio_bitmap.into_inner() == 0
174	}
175
176	/// Checks if the given task is in the queue. Returns `true` if the task
177	/// was found.
178	pub fn contains(&self, task: TaskHandle) -> bool {
179		matches!(self.queues[task.priority.into() as usize]
180			.as_ref(), Some(queue) if queue.iter().any(|queued| queued.id == task.id))
181	}
182
183	/// Add a task handle by its priority to the queue
184	pub fn push(&mut self, task: TaskHandle) {
185		let i = task.priority.into() as usize;
186		//assert!(i < NO_PRIORITIES, "Priority {} is too high", i);
187
188		*self.prio_bitmap |= (1 << i) as u64;
189		if let Some(queue) = &mut self.queues[i] {
190			queue.push_back(task);
191		} else {
192			let mut queue = VecDeque::new();
193			queue.push_back(task);
194			self.queues[i] = Some(queue);
195		}
196	}
197
198	fn pop_from_queue(&mut self, queue_index: usize) -> Option<TaskHandle> {
199		let queue = self.queues[queue_index].as_mut()?;
200
201		let task = queue.pop_front();
202
203		if queue.is_empty() {
204			*self.prio_bitmap &= !(1 << queue_index as u64);
205		}
206
207		task
208	}
209
210	/// Pop the task handle with the highest priority from the queue
211	pub fn pop(&mut self) -> Option<TaskHandle> {
212		let i = msb(self.prio_bitmap.into_inner())?;
213
214		self.pop_from_queue(i as usize)
215	}
216
217	/// Remove a specific task handle from the priority queue. Returns `true` if
218	/// the handle was in the queue.
219	pub fn remove(&mut self, task: TaskHandle) -> bool {
220		let queue_index = task.priority.into() as usize;
221		//assert!(queue_index < NO_PRIORITIES, "Priority {} is too high", queue_index);
222
223		let mut success = false;
224		if let Some(queue) = &mut self.queues[queue_index] {
225			let mut i = 0;
226			while i != queue.len() {
227				if queue[i].id == task.id {
228					queue.remove(i);
229					success = true;
230				} else {
231					i += 1;
232				}
233			}
234
235			if queue.is_empty() {
236				*self.prio_bitmap &= !(1 << queue_index as u64);
237			}
238		}
239
240		success
241	}
242}
243
244/// Realize a priority queue for tasks
245pub(crate) struct PriorityTaskQueue {
246	queues: [LinkedList<Rc<RefCell<Task>>>; NO_PRIORITIES],
247	prio_bitmap: u64,
248}
249
250impl PriorityTaskQueue {
251	/// Creates an empty priority queue for tasks
252	pub const fn new() -> PriorityTaskQueue {
253		const EMPTY_LIST: LinkedList<Rc<RefCell<Task>>> = LinkedList::new();
254		PriorityTaskQueue {
255			queues: [EMPTY_LIST; NO_PRIORITIES],
256			prio_bitmap: 0,
257		}
258	}
259
260	/// Add a task by its priority to the queue
261	pub fn push(&mut self, task: Rc<RefCell<Task>>) {
262		let i = task.borrow().prio.into() as usize;
263		//assert!(i < NO_PRIORITIES, "Priority {} is too high", i);
264
265		self.prio_bitmap |= (1 << i) as u64;
266		let queue = &mut self.queues[i];
267		queue.push_back(task);
268	}
269
270	fn pop_from_queue(&mut self, queue_index: usize) -> Option<Rc<RefCell<Task>>> {
271		let task = self.queues[queue_index].pop_front();
272		if self.queues[queue_index].is_empty() {
273			self.prio_bitmap &= !(1 << queue_index as u64);
274		}
275
276		task
277	}
278
279	/// Remove the task at index from the queue and return that task,
280	/// or None if the index is out of range or the list is empty.
281	fn remove_from_queue(
282		&mut self,
283		task_index: usize,
284		queue_index: usize,
285	) -> Option<Rc<RefCell<Task>>> {
286		//assert!(prio < NO_PRIORITIES, "Priority {} is too high", prio);
287
288		let queue = &mut self.queues[queue_index];
289		if queue.len() < task_index {
290			return None;
291		}
292
293		// Calling remove is unstable: https://github.com/rust-lang/rust/issues/69210
294		let mut split_list = queue.split_off(task_index);
295		let element = split_list.pop_front();
296		queue.append(&mut split_list);
297		if queue.is_empty() {
298			self.prio_bitmap &= !(1 << queue_index as u64);
299		}
300		element
301	}
302
303	/// Returns true if the queue is empty.
304	pub fn is_empty(&self) -> bool {
305		self.prio_bitmap == 0
306	}
307
308	/// Returns reference to prio_bitmap
309	#[allow(dead_code)]
310	#[inline]
311	pub fn get_priority_bitmap(&self) -> &u64 {
312		&self.prio_bitmap
313	}
314
315	/// Pop the task with the highest priority from the queue
316	pub fn pop(&mut self) -> Option<Rc<RefCell<Task>>> {
317		let i = msb(self.prio_bitmap)?;
318
319		self.pop_from_queue(i as usize)
320	}
321
322	/// Pop the next task, which has a higher or the same priority as `prio`
323	pub fn pop_with_prio(&mut self, prio: Priority) -> Option<Rc<RefCell<Task>>> {
324		let i = msb(self.prio_bitmap)?;
325
326		if i < u32::from(prio.into()) {
327			return None;
328		}
329
330		self.pop_from_queue(i as usize)
331	}
332
333	/// Returns the highest priority of all available task
334	#[cfg(all(any(target_arch = "x86_64", target_arch = "riscv64"), feature = "smp"))]
335	pub fn get_highest_priority(&self) -> Priority {
336		let Some(i) = msb(self.prio_bitmap) else {
337			return IDLE_PRIO;
338		};
339
340		Priority::from(i.try_into().unwrap())
341	}
342
343	/// Change priority of specific task
344	pub fn set_priority(&mut self, handle: TaskHandle, prio: Priority) -> Result<(), ()> {
345		let old_priority = handle.get_priority().into() as usize;
346		let index = self.queues[old_priority]
347			.iter()
348			.position(|current_task| current_task.borrow().id == handle.id)
349			.ok_or(())?;
350
351		let task = self.remove_from_queue(index, old_priority).ok_or(())?;
352		task.borrow_mut().prio = prio;
353		self.push(task);
354		Ok(())
355	}
356}
357
358/// A task control block, which identifies either a process or a thread
359#[cfg_attr(any(target_arch = "x86_64", target_arch = "aarch64"), repr(align(128)))]
360#[cfg_attr(
361	not(any(target_arch = "x86_64", target_arch = "aarch64")),
362	repr(align(64))
363)]
364pub(crate) struct Task {
365	/// The ID of this context
366	pub id: TaskId,
367	/// Status of a task, e.g. if the task is ready or blocked
368	pub status: TaskStatus,
369	/// Task priority,
370	pub prio: Priority,
371	/// Last stack pointer before a context switch to another task
372	pub last_stack_pointer: VirtAddr,
373	/// Last stack pointer on the user stack before jumping to kernel space
374	pub user_stack_pointer: VirtAddr,
375	/// Last FPU state before a context switch to another task using the FPU
376	pub last_fpu_state: arch::processor::FPUState,
377	/// ID of the core this task is running on
378	pub core_id: CoreId,
379	/// Stack of the task
380	pub stacks: TaskStacks,
381	/// Mapping between file descriptor and the referenced IO interface
382	pub object_map: Arc<RwSpinLock<HashMap<RawFd, Arc<async_lock::RwLock<Fd>>, RandomState>>>,
383	/// Task Thread-Local-Storage (TLS)
384	#[cfg(not(feature = "common-os"))]
385	pub tls: Option<Tls>,
386	// Physical address of the 1st level page table
387	#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
388	pub root_page_table: usize,
389}
390
391pub(crate) trait TaskFrame {
392	/// Create the initial stack frame for a new task
393	fn create_stack_frame(&mut self, func: unsafe extern "C" fn(usize), arg: usize);
394}
395
396impl Task {
397	pub fn new(
398		tid: TaskId,
399		core_id: CoreId,
400		task_status: TaskStatus,
401		task_prio: Priority,
402		stacks: TaskStacks,
403		object_map: Arc<RwSpinLock<HashMap<RawFd, Arc<async_lock::RwLock<Fd>>, RandomState>>>,
404	) -> Task {
405		debug!("Creating new task {tid} on core {core_id}");
406
407		Task {
408			id: tid,
409			status: task_status,
410			prio: task_prio,
411			last_stack_pointer: VirtAddr::zero(),
412			user_stack_pointer: VirtAddr::zero(),
413			last_fpu_state: arch::processor::FPUState::new(),
414			core_id,
415			stacks,
416			object_map,
417			#[cfg(not(feature = "common-os"))]
418			tls: None,
419			#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
420			root_page_table: arch::create_new_root_page_table(),
421		}
422	}
423
424	pub fn new_idle(tid: TaskId, core_id: CoreId) -> Task {
425		debug!("Creating idle task {tid}");
426
427		/// All cores use the same mapping between file descriptor and the referenced object
428		static OBJECT_MAP: OnceCell<
429			Arc<RwSpinLock<HashMap<RawFd, Arc<async_lock::RwLock<Fd>>, RandomState>>>,
430		> = OnceCell::new();
431
432		if core_id == 0 {
433			OBJECT_MAP
434				.set(Arc::new(RwSpinLock::new(HashMap::<
435					RawFd,
436					Arc<async_lock::RwLock<Fd>>,
437					RandomState,
438				>::with_hasher(
439					RandomState::with_seeds(0, 0, 0, 0),
440				))))
441				// This function is called once per core and thus only once on core 0.
442				// Thus, this is the only place where we set OBJECT_MAP.
443				.unwrap_or_else(|_| unreachable!());
444			let objmap = OBJECT_MAP.get().unwrap().clone();
445			stdio::setup(&mut objmap.write());
446		}
447
448		#[cfg(not(feature = "common-os"))]
449		let tls = if cfg!(feature = "instrument-mcount") {
450			Tls::from_env().inspect(Tls::set_thread_ptr)
451		} else {
452			None
453		};
454
455		Task {
456			id: tid,
457			status: TaskStatus::Idle,
458			prio: IDLE_PRIO,
459			last_stack_pointer: VirtAddr::zero(),
460			user_stack_pointer: VirtAddr::zero(),
461			last_fpu_state: arch::processor::FPUState::new(),
462			core_id,
463			stacks: TaskStacks::from_boot_stacks(),
464			object_map: OBJECT_MAP.get().unwrap().clone(),
465			#[cfg(not(feature = "common-os"))]
466			tls,
467			#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
468			root_page_table: *crate::scheduler::BOOT_ROOT_PAGE_TABLE.get().unwrap(),
469		}
470	}
471}
472
473/*impl Drop for Task {
474	fn drop(&mut self) {
475		debug!("Drop task {}", self.id);
476	}
477}*/
478
479struct BlockedTask {
480	task: Rc<RefCell<Task>>,
481	wakeup_time: Option<u64>,
482}
483
484impl BlockedTask {
485	pub fn new(task: Rc<RefCell<Task>>, wakeup_time: Option<u64>) -> Self {
486		Self { task, wakeup_time }
487	}
488}
489
490pub(crate) struct BlockedTaskQueue {
491	list: LinkedList<BlockedTask>,
492}
493
494impl BlockedTaskQueue {
495	pub const fn new() -> Self {
496		Self {
497			list: LinkedList::new(),
498		}
499	}
500
501	fn mark_ready(task: &RefCell<Task>) {
502		let mut borrowed = task.borrow_mut();
503		debug!(
504			"Waking up task {} on core {}",
505			borrowed.id, borrowed.core_id
506		);
507
508		assert!(
509			borrowed.core_id == core_id(),
510			"Try to wake up task {} on the wrong core {} != {}",
511			borrowed.id,
512			borrowed.core_id,
513			core_id()
514		);
515
516		assert!(
517			borrowed.status == TaskStatus::Blocked,
518			"Trying to wake up task {} which is not blocked",
519			borrowed.id
520		);
521		borrowed.status = TaskStatus::Ready;
522	}
523
524	/// Blocks the given task for `wakeup_time` ticks, or indefinitely if None is given.
525	pub fn add(&mut self, task: Rc<RefCell<Task>>, wakeup_time: Option<u64>) {
526		{
527			// Set the task status to Blocked.
528			let mut borrowed = task.borrow_mut();
529			debug!("Blocking task {}", borrowed.id);
530
531			assert_eq!(
532				borrowed.status,
533				TaskStatus::Running,
534				"Trying to block task {} which is not running",
535				borrowed.id
536			);
537			borrowed.status = TaskStatus::Blocked;
538		}
539
540		let new_node = BlockedTask::new(task, wakeup_time);
541
542		// Shall the task automatically be woken up after a certain time?
543		if let Some(wt) = wakeup_time {
544			let mut cursor = self.list.cursor_front_mut();
545
546			while let Some(node) = cursor.current() {
547				let node_wakeup_time = node.wakeup_time;
548				if node_wakeup_time.is_none() || wt < node_wakeup_time.unwrap() {
549					cursor.insert_before(new_node);
550
551					create_timer_abs(Source::Scheduler, wt);
552					return;
553				}
554
555				cursor.move_next();
556			}
557
558			create_timer_abs(Source::Scheduler, wt);
559		}
560
561		self.list.push_back(new_node);
562	}
563
564	/// Manually wake up a blocked task.
565	pub fn custom_wakeup(&mut self, task: TaskHandle) -> Rc<RefCell<Task>> {
566		let mut first_task = true;
567		let mut cursor = self.list.cursor_front_mut();
568
569		// Loop through all blocked tasks to find it.
570		while let Some(node) = cursor.current() {
571			if node.task.borrow().id == task.get_id() {
572				// Remove it from the list of blocked tasks.
573				let task_ref = node.task.clone();
574				cursor.remove_current();
575
576				// If this is the first task, adjust the One-Shot Timer to fire at the
577				// next task's wakeup time (if any).
578				if first_task
579					&& let Some(wakeup) = cursor
580						.current()
581						.map_or_else(|| None, |node| node.wakeup_time)
582				{
583					create_timer_abs(Source::Scheduler, wakeup);
584				}
585
586				// Wake it up.
587				Self::mark_ready(&task_ref);
588
589				return task_ref;
590			}
591
592			first_task = false;
593			cursor.move_next();
594		}
595
596		unreachable!();
597	}
598
599	/// Wakes up all tasks whose wakeup time has elapsed.
600	///
601	/// Should be called by the One-Shot Timer interrupt handler when the wakeup time for
602	/// at least one task has elapsed.
603	pub fn handle_waiting_tasks(&mut self, ready_queue: &mut PriorityTaskQueue) {
604		// Get the current time.
605		let time = arch::processor::get_timer_ticks();
606
607		// Get the wakeup time of this task and check if we have reached the first task
608		// that hasn't elapsed yet or waits indefinitely.
609		// This iterator has to be consumed to actually remove the elements.
610		let newly_ready_tasks = self.list.extract_if(|blocked_task| {
611			blocked_task
612				.wakeup_time
613				.is_some_and(|wakeup_time| wakeup_time < time)
614		});
615
616		for task in newly_ready_tasks {
617			Self::mark_ready(&task.task);
618			ready_queue.push(task.task);
619		}
620
621		let new_task_wakeup_time = self.list.front().and_then(|task| task.wakeup_time);
622
623		if let Some(wakeup) = new_task_wakeup_time {
624			create_timer_abs(Source::Scheduler, wakeup);
625		}
626	}
627}