From 50db561774baa31438f503ad352a1a3840bb0608 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 19 Mar 2020 16:58:04 +0100 Subject: [PATCH] Update implementation section --- .../posts/12-async-await/index.md | 59 +++++++++++++----- .../12-async-await/qemu-simple-executor.png | Bin 0 -> 6995 bytes 2 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 blog/content/second-edition/posts/12-async-await/qemu-simple-executor.png diff --git a/blog/content/second-edition/posts/12-async-await/index.md b/blog/content/second-edition/posts/12-async-await/index.md index 6de2d9e4..2cb4b9ac 100644 --- a/blog/content/second-edition/posts/12-async-await/index.md +++ b/blog/content/second-edition/posts/12-async-await/index.md @@ -766,7 +766,7 @@ We see that futures and async/await fit the cooperative multitasking pattern per ## Implementation -Now that we understand how cooperative multitasking based on futures and async/await works in Rust, it's time to add support for it to our kernel. Since the [`Future`] trait is part of the `core` library and async/await is a feature of the language itself, there is nothing special we need to do to use it in our `#![no_std]` kernel. The only requirement is that we use at least nightly-TODO of Rust because async/await was based on parts of the standard library before. +Now that we understand how cooperative multitasking based on futures and async/await works in Rust, it's time to add support for it to our kernel. Since the [`Future`] trait is part of the `core` library and async/await is a feature of the language itself, there is nothing special we need to do to use it in our `#![no_std]` kernel. The only requirement is that we use at least nightly-TODO of Rust because async/await was not `no_std` compatible before. With a recent-enough nightly, we can start using async/await in our `main.rs`: @@ -800,6 +800,9 @@ pub mod task; ```rust // in src/task/mod.rs +use core::{future::Future, pin::Pin}; +use alloc::boxed::Box; + pub struct Task { future: Pin>>, } @@ -807,8 +810,8 @@ pub struct Task { The `Task` struct is a newtype wrapper around a pinned, heap allocated, dynamically dispatched future with the empty type `()` as output. Let's go through it in detail: -- We require that the future associated with a task returns `()`. So tasks don't return any result, they are just executed for its side effects. For example, the `example_task` function we defined above has no return value, but it prints something to the screen as a side effect. -- The `dyn` keyword indicates that we store a [trait object] in the `Box`. This means that the type of the future is [dynamically dispatched], which makes it possible to store different types of futures in the task. This is important because each `async fn` has their own type and we want to be able to create different tasks later. +- We require that the future associated with a task returns `()`. This means that tasks don't return any result, they are just executed for its side effects. For example, the `example_task` function we defined above has no return value, but it prints something to the screen as a side effect. +- The `dyn` keyword indicates that we store a [trait object] in the `Box`. This means that the type of the future is [dynamically dispatched], which makes it possible to store different types of futures in the `Task` type. This is important because each `async fn` has their own type and we want to be able to create different tasks later. - As we learned in the [section about pinning], the `Pin` type ensures that a value cannot be moved in memory by placing it on the heap and preventing the creation of `&mut` references to it. This is important because futures generated by async/await might be self-referential, i.e. contain pointers to itself that would be invalidated when the future is moved. [trait object]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html @@ -821,7 +824,7 @@ To allow the creation of new `Task` structs from futures, we create a `new` func // in src/task/mod.rs impl Task { - pub fn new(future: impl Future) -> Task { + pub fn new(future: impl Future + 'static) -> Task { Task { future: Box::pin(future), } @@ -829,15 +832,17 @@ impl Task { } ``` -The function takes an arbitrary future with output type `()` and pins it in memory through the [`Box::pin`] function. Then it wraps it in the `Task` struct and returns the new task. +The function takes an arbitrary future with output type `()` and pins it in memory through the [`Box::pin`] function. Then it wraps it in the `Task` struct and returns the new task. The `'static` lifetime is required here because the returned `Task` can live for an arbitrary time, so the future needs to be valid for that time too. We also add a `poll` method to allow the executor to poll the corresponding future: ```rust // in src/task/mod.rs +use core::task::{Context, Poll}; + impl Task { - fn poll(&mut self, context: &mut Context) -> Poll { + fn poll(&mut self, context: &mut Context) -> Poll<()> { self.future.as_mut().poll(context) } } @@ -868,7 +873,7 @@ pub struct SimpleExecutor { impl SimpleExecutor { pub fn new() -> SimpleExecutor { SimpleExecutor { - task_queue:: VecDeque::new(), + task_queue: VecDeque::new(), } } @@ -885,14 +890,14 @@ The struct contains a single `task_queue` field of type [`VecDeque`], which is b #### Dummy Waker -In order to call the `poll` method, we need to create a [`Context`] type, which wraps a [`Waker`] type. To start simple, we will first create a dummy waker that does nothing. The simplest way to do this is by implementing the [`Wake`] trait: +In order to call the `poll` method, we need to create a [`Context`] type, which wraps a [`Waker`] type. To start simple, we will first create a dummy waker that does nothing. The simplest way to do this is by implementing the unstable [`Wake`] trait for an empty `DummyWaker` struct: [`Wake`]: https://doc.rust-lang.org/nightly/alloc/task/trait.Wake.html ```rust // in src/task/simple_executor.rs -use alloc::task::Wake; +use alloc::{sync::Arc, task::Wake}; struct DummyWaker; @@ -903,9 +908,31 @@ impl Wake for DummyWaker { } ``` -Since the [`Waker`] type implements the [`From>`] trait for all types `W` that implement the [`Wake`] trait, we can easily create a `Waker` through `Waker::from(DummyWaker)`. We will utilize this in the following to create a simple `Executor::run` method. +The trait is still unstable, so we have to add **`#![feature(wake_trait)]`** to the top of our `lib.rs` to use it. The `wake` method of the trait is normally responsible for waking the corresponding task in the executor. However, our `SimpleExecutor` will not differentiate between ready and waiting tasks, so we don't need to do anything on `wake` calls. -[`From>`]: TODO +Since wakers are normally shared between the executor and the asynchronous tasks, the `wake` method requires that the `Self` instance is wrapped in the [`Arc`] type, which implements reference-counted ownership. The basic idea is that the value is heap-allocated and the number of active references to it are counted. If the number of active references reaches zero, the value is no longer needed and can be deallocated. + +[`Arc`]: https://doc.rust-lang.org/stable/alloc/sync/struct.Arc.html + +To make our `DummyWaker` usable with the [`Context`] type, we need a method to convert it to the [`Waker`] defined in the core library: + +```rust +// in src/task/simple_executor.rs + +use core::task::Waker; + +impl DummyWaker { + fn to_waker(self) -> Waker { + Waker::from(Arc::new(self)) + } +} +``` + +The method first makes the `self` instance reference-counted by wrapping it in an [`Arc`]. Then it uses the [`Waker::from`] method to create the `Waker`. This method is available for all reference counted types that implement the [`Wake`] trait. + +[`Waker::from`]: TODO + +Now we have a way to create a `Waker` instance, we can use it to implement a `run` method on our executor. #### A `run` Method @@ -914,10 +941,13 @@ The most simple `run` method is to repeatedly poll all queued tasks in a loop un ```rust // in src/task/simple_executor.rs +use core::task::{Context, Poll}; + impl SimpleExecutor { pub fn run(&mut self) { while let Some(mut task) = self.task_queue.pop_front() { - let mut context = Context::from_waker(Waker::from(DummyWaker)); + let waker = DummyWaker.to_waker(); + let mut context = Context::from_waker(&waker); match task.poll(&mut context) { Poll::Ready(()) => {} // task done Poll::Pending => self.task_queue.push_back(task), @@ -942,7 +972,7 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { // […] initialization routines, including `init_heap` let mut executor = SimpleExecutor::new(); - executor.spawn(Task::new(example_task())): + executor.spawn(Task::new(example_task())); executor.run(); // […] test_main, "it did not crash" message, hlt_loop @@ -951,5 +981,4 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { When we run it, we see that the expected _"async number: 42"_ message is printed to the screen: -TODO image - +![QEMU printing "Hello World", "async number: 42", and "It did not crash!"](qemu-simple-executor.png) diff --git a/blog/content/second-edition/posts/12-async-await/qemu-simple-executor.png b/blog/content/second-edition/posts/12-async-await/qemu-simple-executor.png new file mode 100644 index 0000000000000000000000000000000000000000..7f9dc3f0f33f562305168a87d0a9d98f5f0a6e7f GIT binary patch literal 6995 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 literal 0 HcmV?d00001