embassy/docs/modules/ROOT/pages/runtime.adoc

29 lines
2.4 KiB
Plaintext
Raw Normal View History

2021-12-09 09:06:17 +00:00
= Embassy runtime
2021-12-10 09:22:11 +00:00
The Embassy executor is an async/await executor designed for embedded usage.
2021-12-09 09:40:26 +00:00
2021-12-10 09:22:11 +00:00
* No `alloc`, no heap needed. Task are statically allocated.
2021-12-09 09:40:26 +00:00
* No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
* Integrated timer queue: sleeping is easy, just do `Timer::after(Duration::from_secs(1)).await;`.
* No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
* Efficient polling: a wake will only poll the woken task, not all of them.
* Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time.
* Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
2021-12-10 09:22:11 +00:00
== Executor
The executor function is described below. The executor keeps a queue of tasks that it should poll. When a task is created, it is polled (1). The task will attempt to make progress until reaches a point where it would be blocked. This may happen whenever a task is .await'ing an async function. When that happens, the task yields execution by (2) returning `Poll::Pending`. Once a task yields, the executor enqueues the task at the end of the run queue, and proceeds to (3) poll the next task in the queue. If a task returns `Poll::Ready` it essentially means that the task is finished and will not be enqueued again.
IMPORTANT: The executor relies on tasks not blocking indefinitely, as this prevents the executor to regain control and schedule another task.
image::embassy_executor.png[Executor model]
== Interrupts
Interrupts are a common way for peripherals to signal completion of some operation and fits well with the async execution model. The following diagram describes a typical application flow where (1) a task is polled and is attempting to make progress. The task then (2) instructs the peripheral to perform some operation, and awaits. After some time has passede, (3) an interrupt is raised, marking the completion of the operation.
The peripheral HAL then (4) ensures that interrupt signals are routed to to the peripheral and updating the peripheral state with the results of the operation. The executor is then (5) notified that the task should be polled, which it will do.
image::embassy_irq.png[Interrupt handling]