| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- use crate::{
- context::ExecutionContext,
- executor::{ExecuteStatus, OutputHandle, Stack},
- ready_queue::{cpu_rq, local_rq},
- run::{Contexted, Run},
- task::{Task, TaskAdapter, TaskHandle},
- };
- use alloc::sync::Arc;
- use core::{
- mem::forget,
- ptr::NonNull,
- sync::atomic::{compiler_fence, Ordering},
- task::Waker,
- };
- use eonix_log::println_trace;
- use eonix_preempt::assert_preempt_count_eq;
- use eonix_sync::{LazyLock, Spin, SpinIrq as _};
- use intrusive_collections::RBTree;
- use pointers::BorrowedArc;
- #[arch::define_percpu]
- static CURRENT_TASK: Option<NonNull<Task>> = None;
- #[arch::define_percpu]
- static LOCAL_SCHEDULER_CONTEXT: ExecutionContext = ExecutionContext::new();
- static TASKS: LazyLock<Spin<RBTree<TaskAdapter>>> =
- LazyLock::new(|| Spin::new(RBTree::new(TaskAdapter::new())));
- pub struct Scheduler;
- pub struct JoinHandle<Output>(Arc<Spin<OutputHandle<Output>>>)
- where
- Output: Send;
- impl Task {
- pub fn current<'a>() -> BorrowedArc<'a, Task> {
- unsafe {
- // SAFETY:
- // We should never "inspect" a change in `current`.
- // The change of `CURRENT` will only happen in the scheduler. And if we are preempted,
- // when we DO return, the `CURRENT` will be the same and remain valid.
- BorrowedArc::from_raw(CURRENT_TASK.get().expect("Current task should be present"))
- }
- }
- }
- impl<O> JoinHandle<O>
- where
- O: Send,
- {
- pub fn join(self) -> O {
- let Self(output) = self;
- let mut waker = Some(Waker::from(Task::current().clone()));
- loop {
- let mut locked = output.lock();
- match locked.try_resolve() {
- Some(output) => break output,
- None => {
- if let Some(waker) = waker.take() {
- locked.register_waiter(waker);
- }
- }
- }
- }
- }
- }
- impl Scheduler {
- /// `Scheduler` might be used in various places. Do not hold it for a long time.
- ///
- /// # Safety
- /// The locked returned by this function should be locked with `lock_irq` to prevent from
- /// rescheduling during access to the scheduler. Disabling preemption will do the same.
- ///
- /// Drop the lock before calling `schedule`.
- pub fn get() -> &'static Self {
- static GLOBAL_SCHEDULER: Scheduler = Scheduler;
- &GLOBAL_SCHEDULER
- }
- pub fn init_local_scheduler<S>()
- where
- S: Stack,
- {
- let stack = S::new();
- unsafe {
- eonix_preempt::disable();
- // SAFETY: Preemption is disabled.
- let context: &mut ExecutionContext = LOCAL_SCHEDULER_CONTEXT.as_mut();
- context.set_ip(local_scheduler as _);
- context.set_sp(stack.get_bottom().addr().get() as usize);
- eonix_preempt::enable();
- }
- // We don't need to keep the stack around.
- forget(stack);
- }
- /// # Safety
- /// This function must not be called inside of the scheulder context.
- ///
- /// The caller must ensure that `preempt::count` == 1.
- pub unsafe fn go_from_scheduler(to: &ExecutionContext) {
- // SAFETY: Preemption is disabled.
- unsafe { LOCAL_SCHEDULER_CONTEXT.as_ref() }.switch_to(to);
- }
- /// # Safety
- /// This function must not be called inside of the scheulder context.
- ///
- /// The caller must ensure that `preempt::count` == 1.
- pub unsafe fn goto_scheduler(from: &ExecutionContext) {
- // SAFETY: Preemption is disabled.
- from.switch_to(unsafe { LOCAL_SCHEDULER_CONTEXT.as_ref() });
- }
- /// # Safety
- /// This function must not be called inside of the scheulder context.
- ///
- /// The caller must ensure that `preempt::count` == 1.
- pub unsafe fn goto_scheduler_noreturn() -> ! {
- // SAFETY: Preemption is disabled.
- unsafe { LOCAL_SCHEDULER_CONTEXT.as_ref().switch_noreturn() }
- }
- fn add_task(task: Arc<Task>) {
- TASKS.lock().insert(task);
- }
- fn remove_task(task: &Task) {
- unsafe { TASKS.lock().cursor_mut_from_ptr(task as *const _).remove() };
- }
- fn select_cpu_for_task(&self, task: &Task) -> usize {
- task.cpu.load(Ordering::Relaxed) as _
- }
- pub fn activate(&self, task: &Arc<Task>) {
- // Only one cpu can be activating the task at a time.
- // TODO: Add some checks.
- if task.on_rq.swap(true, Ordering::Acquire) {
- // Lock the rq and check whether the task is on the rq again.
- let cpuid = task.cpu.load(Ordering::Acquire);
- let mut rq = cpu_rq(cpuid as _).lock_irq();
- if !task.on_rq.load(Ordering::Acquire) {
- // Task has just got off the rq. Put it back.
- rq.put(task.clone());
- } else {
- // Task is already on the rq. Do nothing.
- return;
- }
- } else {
- // Task not on some rq. Select one and put it here.
- let cpu = self.select_cpu_for_task(&task);
- let mut rq = cpu_rq(cpu).lock_irq();
- task.cpu.store(cpu as _, Ordering::Release);
- rq.put(task.clone());
- }
- }
- pub fn spawn<S, R>(&self, runnable: R) -> JoinHandle<R::Output>
- where
- S: Stack + 'static,
- R: Run + Contexted + Send + 'static,
- R::Output: Send + 'static,
- {
- let TaskHandle {
- task,
- output_handle,
- } = Task::new::<S, _>(runnable);
- Self::add_task(task.clone());
- self.activate(&task);
- JoinHandle(output_handle)
- }
- /// Go to idle task. Call this with `preempt_count == 1`.
- /// The preempt count will be decremented by this function.
- ///
- /// # Safety
- /// We might never return from here.
- /// Drop all variables that take ownership of some resource before calling this function.
- pub fn schedule() {
- assert_preempt_count_eq!(1, "Scheduler::schedule");
- // Make sure all works are done before scheduling.
- compiler_fence(Ordering::SeqCst);
- // TODO!!!!!: Use of reference here needs further consideration.
- //
- // Since we might never return to here, we can't take ownership of `current()`.
- // Is it safe to believe that `current()` will never change across calls?
- unsafe {
- // SAFETY: Preemption is disabled.
- Scheduler::goto_scheduler(&Task::current().execution_context);
- }
- eonix_preempt::enable();
- }
- }
- extern "C" fn local_scheduler() -> ! {
- loop {
- assert_preempt_count_eq!(1, "Scheduler::idle_task");
- let mut rq = local_rq().lock_irq();
- let previous_task = CURRENT_TASK
- .get()
- .map(|ptr| unsafe { Arc::from_raw(ptr.as_ptr()) });
- let next_task = rq.get();
- match (previous_task, next_task) {
- (None, None) => {
- // Nothing to do, halt the cpu and rerun the loop.
- drop(rq);
- arch::halt();
- continue;
- }
- (None, Some(next)) => {
- CURRENT_TASK.set(NonNull::new(Arc::into_raw(next) as *mut _));
- }
- (Some(previous), None) => {
- if previous.state.is_running() {
- // Previous thread is `Running`, return to the current running thread.
- println_trace!(
- "trace_scheduler",
- "Returning to task id({}) without doing context switch",
- previous.id
- );
- CURRENT_TASK.set(NonNull::new(Arc::into_raw(previous) as *mut _));
- } else {
- // Nothing to do, halt the cpu and rerun the loop.
- CURRENT_TASK.set(NonNull::new(Arc::into_raw(previous) as *mut _));
- drop(rq);
- arch::halt();
- continue;
- }
- }
- (Some(previous), Some(next)) => {
- println_trace!(
- "trace_scheduler",
- "Switching from task id({}) to task id({})",
- previous.id,
- next.id
- );
- debug_assert_ne!(previous.id, next.id, "Switching to the same task");
- if previous.state.is_running() || !previous.state.try_park() {
- rq.put(previous);
- } else {
- previous.on_rq.store(false, Ordering::Release);
- }
- CURRENT_TASK.set(NonNull::new(Arc::into_raw(next) as *mut _));
- }
- }
- drop(rq);
- // TODO: We can move the release of finished tasks to some worker thread.
- if let ExecuteStatus::Finished = Task::current().run() {
- let current = CURRENT_TASK
- .swap(None)
- .map(|ptr| unsafe { Arc::from_raw(ptr.as_ptr()) })
- .expect("Current task should be present");
- Scheduler::remove_task(¤t);
- }
- }
- }
|