Bladeren bron

task: brand new block_on and stackful wrapper

We provide a simple block_on to constantly poll the given future and
block the current execution thread as before.

We also introduce a new future wrapper named `stackful` to convert any
future into a stackful one. We allocate a stack and keep polling the
future on the stack by constructing a TrapContext and call trap_return()
to get into the stackful environment. Then we capture the timer
interrupt to get preempts work.

Signed-off-by: greatbridf <greatbridf@icloud.com>
greatbridf 6 maanden geleden
bovenliggende
commit
33ff3156a0
4 gewijzigde bestanden met toevoegingen van 143 en 12 verwijderingen
  1. 0 7
      crates/eonix_runtime/src/task.rs
  2. 137 0
      src/kernel/task.rs
  3. 4 3
      src/kernel/task/clone.rs
  4. 2 2
      src/lib.rs

+ 0 - 7
crates/eonix_runtime/src/task.rs

@@ -93,13 +93,6 @@ impl Task {
             return rq;
         }
     }
-
-    pub fn block_on<F>(future: F) -> F::Output
-    where
-        F: Future,
-    {
-        todo!()
-    }
 }
 
 impl Wake for Task {

+ 137 - 0
src/kernel/task.rs

@@ -19,3 +19,140 @@ pub use process_list::ProcessList;
 pub use session::Session;
 pub use signal::SignalAction;
 pub use thread::{yield_now, Thread, ThreadBuilder};
+
+fn do_block_on<F>(mut future: core::pin::Pin<&mut F>) -> F::Output
+where
+    F: core::future::Future,
+{
+    let waker = core::task::Waker::noop();
+    let mut cx = core::task::Context::from_waker(&waker);
+
+    loop {
+        match future.as_mut().poll(&mut cx) {
+            core::task::Poll::Ready(output) => return output,
+            core::task::Poll::Pending => {}
+        }
+    }
+}
+
+/// Constantly poll the given future until it is ready, blocking the current thread.
+///
+/// # Warning
+/// This function will block the current thread and should not be used in async
+/// contexts as it might cause infinite blocking or deadlocks. The following is
+/// a bad example:
+///
+/// ```ignore
+/// block_on(async {
+///     // This will block the current thread forever.
+///     loop {
+///         println_debug!("This will never end!");
+///     }
+/// });
+///
+/// // The code below will never be reached.
+/// println_debug!("You'll never see this message!");
+/// ```
+///
+/// Use [`stackful`] instead to run async (or computational) code in a separate
+/// stackful (and preemptive) context or `RUNTIME.spawn` to run async code in
+/// the runtime's executor.
+pub fn block_on<F>(future: F) -> F::Output
+where
+    F: core::future::Future,
+{
+    do_block_on(core::pin::pin!(future))
+}
+
+/// Run the given future in a stackful context, allowing it to be preempted by
+/// timer interrupts.
+///
+/// ```ignore
+/// RUNTIME.spawn(stackful(async {
+///     // Some simulated computation heavy task.
+///     loop {
+///         println_debug!("Hello from stackful future!");
+///     }
+/// }));
+/// ```
+pub async fn stackful<F>(mut future: F) -> F::Output
+where
+    F: core::future::Future,
+{
+    use core::cell::UnsafeCell;
+    use eonix_hal::traits::fault::Fault;
+    use eonix_hal::traits::trap::RawTrapContext;
+    use eonix_hal::traits::trap::TrapReturn;
+    use eonix_hal::trap::TrapContext;
+    use eonix_log::println_debug;
+    use eonix_runtime::executor::Stack;
+
+    use crate::kernel::{
+        interrupt::{default_fault_handler, default_irq_handler},
+        timer::{should_reschedule, timer_interrupt},
+    };
+
+    let stack = KernelStack::new();
+
+    fn execute<F>(
+        future: core::pin::Pin<&mut F>,
+        output_ptr: core::ptr::NonNull<Option<F::Output>>,
+    ) -> !
+    where
+        F: core::future::Future,
+    {
+        let output = do_block_on(future);
+
+        unsafe {
+            output_ptr.write(Some(output));
+        }
+
+        unsafe {
+            core::arch::asm!("ebreak");
+        }
+
+        unreachable!()
+    }
+
+    let sp = stack.get_bottom();
+    let output = UnsafeCell::new(None);
+
+    let mut trap_ctx = TrapContext::new();
+
+    trap_ctx.set_user_mode(false);
+    trap_ctx.set_interrupt_enabled(true);
+    let _ = trap_ctx.set_user_call_frame(
+        execute::<F> as usize,
+        Some(sp.addr().get()),
+        None,
+        &[(&raw mut future) as usize, output.get() as usize],
+        |_, _| Ok::<(), u32>(()),
+    );
+
+    loop {
+        unsafe {
+            trap_ctx.trap_return();
+        }
+
+        match trap_ctx.trap_type() {
+            eonix_hal::traits::trap::TrapType::Syscall { .. } => {}
+            eonix_hal::traits::trap::TrapType::Fault(fault) => {
+                // Breakpoint
+                if let Fault::Unknown(3) = &fault {
+                    println_debug!("Breakpoint hit, returning output");
+                    break output.into_inner().unwrap();
+                }
+
+                default_fault_handler(fault, &mut trap_ctx)
+            }
+            eonix_hal::traits::trap::TrapType::Irq { callback } => callback(default_irq_handler),
+            eonix_hal::traits::trap::TrapType::Timer { callback } => {
+                callback(timer_interrupt);
+
+                if should_reschedule() {
+                    yield_now().await;
+                }
+            }
+        }
+    }
+}

+ 4 - 3
src/kernel/task/clone.rs

@@ -1,3 +1,4 @@
+use super::{block_on, stackful};
 use crate::{
     kernel::{
         syscall::procops::parse_user_tls,
@@ -9,7 +10,7 @@ use crate::{
 use bitflags::bitflags;
 use core::num::NonZero;
 use eonix_hal::processor::UserTLS;
-use eonix_runtime::{scheduler::RUNTIME, task::Task};
+use eonix_runtime::scheduler::RUNTIME;
 use eonix_sync::AsProof;
 use posix_types::signal::Signal;
 
@@ -131,7 +132,7 @@ impl CloneArgs {
 }
 
 pub fn do_clone(thread: &Thread, clone_args: CloneArgs) -> KResult<u32> {
-    let mut procs = Task::block_on(ProcessList::get().write());
+    let mut procs = block_on(ProcessList::get().write());
 
     let thread_builder = ThreadBuilder::new().clone_from(&thread, &clone_args)?;
     let current_process = thread.process.clone();
@@ -163,7 +164,7 @@ pub fn do_clone(thread: &Thread, clone_args: CloneArgs) -> KResult<u32> {
         UserPointerMut::new(parent_tid_ptr as *mut u32)?.write(new_pid)?
     }
 
-    RUNTIME.spawn(new_thread.run());
+    RUNTIME.spawn(stackful(new_thread.run()));
 
     Ok(new_pid)
 }

+ 2 - 2
src/lib.rs

@@ -37,7 +37,7 @@ use eonix_mm::address::PRange;
 use eonix_runtime::{executor::Stack, scheduler::RUNTIME};
 use kernel::{
     mem::GlobalPageAlloc,
-    task::{KernelStack, ProcessBuilder, ProcessList, ProgramLoader, ThreadBuilder},
+    task::{stackful, KernelStack, ProcessBuilder, ProcessList, ProgramLoader, ThreadBuilder},
     vfs::{
         dentry::Dentry,
         mount::{do_mount, MS_NOATIME, MS_NODEV, MS_NOSUID, MS_RDONLY},
@@ -272,5 +272,5 @@ async fn init_process(early_kstack: PRange) {
     // TODO!!!: Remove this.
     thread.files.open_console();
 
-    RUNTIME.spawn(thread.run());
+    RUNTIME.spawn(stackful(thread.run()));
 }