4

I am trying to implement futures on the Tock OS embedded operating system. I'm trying to using Tokio in a #[no_std] environment.

My Cargo.toml file looks like this:

[package]
name = "nrf52dk"
version = "0.1.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
build = "build.rs"

[profile.dev]
panic = "abort"
lto = true
opt-level = "z"
debug = true

[profile.release]
panic = "abort"
lto = true
opt-level = "z"
debug = true

[dependencies]
cortexm4 = { path = "../../arch/cortex-m4" }
capsules = { path = "../../capsules" }
kernel = { path = "../../kernel" }
nrf52 = { path = "../../chips/nrf52" }
nrf5x = { path = "../../chips/nrf5x" }
futures = {version = "0.2.0", default-features = false }

This compiles with no errors but when I add tokio-reactor = "0.1.1", I get the error: error[E0463]: can't find crate for std. I understand this is because Tokio imports some stuff from the std library.

Is it possible to get around this problem?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
EmbeddedOS
  • 173
  • 6
  • 18

2 Answers2

5

As far as I can tell, you don't. Tokio Reactor 0.1.1 imports many things from the standard library, none of them conditionally.

Most of the imports could probably be switched to libcore alternatives, but Arc requires memory allocation, which lives in the alloc crate.

As an example of a crate that supports no_std, check out Futures 0.1.20. This has a feature flag to opt-in to functionality that requires the standard library.

If you wish to do this, you'll need to contribute substantial effort to Tokio and all of its dependencies to add feature flags to opt-in to all the functionality that requires the standard library. It would be worth opening an issue with the maintainers to coordinate such an effort.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    `futures` is also going to miss a lot of features (e.g. [`impl From> for NotifyHandle`](https://docs.rs/futures/0.1.21/futures/executor/struct.NotifyHandle.html#impl-From%3CArc%3CT%3E%3E)) in `no_std`, and `mio` is going to be complicated too. It's probably easier to write your own executor for your own needs. – Stefan Apr 21 '18 at 20:27
  • 4 years later: is this still true? Blog post https://ferrous-systems.com/blog/stable-async-on-embedded/ says that Rust async will soon be available for no_std. That said, it doesn't mention Tokio anywhere. Will Tokio support no_std as well? Or do I need to use some other executor? Or is it all a theoretical discussion because there isn't (yet) an executor for no_std? – Bruno Rijsman Mar 06 '22 at 20:04
  • @BrunoRijsman *async* works fine on embedded devices. *Tokio* does not. I've written my own executors for specific embedded devices, for example. Most embedded executors will be tightly tied to the platform as they use tools like interrupts. – Shepmaster Mar 07 '22 at 14:33
  • @Shepmaster Are there any public (open source or commercial) executors available for common embedded platforms (e.g. Cortex Mx) or will it be necessary to implement an executor myself? – Bruno Rijsman Mar 07 '22 at 23:48
  • @BrunoRijsman none I've used myself. Perhaps [embassy](https://github.com/embassy-rs/embassy)? – Shepmaster Mar 08 '22 at 14:39
  • @Shepmaster Thank you for the reference to embassy. That looks very promising. – Bruno Rijsman Mar 08 '22 at 16:20
4

Expanding on what Shepmaster already said: you don't want tokio; it's based on mio, which is unlikely to ever work in a kernel, especially without heap allocation / std.

So how to drive tasks (spawned Futures) in such environment (this is written for the futures 0.1.x series):

  • your "Executor" ("main loop") will want to track some state per task, e.g. whether you need to poll it, perhaps some linked list to find those that need to be polled.
  • you need a place for that state; you also need to store the Futures wrapped in Spawn<...>. It should be possible to use "static" allocated storage for that.
  • you'll need to implement UnsafeNotify (and the base trait Notify), probably for some raw pointer/&'static reference to the task (including the state); notify needs to be able to queue tasks to get polled in a thread safe way. The {clone,drop}_{raw,id} functions can be empty as you'll be using static allocations anyway. notify also needs to schedule the main loop if it is sleeping. The queue itself will need some global state too ("list head+tail"); if you need different queues you can store a reference to it too in a NotifyHandle (e.g. in the id: usize parameter).
  • you could even try running multiple loops on the same "poll queue", good luck getting it thread-safe :) The future-0.2 ThreadPool might give some ideas how to do that (or the tokio-threadpool crate).
  • you'll probably need to add some "timer" handling to the event loop; a timer should store a NotifyHandle to the task it is supposed to wake on a timeout, some state to track whether the timeout was hit, and the event loop needs a list of active (pointers to) timers to determine how long to wait. (the tokio-timer crate might give you some ideas how to implement this)
  • some similar handling for async IO; in userspace you'd use select with a timeout (or platform specific optimized version of it), in a kernel you'll probably have to find other ways :) (In the tokio world this is provided by the Reactor, which is based on mio)
  • to drive a task you'll want to use poll_future_notify

In futures-0.2 NotifyHandle became Waker, and UnsafeNotify became UnsafeWake; the id: usize context is gone (just use a struct with all the data you need to implement UnsafeWake for). Instead of storing Spawn<...> for a future you need to manually store a LocalMap for each task, which is then be used to create a Context with Context::without_spawn, which is then passed to Future::poll.

Stefan
  • 5,304
  • 2
  • 25
  • 44