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