wg
wg copied to clipboard
Multitasking models
(Originally this was issue #45 but that turned into an RTOS only discussion)
Unlike Linux / Windows / *BSD / etc. targets where the OS provides threading support, there exist no standard way to do multitasking on embedded targets. This means that we either have to implement a multitasking mechanism ourselves or bind to a C implementation. There are few different ways to do multitasking:
Preemptive multitasking with support for task prioritization. Hardware scheduler with zero software (bookkeeping) overhead. Dead lock free execution plus all the memory safety that Rust grants.
@japaric is working on / maintaining the Cortex-M port.
- Tokio-like cooperative event loop.
Basically, cooperative multitasking that avoids the "all wakeups must poll all events" problem.
There's an implementation for the STM32F4 here but I haven't look at it in detail. I think that whatever implementation we end up adopting should meet these requirements:
- Should integrate with the device sleep functionality.
- It should be easy to use it with
embedded-haldrivers. - It should not require dynamic memory allocation, or features that require memory allocation should be opt-in.
- It should be possible to integrate this event loop with preemptive multitasking models like RTFM and the thread model.
@japaric is looking into a Cortex-M implementation of this and will write about his findings in a blog post.
- (RTOS) Threads. Discuss this in #45
A lot of embedded programmers are used to thread programming so this multitasking model should be well supported. Here we have the choice to implement threading support in Rust or to bind an existing RTOS / C implementation.
If we go the Rust route I think Chromium OS Embedded Controller implementation (MIT license) could be used as a reference.
If we go the C route perhaps we could bind Amazon's FreeRTOS. I think that at some point I saw some FreeRTOS bindings but that was before Amazon bought it.
~~Maybe thejpster can recommend some RTOS to bind?~~ See #45
@japaric won't work on this due to conflict of interests (he's already working on RTFM)
Ideally, all these models should have ports for the all the embedded targets. Also, the
embedded-hal drivers should play nicely will all these multitasking models (without
duplicating a bunch of code).
- Tokio-like cooperative event loop.
[...]
@japaric is looking into a Cortex-M implementation of this and will write about his findings in a blog post.
Any currently public information on this? I'm currently looking into what is necessary for an ergonomic futures wrapper around the embedded-hal traits along with all the associated machinery to run them, so knowing what else is being worked on in this space would be very useful.
Note about Event Loop. IMHO support of something like async/await required. With pure promises/futures and callbacks it's still not convenient to read and write code. Of cause, async/await helps only with linear logic, but it's the most common case.
@puzrin that's why I'm attempting to build off the futures crate, there are #[async]/await! macros available to work with the futures defined in that crate. Using them is still a bit of a mess at the moment (minimum of 3 nightly features required), but I believe getting those macros supported on stable Rust is one of the goals of the Rust 2018 Edition.
@Nemo157 yes, thanks. I'd like to clarify. There are 3 "components" to keep code clean:
- event loop
- async/await (or something of this kind)
- futures at HAL level
Partial combination is still much better than nothing, but...
I have made some prof-of-concept of async/await crate usage on Cortex-M4 board and it looks pretty interesting and promising. https://github.com/andreytkachenko/async-hal-stm32
@andreytkachenko Is there any documentation or do I need to dig through the code? I'm really excited about a cooperative event loop with await syntax. Where's a good place to start?
@bergus, I made it as simple as possible (thats why it is PoC), there are some examples. Once async/await will land in nightly I'll try add some more examples to bobbin-sdk on my stm32f429i.
@andreytkachenko Thanks, I've skimmed through everything, wasn't that much code after all. Unfortunately none of the examples feature concurrency of multiple tasks (hc_sr04 seems like an attempt, but still only does reactor::execute(task_blink…)).
Is this set_action_flag really necessary? I really dislike the idea of having a global static variable, and every future implementer having to know about it (saying that it will run only in that particular reactor). But I guess that's how futures work in Rust... :-/ (Update: in futures 0.2 there's a Waker that seems to solve this, but requires every interrupt event to store a reference to one waker)
There's a race condition in the reactor code: if an interrupt happens between the last check for while unsafe {ACTION} and the asm::wfi(), it hangs (until the next interrupt).
Also I don't like these timer CHANNELS, USART_VALUEs and EXTI_LINE_FLAGs. It seems cumbersome to have static variables for all these values in RAM while they already exist in the peripheral. All the interrupts do is copy the interrupt flag from the peripheral to ram, clear it in the peripheral and activate the reactor. I think I found a better approach: don't clear the event, but instead just disable the interrupt (not the whole interrupt, which typically handles multiple events, but just the event-triggers-interrupt-request flag - e.g. TIE in timer DIER or TX/RX*IE in usart CR1). Then do the peripheral access in the future's poll function itself, and after you cleared the event there you'd re-enable the interrupt (if you need it again at all).
@bergus, yes I thought about updating it for futures 0.2, but I am doing it as one more example for stm32f429-discovery board in bobbin-sdk, you can try add your version of that there too(I saw @jcsoo already has experiments with poll mechanism commented there)
The current futures unfortunately lead to code size so big, it doesn't even work for some Linux based devices ( mips in my case with 4mb flash ).
I was thinking of going back to a less "modern" way and just using callbacks.
Will the upcoming async/await address the code bloat?
I meant to write a blog post on the topic of no_std cooperative multitasking earlier this year but I've been prioritizing other work. For now, I've cleaned up some code that was sitting on my disk: https://github.com/japaric/no-std-async-experiments .
The repo contains an executor of generators that supports concurrently running several infinite tasks; the executor puts the uC to sleep when no task can make progress; and interrupts can wake up tasks using signals.
The repo also contains an executor of futures based on core::{task,future} which is basically a port of the first executor; semantics are the same but this second version needs a bit more indirection to get the job done.
The repo contains an example, which you can run on your PC / laptop using QEMU, for each executor.
cc @Nemo157
Closing this old discussion issue as part of today's triage of old issues. These days a number of multitasking models are in widespread use: RTOS bindings for classic threads (including popularly on ESP32, as well as native Rust RTOSs such as TockOS and Hubris), RTIC's interrupt-based multiple tasks, and async in both RTIC and Embassy. They're all widely used and seem to work out OK for people, so I don't think there's anything more for us to do today.