Skip to main content

hermit/scheduler/
mod.rs

1#![allow(clippy::type_complexity)]
2
3use alloc::boxed::Box;
4use alloc::collections::{BTreeMap, VecDeque};
5use alloc::rc::Rc;
6use alloc::sync::Arc;
7#[cfg(feature = "smp")]
8use alloc::vec::Vec;
9use core::cell::RefCell;
10use core::ptr;
11use core::sync::atomic::{AtomicI32, AtomicU32, Ordering};
12
13use ahash::RandomState;
14use crossbeam_utils::Backoff;
15use hashbrown::{HashMap, hash_map};
16use hermit_sync::*;
17#[cfg(target_arch = "riscv64")]
18use riscv::register::sstatus;
19use timer_interrupts::TimerList;
20
21use crate::arch::kernel;
22use crate::arch::kernel::core_local::*;
23#[cfg(feature = "preemptive")]
24use crate::arch::kernel::processor;
25use crate::arch::kernel::scheduler::TaskStacks;
26#[cfg(target_arch = "riscv64")]
27use crate::arch::kernel::switch::switch_to_task;
28#[cfg(target_arch = "x86_64")]
29use crate::arch::kernel::switch::{switch_to_fpu_owner, switch_to_task};
30use crate::arch::kernel::{get_processor_count, interrupts};
31use crate::errno::Errno;
32use crate::fd::{Fd, RawFd};
33use crate::io;
34use crate::scheduler::task::*;
35
36#[cfg(all(
37	any(target_arch = "x86_64", target_arch = "riscv64"),
38	feature = "smp",
39	not(feature = "idle-poll")
40))]
41pub mod sleep_state;
42pub mod task;
43pub mod timer_interrupts;
44
45static NO_TASKS: AtomicU32 = AtomicU32::new(0);
46/// Length of a preemptive scheduling time slice, in microseconds (10 ms). With
47/// rhyve's external-interrupt exiting this also bounds the guest VM-exit rate
48/// during contention (one exit per slice).
49#[cfg(feature = "preemptive")]
50const PREEMPTION_SLICE_US: u64 = 10_000;
51/// Map between Core ID and per-core scheduler
52#[cfg(feature = "smp")]
53static SCHEDULER_INPUTS: SpinMutex<Vec<&InterruptTicketMutex<SchedulerInput>>> =
54	SpinMutex::new(Vec::new());
55/// Map between Task ID and Queue of waiting tasks
56static WAITING_TASKS: InterruptTicketMutex<BTreeMap<TaskId, VecDeque<TaskHandle>>> =
57	InterruptTicketMutex::new(BTreeMap::new());
58/// Map between Task ID and TaskHandle
59static TASKS: InterruptTicketMutex<BTreeMap<TaskId, TaskHandle>> =
60	InterruptTicketMutex::new(BTreeMap::new());
61
62/// Unique identifier for a core.
63pub type CoreId = u32;
64
65#[cfg(feature = "smp")]
66pub(crate) struct SchedulerInput {
67	/// Queue of new tasks
68	new_tasks: VecDeque<NewTask>,
69	/// Queue of task, which are wakeup by another core
70	wakeup_tasks: VecDeque<TaskHandle>,
71}
72
73#[cfg(feature = "smp")]
74impl SchedulerInput {
75	pub fn new() -> Self {
76		Self {
77			new_tasks: VecDeque::new(),
78			wakeup_tasks: VecDeque::new(),
79		}
80	}
81}
82
83#[cfg_attr(any(target_arch = "x86_64", target_arch = "aarch64"), repr(align(128)))]
84#[cfg_attr(
85	not(any(target_arch = "x86_64", target_arch = "aarch64")),
86	repr(align(64))
87)]
88pub(crate) struct PerCoreScheduler {
89	/// Core ID of this per-core scheduler
90	#[cfg(feature = "smp")]
91	core_id: CoreId,
92	/// Task which is currently running
93	current_task: Rc<RefCell<Task>>,
94	/// Idle Task
95	idle_task: Rc<RefCell<Task>>,
96	/// Task that currently owns the FPU
97	#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
98	fpu_owner: Rc<RefCell<Task>>,
99	/// Queue of tasks, which are ready
100	ready_queue: PriorityTaskQueue,
101	/// Queue of tasks, which are finished and can be released
102	finished_tasks: VecDeque<Rc<RefCell<Task>>>,
103	/// Queue of blocked tasks, sorted by wakeup time.
104	blocked_tasks: BlockedTaskQueue,
105	/// Queue of timer interrupts.
106	pub timers: TimerList,
107}
108
109pub(crate) trait PerCoreSchedulerExt {
110	/// Triggers the scheduler to reschedule the tasks.
111	/// Interrupt flag will be cleared during the reschedule
112	fn reschedule(self);
113
114	/// Terminate the current task on the current core.
115	fn exit(self, exit_code: i32) -> !;
116}
117
118impl PerCoreSchedulerExt for &mut PerCoreScheduler {
119	#[cfg(target_arch = "x86_64")]
120	fn reschedule(self) {
121		without_interrupts(|| {
122			let Some(last_stack_pointer) = self.scheduler() else {
123				return;
124			};
125
126			let (new_stack_pointer, is_idle) = {
127				let borrowed = self.current_task.borrow();
128				(
129					borrowed.last_stack_pointer,
130					borrowed.status == TaskStatus::Idle,
131				)
132			};
133
134			if is_idle || Rc::ptr_eq(&self.current_task, &self.fpu_owner) {
135				unsafe {
136					switch_to_fpu_owner(last_stack_pointer, new_stack_pointer.as_u64() as usize);
137				}
138			} else {
139				unsafe {
140					switch_to_task(last_stack_pointer, new_stack_pointer.as_u64() as usize);
141				}
142			}
143		});
144	}
145
146	/// Trigger an interrupt to reschedule the system
147	#[cfg(target_arch = "aarch64")]
148	fn reschedule(self) {
149		use aarch64_cpu::asm::barrier::{NSH, SY, dsb, isb};
150		use arm_gic::IntId;
151		use arm_gic::gicv3::{GicCpuInterface, SgiTarget, SgiTargetGroup};
152
153		use crate::arch::kernel::interrupts::SGI_RESCHED;
154
155		dsb(NSH);
156		isb(SY);
157
158		let reschedid = IntId::sgi(SGI_RESCHED.into());
159		#[cfg(feature = "smp")]
160		let core_id = self.core_id;
161		#[cfg(not(feature = "smp"))]
162		let core_id = 0;
163
164		GicCpuInterface::send_sgi(
165			reschedid,
166			SgiTarget::List {
167				affinity3: 0,
168				affinity2: 0,
169				affinity1: 0,
170				target_list: 1 << core_id,
171			},
172			SgiTargetGroup::CurrentGroup1,
173		)
174		.unwrap();
175
176		interrupts::enable();
177	}
178
179	#[cfg(target_arch = "riscv64")]
180	fn reschedule(self) {
181		without_interrupts(|| self.scheduler());
182	}
183
184	fn exit(self, exit_code: i32) -> ! {
185		without_interrupts(|| {
186			// Get the current task.
187			let mut current_task_borrowed = self.current_task.borrow_mut();
188			assert_ne!(
189				current_task_borrowed.status,
190				TaskStatus::Idle,
191				"Trying to terminate the idle task"
192			);
193
194			// Finish the task and reschedule.
195			debug!(
196				"Finishing task {} with exit code {}",
197				current_task_borrowed.id, exit_code
198			);
199			current_task_borrowed.status = TaskStatus::Finished;
200			NO_TASKS.fetch_sub(1, Ordering::SeqCst);
201
202			let current_id = current_task_borrowed.id;
203			drop(current_task_borrowed);
204
205			// wakeup tasks, which are waiting for task with the identifier id
206			if let Some(mut queue) = WAITING_TASKS.lock().remove(&current_id) {
207				while let Some(task) = queue.pop_front() {
208					self.custom_wakeup(task);
209				}
210			}
211
212			TASKS.lock().remove(&current_id);
213		});
214
215		self.reschedule();
216		unreachable!()
217	}
218}
219
220struct NewTask {
221	tid: TaskId,
222	func: unsafe extern "C" fn(usize),
223	arg: usize,
224	prio: Priority,
225	core_id: CoreId,
226	stacks: TaskStacks,
227	object_map: Arc<RwSpinLock<HashMap<RawFd, Arc<async_lock::RwLock<Fd>>, RandomState>>>,
228}
229
230impl From<NewTask> for Task {
231	fn from(value: NewTask) -> Self {
232		let NewTask {
233			tid,
234			func,
235			arg,
236			prio,
237			core_id,
238			stacks,
239			object_map,
240		} = value;
241		let mut task = Self::new(tid, core_id, TaskStatus::Ready, prio, stacks, object_map);
242		task.create_stack_frame(func, arg);
243		task
244	}
245}
246
247impl PerCoreScheduler {
248	/// Spawn a new task.
249	pub unsafe fn spawn(
250		func: unsafe extern "C" fn(usize),
251		arg: usize,
252		prio: Priority,
253		core_id: CoreId,
254		stack_size: usize,
255	) -> TaskId {
256		// Create the new task.
257		let tid = get_tid();
258		let stacks = TaskStacks::new(stack_size);
259		let new_task = NewTask {
260			tid,
261			func,
262			arg,
263			prio,
264			core_id,
265			stacks,
266			object_map: core_scheduler().get_current_task_object_map(),
267		};
268
269		// Add it to the task lists.
270		let wakeup = {
271			#[cfg(feature = "smp")]
272			let mut input_locked = get_scheduler_input(core_id).lock();
273			WAITING_TASKS.lock().insert(tid, VecDeque::with_capacity(1));
274			TASKS.lock().insert(
275				tid,
276				TaskHandle::new(
277					tid,
278					prio,
279					#[cfg(feature = "smp")]
280					core_id,
281				),
282			);
283			NO_TASKS.fetch_add(1, Ordering::SeqCst);
284
285			#[cfg(feature = "smp")]
286			if core_id == core_scheduler().core_id {
287				let task = Rc::new(RefCell::new(Task::from(new_task)));
288				core_scheduler().ready_queue.push(task);
289				false
290			} else {
291				input_locked.new_tasks.push_back(new_task);
292				true
293			}
294			#[cfg(not(feature = "smp"))]
295			if core_id == 0 {
296				let task = Rc::new(RefCell::new(Task::from(new_task)));
297				core_scheduler().ready_queue.push(task);
298				false
299			} else {
300				panic!("Invalid core_id {core_id}!")
301			}
302		};
303
304		debug!("Creating task {tid} with priority {prio} on core {core_id}");
305
306		if wakeup {
307			kernel::wakeup_core(core_id);
308		}
309
310		tid
311	}
312
313	#[cfg(feature = "newlib")]
314	fn clone_impl(&self, func: extern "C" fn(usize), arg: usize) -> TaskId {
315		static NEXT_CORE_ID: AtomicU32 = AtomicU32::new(1);
316
317		// Get the Core ID of the next CPU.
318		let core_id: CoreId = {
319			// Increase the CPU number by 1.
320			let id = NEXT_CORE_ID.fetch_add(1, Ordering::SeqCst);
321
322			// Check for overflow.
323			if id == get_processor_count() {
324				NEXT_CORE_ID.store(0, Ordering::SeqCst);
325				0
326			} else {
327				id
328			}
329		};
330
331		// Get the current task.
332		let current_task_borrowed = self.current_task.borrow();
333
334		// Clone the current task.
335		let tid = get_tid();
336		let clone_task = NewTask {
337			tid,
338			func,
339			arg,
340			prio: current_task_borrowed.prio,
341			core_id,
342			stacks: TaskStacks::new(current_task_borrowed.stacks.get_user_stack_size()),
343			object_map: current_task_borrowed.object_map.clone(),
344		};
345
346		// Add it to the task lists.
347		let wakeup = {
348			#[cfg(feature = "smp")]
349			let mut input_locked = get_scheduler_input(core_id).lock();
350			WAITING_TASKS.lock().insert(tid, VecDeque::with_capacity(1));
351			TASKS.lock().insert(
352				tid,
353				TaskHandle::new(
354					tid,
355					current_task_borrowed.prio,
356					#[cfg(feature = "smp")]
357					core_id,
358				),
359			);
360			NO_TASKS.fetch_add(1, Ordering::SeqCst);
361			#[cfg(feature = "smp")]
362			if core_id == core_scheduler().core_id {
363				let clone_task = Rc::new(RefCell::new(Task::from(clone_task)));
364				core_scheduler().ready_queue.push(clone_task);
365				false
366			} else {
367				input_locked.new_tasks.push_back(clone_task);
368				true
369			}
370			#[cfg(not(feature = "smp"))]
371			if core_id == 0 {
372				let clone_task = Rc::new(RefCell::new(Task::from(clone_task)));
373				core_scheduler().ready_queue.push(clone_task);
374				false
375			} else {
376				panic!("Invalid core_id {core_id}!");
377			}
378		};
379
380		// Wake up the CPU
381		if wakeup {
382			kernel::wakeup_core(core_id);
383		}
384
385		tid
386	}
387
388	#[cfg(feature = "newlib")]
389	pub fn clone(&self, func: extern "C" fn(usize), arg: usize) -> TaskId {
390		without_interrupts(|| self.clone_impl(func, arg))
391	}
392
393	/// Returns `true` if a reschedule is required
394	#[inline]
395	#[cfg(all(any(target_arch = "x86_64", target_arch = "riscv64"), feature = "smp"))]
396	pub fn is_scheduling(&self) -> bool {
397		self.current_task.borrow().prio < self.ready_queue.get_highest_priority()
398	}
399
400	#[inline]
401	pub fn handle_waiting_tasks(&mut self) {
402		without_interrupts(|| {
403			crate::executor::run();
404			self.blocked_tasks
405				.handle_waiting_tasks(&mut self.ready_queue);
406		});
407	}
408
409	#[cfg(not(feature = "smp"))]
410	pub fn custom_wakeup(&mut self, task: TaskHandle) {
411		without_interrupts(|| {
412			let task = self.blocked_tasks.custom_wakeup(task);
413			self.ready_queue.push(task);
414			// The woken task is now ready. Arm the preemption timer so the running
415			// task — which may be CPU-bound and never yield — is preempted soon and
416			// the newcomer gets to run.
417			#[cfg(feature = "preemptive")]
418			{
419				let deadline = processor::get_timer_ticks() + PREEMPTION_SLICE_US;
420				timer_interrupts::create_timer_abs(timer_interrupts::Source::Preemption, deadline);
421			}
422		});
423	}
424
425	#[cfg(feature = "smp")]
426	pub fn custom_wakeup(&mut self, task: TaskHandle) {
427		if task.get_core_id() == self.core_id {
428			without_interrupts(|| {
429				let task = self.blocked_tasks.custom_wakeup(task);
430				self.ready_queue.push(task);
431				// The woken task is now ready on this core. Arm the preemption timer
432				// so the running (possibly non-yielding) task is preempted soon.
433				#[cfg(feature = "preemptive")]
434				{
435					let deadline = processor::get_timer_ticks() + PREEMPTION_SLICE_US;
436					timer_interrupts::create_timer_abs(
437						timer_interrupts::Source::Preemption,
438						deadline,
439					);
440				}
441			});
442		} else {
443			get_scheduler_input(task.get_core_id())
444				.lock()
445				.wakeup_tasks
446				.push_back(task);
447			// Wake up the CPU
448			kernel::wakeup_core(task.get_core_id());
449		}
450	}
451
452	#[inline]
453	pub fn block_current_task(&mut self, wakeup_time: Option<u64>) {
454		without_interrupts(|| {
455			self.blocked_tasks
456				.add(self.current_task.clone(), wakeup_time);
457		});
458	}
459
460	#[inline]
461	pub fn get_current_task_handle(&self) -> TaskHandle {
462		without_interrupts(|| {
463			let current_task_borrowed = self.current_task.borrow();
464
465			TaskHandle::new(
466				current_task_borrowed.id,
467				current_task_borrowed.prio,
468				#[cfg(feature = "smp")]
469				current_task_borrowed.core_id,
470			)
471		})
472	}
473
474	#[inline]
475	pub fn get_current_task_id(&self) -> TaskId {
476		without_interrupts(|| self.current_task.borrow().id)
477	}
478
479	#[inline]
480	pub fn get_current_task_object_map(
481		&self,
482	) -> Arc<RwSpinLock<HashMap<RawFd, Arc<async_lock::RwLock<Fd>>, RandomState>>> {
483		without_interrupts(|| self.current_task.borrow().object_map.clone())
484	}
485
486	/// Map a file descriptor to their IO interface and returns
487	/// the shared reference
488	#[inline]
489	pub fn get_object(&self, fd: RawFd) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
490		without_interrupts(|| {
491			let current_task = self.current_task.borrow();
492			let object_map = current_task.object_map.read();
493			object_map.get(&fd).cloned().ok_or(Errno::Badf)
494		})
495	}
496
497	/// Creates a new map between file descriptor and their IO interface and
498	/// clone the standard descriptors.
499	#[cfg(feature = "common-os")]
500	#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
501	pub fn recreate_objmap(&self) -> io::Result<()> {
502		let mut map = HashMap::<RawFd, Arc<async_lock::RwLock<Fd>>, RandomState>::with_hasher(
503			RandomState::with_seeds(0, 0, 0, 0),
504		);
505
506		without_interrupts(|| {
507			let mut current_task = self.current_task.borrow_mut();
508			let object_map = current_task.object_map.read();
509
510			// clone standard file descriptors
511			for i in 0..3 {
512				if let Some(obj) = object_map.get(&i) {
513					map.insert(i, obj.clone());
514				}
515			}
516
517			drop(object_map);
518			current_task.object_map = Arc::new(RwSpinLock::new(map));
519		});
520
521		Ok(())
522	}
523
524	/// Insert a new IO interface and returns a file descriptor as
525	/// identifier to this object
526	pub fn insert_object(&self, obj: Arc<async_lock::RwLock<Fd>>) -> io::Result<RawFd> {
527		without_interrupts(|| {
528			let current_task = self.current_task.borrow();
529			let mut object_map = current_task.object_map.write();
530
531			let new_fd = || -> io::Result<RawFd> {
532				let mut fd: RawFd = 0;
533				loop {
534					if !object_map.contains_key(&fd) {
535						break Ok(fd);
536					} else if fd == RawFd::MAX {
537						break Err(Errno::Overflow);
538					}
539
540					fd = fd.saturating_add(1);
541				}
542			};
543
544			let fd = new_fd()?;
545			object_map.insert(fd, obj.clone());
546			Ok(fd)
547		})
548	}
549
550	/// Duplicate a IO interface and returns a new file descriptor as
551	/// identifier to the new copy
552	pub fn dup_object(&self, fd: RawFd) -> io::Result<RawFd> {
553		without_interrupts(|| {
554			let current_task = self.current_task.borrow();
555			let mut object_map = current_task.object_map.write();
556
557			let obj = (*(object_map.get(&fd).ok_or(Errno::Inval)?)).clone();
558
559			let new_fd = || -> io::Result<RawFd> {
560				let mut fd: RawFd = 0;
561				loop {
562					if !object_map.contains_key(&fd) {
563						break Ok(fd);
564					} else if fd == RawFd::MAX {
565						break Err(Errno::Overflow);
566					}
567
568					fd = fd.saturating_add(1);
569				}
570			};
571
572			let fd = new_fd()?;
573			match object_map.entry(fd) {
574				hash_map::Entry::Occupied(_occupied_entry) => Err(Errno::Mfile),
575				hash_map::Entry::Vacant(vacant_entry) => {
576					vacant_entry.insert(obj);
577					Ok(fd)
578				}
579			}
580		})
581	}
582
583	pub fn dup_object2(&self, fd1: RawFd, fd2: RawFd) -> io::Result<RawFd> {
584		without_interrupts(|| {
585			let current_task = self.current_task.borrow();
586			let mut object_map = current_task.object_map.write();
587
588			let obj = object_map.get(&fd1).cloned().ok_or(Errno::Badf)?;
589
590			if object_map.insert(fd2, obj).is_some() {
591				debug!("dup2({fd1}, {fd2}): replaced previously open fd {fd2}");
592			}
593			Ok(fd2)
594		})
595	}
596
597	/// Remove a IO interface, which is named by the file descriptor
598	pub fn remove_object(&self, fd: RawFd) -> io::Result<Arc<async_lock::RwLock<Fd>>> {
599		without_interrupts(|| {
600			let current_task = self.current_task.borrow();
601			let mut object_map = current_task.object_map.write();
602
603			object_map.remove(&fd).ok_or(Errno::Badf)
604		})
605	}
606
607	#[inline]
608	pub fn get_current_task_prio(&self) -> Priority {
609		without_interrupts(|| self.current_task.borrow().prio)
610	}
611
612	/// Returns reference to prio_bitmap
613	#[allow(dead_code)]
614	#[inline]
615	pub fn get_priority_bitmap(&self) -> &u64 {
616		self.ready_queue.get_priority_bitmap()
617	}
618
619	#[cfg(target_arch = "x86_64")]
620	pub fn set_current_kernel_stack(&self) {
621		let current_task_borrowed = self.current_task.borrow();
622		let tss = unsafe { &mut *CoreLocal::get().tss.get() };
623
624		let rsp = current_task_borrowed.stacks.get_kernel_stack()
625			+ current_task_borrowed.stacks.get_kernel_stack_size() as u64
626			- TaskStacks::MARKER_SIZE as u64;
627		tss.privilege_stack_table[0] = rsp.into();
628		CoreLocal::get().kernel_stack.set(rsp.as_mut_ptr());
629		let ist_start = current_task_borrowed.stacks.get_interrupt_stack()
630			+ current_task_borrowed.stacks.get_interrupt_stack_size() as u64
631			- TaskStacks::MARKER_SIZE as u64;
632		tss.interrupt_stack_table[0] = ist_start.into();
633	}
634
635	pub fn set_current_task_priority(&mut self, prio: Priority) {
636		without_interrupts(|| {
637			trace!("Change priority of the current task");
638			self.current_task.borrow_mut().prio = prio;
639		});
640	}
641
642	pub fn set_priority(&mut self, id: TaskId, prio: Priority) -> Result<(), ()> {
643		trace!("Change priority of task {id} to priority {prio}");
644
645		without_interrupts(|| {
646			let task = get_task_handle(id).ok_or(())?;
647			#[cfg(feature = "smp")]
648			let other_core = task.get_core_id() != self.core_id;
649			#[cfg(not(feature = "smp"))]
650			let other_core = false;
651
652			if other_core {
653				warn!("Have to change the priority on another core");
654			} else if self.current_task.borrow().id == task.get_id() {
655				self.current_task.borrow_mut().prio = prio;
656			} else {
657				self.ready_queue
658					.set_priority(task, prio)
659					.expect("Do not find valid task in ready queue");
660			}
661
662			Ok(())
663		})
664	}
665
666	#[cfg(target_arch = "riscv64")]
667	pub fn set_current_kernel_stack(&self) {
668		let current_task_borrowed = self.current_task.borrow();
669
670		let stack = (current_task_borrowed.stacks.get_kernel_stack()
671			+ current_task_borrowed.stacks.get_kernel_stack_size() as u64
672			- TaskStacks::MARKER_SIZE as u64)
673			.as_u64();
674		CoreLocal::get().kernel_stack.set(stack);
675	}
676
677	/// Save the FPU context for the current FPU owner and restore it for the current task,
678	/// which wants to use the FPU now.
679	#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
680	pub fn fpu_switch(&mut self) {
681		if !Rc::ptr_eq(&self.current_task, &self.fpu_owner) {
682			debug!(
683				"Switching FPU owner from task {} to {}",
684				self.fpu_owner.borrow().id,
685				self.current_task.borrow().id
686			);
687
688			self.fpu_owner.borrow_mut().last_fpu_state.save();
689			self.current_task.borrow().last_fpu_state.restore();
690			self.fpu_owner = self.current_task.clone();
691		}
692	}
693
694	/// Check if a finished task could be deleted.
695	fn cleanup_tasks(&mut self) {
696		// Pop the first finished task and remove it from the TASKS list, which implicitly deallocates all associated memory.
697		while let Some(finished_task) = self.finished_tasks.pop_front() {
698			debug!("Cleaning up task {}", finished_task.borrow().id);
699		}
700	}
701
702	#[cfg(feature = "smp")]
703	pub fn check_input(&mut self) {
704		let mut input_locked = CoreLocal::get().scheduler_input.lock();
705
706		while let Some(task) = input_locked.wakeup_tasks.pop_front() {
707			let task = self.blocked_tasks.custom_wakeup(task);
708			self.ready_queue.push(task);
709		}
710
711		while let Some(new_task) = input_locked.new_tasks.pop_front() {
712			let task = Rc::new(RefCell::new(Task::from(new_task)));
713			self.ready_queue.push(task.clone());
714		}
715	}
716
717	/// Only the idle task should call this function.
718	/// Set the idle task to halt state if not another
719	/// available.
720	pub fn run() -> ! {
721		let backoff = Backoff::new();
722
723		loop {
724			let core_scheduler = core_scheduler();
725			interrupts::disable();
726
727			// run async tasks
728			crate::executor::run();
729
730			// do housekeeping
731			#[cfg(feature = "smp")]
732			core_scheduler.check_input();
733			core_scheduler.cleanup_tasks();
734
735			if core_scheduler.ready_queue.is_empty() {
736				if backoff.is_completed() {
737					interrupts::enable_and_wait();
738					backoff.reset();
739				} else {
740					interrupts::enable();
741					backoff.snooze();
742				}
743			} else {
744				interrupts::enable();
745				core_scheduler.reschedule();
746				backoff.reset();
747			}
748		}
749	}
750
751	#[inline]
752	#[cfg(target_arch = "aarch64")]
753	pub fn get_last_stack_pointer(&self) -> memory_addresses::VirtAddr {
754		self.current_task.borrow().last_stack_pointer
755	}
756
757	/// Triggers the scheduler to reschedule the tasks.
758	/// Interrupt flag must be cleared before calling this function.
759	pub fn scheduler(&mut self) -> Option<*mut usize> {
760		// run background tasks
761		crate::executor::run();
762
763		// Someone wants to give up the CPU
764		// => we have time to cleanup the system
765		self.cleanup_tasks();
766
767		// Get information about the current task.
768		let (id, last_stack_pointer, prio, status) = {
769			let mut borrowed = self.current_task.borrow_mut();
770			(
771				borrowed.id,
772				ptr::from_mut(&mut borrowed.last_stack_pointer).cast::<usize>(),
773				borrowed.prio,
774				borrowed.status,
775			)
776		};
777
778		let mut new_task = None;
779
780		if status == TaskStatus::Running {
781			// A task is currently running.
782			// Check if a task with a equal or higher priority is available.
783			if let Some(task) = self.ready_queue.pop_with_prio(prio) {
784				new_task = Some(task);
785			}
786		} else {
787			if status == TaskStatus::Finished {
788				// Mark the finished task as invalid and add it to the finished tasks for a later cleanup.
789				self.current_task.borrow_mut().status = TaskStatus::Invalid;
790				self.finished_tasks.push_back(self.current_task.clone());
791			}
792
793			// No task is currently running.
794			// Check if there is any available task and get the one with the highest priority.
795			if let Some(task) = self.ready_queue.pop() {
796				// This available task becomes the new task.
797				debug!("Task is available.");
798				new_task = Some(task);
799			} else if status != TaskStatus::Idle {
800				// The Idle task becomes the new task.
801				debug!("Only Idle Task is available.");
802				new_task = Some(self.idle_task.clone());
803			}
804		}
805
806		let task = new_task?;
807		// There is a new task we want to switch to.
808
809		// Handle the current task.
810		if status == TaskStatus::Running {
811			// Mark the running task as ready again and add it back to the queue.
812			self.current_task.borrow_mut().status = TaskStatus::Ready;
813			self.ready_queue.push(self.current_task.clone());
814		}
815
816		// Handle the new task and get information about it.
817		let (new_id, new_stack_pointer) = {
818			let mut borrowed = task.borrow_mut();
819			if borrowed.status != TaskStatus::Idle {
820				// Mark the new task as running.
821				borrowed.status = TaskStatus::Running;
822			}
823
824			(borrowed.id, borrowed.last_stack_pointer)
825		};
826
827		if id == new_id {
828			return None;
829		}
830
831		// Preemptive round-robin: arm a one-shot time-slice timer for the task we
832		// are about to run, but only while other tasks are ready — with no
833		// contention the system stays tickless. When the slice expires, the timer
834		// interrupt's `reschedule()` switches to the next ready task.
835		#[cfg(feature = "preemptive")]
836		if !self.ready_queue.is_empty() {
837			let deadline = processor::get_timer_ticks() + PREEMPTION_SLICE_US;
838			timer_interrupts::create_timer_abs(timer_interrupts::Source::Preemption, deadline);
839		}
840
841		// Tell the scheduler about the new task.
842		debug!(
843			"Switching task from {} to {} (stack {:#X} => {:p})",
844			id,
845			new_id,
846			unsafe { *last_stack_pointer },
847			new_stack_pointer
848		);
849		#[cfg(not(target_arch = "riscv64"))]
850		{
851			self.current_task = task;
852		}
853
854		// Finally return the context of the new task.
855		#[cfg(not(target_arch = "riscv64"))]
856		return Some(last_stack_pointer);
857
858		#[cfg(target_arch = "riscv64")]
859		{
860			if sstatus::read().fs() == sstatus::FS::Dirty {
861				self.current_task.borrow_mut().last_fpu_state.save();
862			}
863			task.borrow().last_fpu_state.restore();
864			self.current_task = task;
865			unsafe {
866				switch_to_task(last_stack_pointer, new_stack_pointer.as_usize());
867			}
868			None
869		}
870	}
871}
872
873fn get_tid() -> TaskId {
874	static TID_COUNTER: AtomicI32 = AtomicI32::new(0);
875	let guard = TASKS.lock();
876
877	loop {
878		let id = TaskId::from(TID_COUNTER.fetch_add(1, Ordering::SeqCst));
879		if !guard.contains_key(&id) {
880			return id;
881		}
882	}
883}
884
885#[inline]
886pub(crate) fn abort() -> ! {
887	core_scheduler().exit(-1)
888}
889
890/// Add a per-core scheduler for the current core.
891pub(crate) fn add_current_core() {
892	// Create an idle task for this core.
893	let core_id = core_id();
894	let tid = get_tid();
895	let idle_task = Rc::new(RefCell::new(Task::new_idle(tid, core_id)));
896
897	// Add the ID -> Task mapping.
898	WAITING_TASKS.lock().insert(tid, VecDeque::with_capacity(1));
899	TASKS.lock().insert(
900		tid,
901		TaskHandle::new(
902			tid,
903			IDLE_PRIO,
904			#[cfg(feature = "smp")]
905			core_id,
906		),
907	);
908	// Initialize a scheduler for this core.
909	debug!("Initializing scheduler for core {core_id} with idle task {tid}");
910	let boxed_scheduler = Box::new(PerCoreScheduler {
911		#[cfg(feature = "smp")]
912		core_id,
913		current_task: idle_task.clone(),
914		#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
915		fpu_owner: idle_task.clone(),
916		idle_task,
917		ready_queue: PriorityTaskQueue::new(),
918		finished_tasks: VecDeque::new(),
919		blocked_tasks: BlockedTaskQueue::new(),
920		timers: TimerList::new(),
921	});
922
923	let scheduler = Box::into_raw(boxed_scheduler);
924	set_core_scheduler(scheduler);
925	#[cfg(feature = "smp")]
926	{
927		SCHEDULER_INPUTS.lock().insert(
928			core_id.try_into().unwrap(),
929			&CoreLocal::get().scheduler_input,
930		);
931		#[cfg(all(
932			any(target_arch = "x86_64", target_arch = "riscv64"),
933			not(feature = "idle-poll")
934		))]
935		sleep_state::install_for_core(core_id);
936	}
937}
938
939#[inline]
940#[cfg(feature = "smp")]
941fn get_scheduler_input(core_id: CoreId) -> &'static InterruptTicketMutex<SchedulerInput> {
942	SCHEDULER_INPUTS.lock()[usize::try_from(core_id).unwrap()]
943}
944
945pub unsafe fn spawn(
946	func: unsafe extern "C" fn(usize),
947	arg: usize,
948	prio: Priority,
949	stack_size: usize,
950	selector: isize,
951) -> TaskId {
952	static CORE_COUNTER: AtomicU32 = AtomicU32::new(1);
953
954	let core_id = if selector < 0 {
955		// use Round Robin to schedule the cores
956		CORE_COUNTER.fetch_add(1, Ordering::SeqCst) % get_processor_count()
957	} else {
958		selector as u32
959	};
960
961	unsafe { PerCoreScheduler::spawn(func, arg, prio, core_id, stack_size) }
962}
963
964#[allow(clippy::result_unit_err)]
965pub fn join(id: TaskId) -> Result<(), ()> {
966	let core_scheduler = core_scheduler();
967
968	debug!(
969		"Task {} is waiting for task {}",
970		core_scheduler.get_current_task_id(),
971		id
972	);
973
974	loop {
975		let mut waiting_tasks_guard = WAITING_TASKS.lock();
976
977		let Some(queue) = waiting_tasks_guard.get_mut(&id) else {
978			return Ok(());
979		};
980
981		queue.push_back(core_scheduler.get_current_task_handle());
982		core_scheduler.block_current_task(None);
983
984		// Switch to the next task.
985		drop(waiting_tasks_guard);
986		core_scheduler.reschedule();
987	}
988}
989
990pub fn shutdown(arg: i32) -> ! {
991	crate::syscalls::shutdown(arg)
992}
993
994fn get_task_handle(id: TaskId) -> Option<TaskHandle> {
995	TASKS.lock().get(&id).copied()
996}
997
998#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
999pub(crate) static BOOT_ROOT_PAGE_TABLE: OnceCell<usize> = OnceCell::new();
1000
1001#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
1002pub(crate) fn get_root_page_table() -> usize {
1003	let current_task_borrowed = core_scheduler().current_task.borrow_mut();
1004	current_task_borrowed.root_page_table
1005}