Expand description
Zephyr Work Queues
§Zephyr Work Queues and Work
Zephyr has a mechanism called a Workqueue.
Each workqueue is backed by a single Zephyr thread, and has its own stack. The work queue
consists of a FIFO queue of work items that will be run consecutively on that thread. The
underlying types are k_work_q
for the work queue itself, and k_work
for the worker.
In addition to the simple schedulable work, Zephyr also has two additional types of work:
k_work_delayable
which can be scheduled to run in the future, and k_work_poll
, described as
triggered work in the docs. This can be scheduled to run when various items within Zephyr
become available. This triggered work also has a timeout. In this sense the triggered work is
a superset of the other types of work. Both delayable and triggered work are implemented by
having the k_work
embedded in their structure, and Zephyr schedules the work when the given
reason happens.
At this time, only the basic work queue type is supported.
Zephyr’s work queues can be used in different ways:
- Work can be scheduled as needed. For example, an IRQ handler can queue a work item to process data it has received from a device.
- Work can be scheduled periodically.
As most C use of Zephyr statically allocates things like work, these are typically rescheduled when the work is complete. The work queue scheduling functions are designed, and intended, for a given work item to be able to reschedule itself, and such usage is common.
§Ownership
The remaining challenge with implementing k_work
for Rust is that of ownership. The model
taken here is that the work items are held in a Box
that is effectively owned by the work
itself. When the work item is scheduled to Zephyr, ownership of that box is effectively handed
off to C, and then when the work item is called, the Box re-constructed. This repeats until the
work is no longer needed, at which point the work will be dropped.
There are two common ways the lifecycle of work can be managed in an embedded system:
- A set of
Future
’s are allocated once at the start, and these never return a value. Work Futures inside of this (which correspond to.await
in async code) can have lives and return values, but the main loops will not return values, or be dropped. Embedded Futures will typically not be boxed.
One consequence of the ownership being passed through to C code is that if the work cancellation mechanism is used on a work queue, the work items themselves will be leaked.
These work items are also Pin
, to ensure that the work actions are not moved.
§The work queues themselves
Workqueues themselves are built using WorkQueueBuilder
. This needs a statically defined
stack. Typical usage will be along the lines of:
kobj_define! {
WORKER_STACK: ThreadStack<2048>;
}
// ...
let main_worker = Box::new(
WorkQueueBuilder::new()
.set_priority(2).
.set_name(c"mainloop")
.set_no_yield(true)
.start(MAIN_LOOP_STACK.init_once(()).unwrap())
);
let _ = zephyr::kio::spawn(
mainloop(), // Async or function returning Future.
&main_worker,
c"w:mainloop",
);
...
// Leak the Box so that the worker is never freed.
let _ = Box::leak(main_worker);
It is important that WorkQueues never be dropped. It has a Drop implementation that invokes panic. Zephyr provides no mechanism to stop work queue threads, so dropping would result in undefined behavior.
§Current Status
Although Zephyr has 3 types of work queues, the k_work_poll
is sufficient to implement all of
the behavior, and this implementation only implements this type. Non Future work could be built
around the other work types.
As such, this means that manually constructed work is still built using Future
. The _async
primitives throughout this crate can be used just as readily by hand-written Futures as by async
code. Notable, the use of Signal
will likely be common, along with possible timeouts.
Structs§
- Signal
- A Rust wrapper for
k_poll_signal
. - Work
- A basic Zephyr work item.
- Work
Queue - A running work queue thread.
- Work
Queue Builder - A builder for work queues themselves.
Enums§
- Submit
Result - Possible returns from work queue submission.
Traits§
- Simple
Action - A simple action that just does something with its data.
Functions§
- get_
current_ workq - Retrieve the current work queue, if we are running within one.