|
17 | 17 | //! having the `k_work` embedded in their structure, and Zephyr schedules the work when the given
|
18 | 18 | //! reason happens.
|
19 | 19 | //!
|
| 20 | +//! At this time, only the basic work queue type is supported. |
| 21 | +//! |
20 | 22 | //! Zephyr's work queues can be used in different ways:
|
21 | 23 | //!
|
22 | 24 | //! - Work can be scheduled as needed. For example, an IRQ handler can queue a work item to process
|
|
27 | 29 | //! when the work is complete. The work queue scheduling functions are designed, and intended, for
|
28 | 30 | //! a given work item to be able to reschedule itself, and such usage is common.
|
29 | 31 | //!
|
30 |
| -//! ## Waitable events |
31 |
| -//! |
32 |
| -//! The triggerable work items can be triggered to wake on a set of any of the following: |
33 |
| -//! |
34 |
| -//! - A signal. `k_poll_signal` is a type used just for waking work items. This works similar to a |
35 |
| -//! binary semaphore, but is lighter weight for use just by this mechanism. |
36 |
| -//! - A semaphore. Work can be scheduled to run when a `k_sem` is available. Since |
37 |
| -//! [`sys::sync::Semaphore`] is built on top of `k_sem`, the "take" operation for these semaphores |
38 |
| -//! can be a trigger source. |
39 |
| -//! - A queue/FIFO/LIFO. The queue is used to implement [`sync::channel`] and thus any blocking |
40 |
| -//! operation on queues can be a trigger source. |
41 |
| -//! - Message Queues, and Pipes. Although not yet provided in Rust, these can also be a source of |
42 |
| -//! triggering. |
43 |
| -//! |
44 |
| -//! It is important to note that the trigger source may not necessarily still be available by the |
45 |
| -//! time the work item is actually run. This depends on the design of the system. If there is only |
46 |
| -//! a single waiter, then it will still be available (the mechanism does not have false triggers, |
47 |
| -//! like CondVar). |
48 |
| -//! |
49 |
| -//! Also, note, specifically, that Zephyr Mutexes cannot be used as a trigger source. That means |
50 |
| -//! that locking a [`sync::Mutex`] shouldn't be use within work items. There is another |
51 |
| -//! [`kio::sync::Mutex`], which is a simplified Mutex that is implemented with a Semaphore that can |
52 |
| -//! be used from work-queue based code. |
53 |
| -//! |
54 |
| -//! # Rust `Future` |
55 |
| -//! |
56 |
| -//! The rust language, also has built-in support for something rather similar to Zephyr work queues. |
57 |
| -//! The main user-visible type behind this is [`Future`]. The rust compiler has support for |
58 |
| -//! functions, as well as code blocks to be declared as `async`. For this code, instead of directly |
59 |
| -//! returning the given data, returns a `Future` that has as its output type the data. What this |
60 |
| -//! does is essentially capture what would be stored on the stack to maintain the state of that code |
61 |
| -//! into the data of the `Future` itself. For rust code running on a typical OS, a crate such as |
62 |
| -//! [Tokio](https://tokio.rs/) provides what is known as an executor, which implements the schedule |
63 |
| -//! for these `Futures` as well as provides equivalent primitives for Mutex, Semaphores and channels |
64 |
| -//! for this code to use for synchronization. |
65 |
| -//! |
66 |
| -//! It is notable that the Zephyr implementation of `Future` operations under a fairly simple |
67 |
| -//! assumption of how this scheduling will work. Each future is invoked with a Context, which |
68 |
| -//! contains a dynamic `Waker` that can be invoked to schedule this Future to run again. This means |
69 |
| -//! that the primitives are typically implemented above OS primitives, where each manages wake |
70 |
| -//! queues to determine the work that needs to be woken. |
71 |
| -//! |
72 |
| -//! # Bringing it together. |
73 |
| -//! |
74 |
| -//! There are a couple of issues that need to be addressed to bring work-queue support to Rust. |
75 |
| -//! First is the question of how they will be used. On the one hand, there are users that will |
76 |
| -//! definitely want to make use of `async` in rust, and it is important to implement a executor, |
77 |
| -//! similar to Tokio, that will schedule this `async` code. On the other hand, it will likely be |
78 |
| -//! common for others to want to make more direct use of the work queues themselves. As such, these |
79 |
| -//! users will want more direct access to scheduling and triggering of work. |
80 |
| -//! |
81 |
| -//! ## Future erasure |
82 |
| -//! |
83 |
| -//! One challenge with using `Future` for work is that the `Future` type intentionally erases the |
84 |
| -//! details of scheduling work, reducing it down to a single `Waker`, which similar to a trait, has |
85 |
| -//! a `wake` method to cause the executor to schedule this work. Unfortunately, this simple |
86 |
| -//! mechanism makes it challenging to take advantage of Zephyr's existing mechanisms to be able to |
87 |
| -//! automatically trigger work based on primitives. |
88 |
| -//! |
89 |
| -//! As such, what we do is have a structure `Work` that contains both a `k_work_poll` as well as |
90 |
| -//! `Context` from Rust. Our handler can use a mechanism similar to C's `CONTAINER_OF` macro to |
91 |
| -//! recover this outer structure. |
92 |
| -//! |
93 |
| -//! There is some extra complexity to this process, as the `Future` we are storing associated with |
94 |
| -//! the work is `?Sized`, since each particular Future will have a different size. As such, it is |
95 |
| -//! not possible to recover the full work type. To work around this, we have a Sized struct at the |
96 |
| -//! beginning of this structure, that along with judicious use of `#[repr(C)]` allows us to recover |
97 |
| -//! this fixed data. This structure contains the information needed to re-schedule the work, based |
98 |
| -//! on what is needed. |
99 |
| -//! |
100 | 32 | //! ## Ownership
|
101 | 33 | //!
|
102 | 34 | //! The remaining challenge with implementing `k_work` for Rust is that of ownership. The model
|
103 | 35 | //! taken here is that the work items are held in a `Box` that is effectively owned by the work
|
104 | 36 | //! itself. When the work item is scheduled to Zephyr, ownership of that box is effectively handed
|
105 | 37 | //! off to C, and then when the work item is called, the Box re-constructed. This repeats until the
|
106 |
| -//! work is no longer needed (e.g. when a [`Future::poll`] returns `Ready`), at which point the work |
107 |
| -//! will be dropped. |
| 38 | +//! work is no longer needed, at which point the work will be dropped. |
108 | 39 | //!
|
109 | 40 | //! There are two common ways the lifecycle of work can be managed in an embedded system:
|
110 | 41 | //!
|
111 | 42 | //! - A set of `Future`'s are allocated once at the start, and these never return a value. Work
|
112 | 43 | //! Futures inside of this (which correspond to `.await` in async code) can have lives and return
|
113 | 44 | //! values, but the main loops will not return values, or be dropped. Embedded Futures will
|
114 | 45 | //! typically not be boxed.
|
115 |
| -//! - Work will be dynamically created based on system need, with threads using [`kio::spawn`] to |
116 |
| -//! create additional work (or creating the `Work` items directly). These can use [`join`] or |
117 |
| -//! [`join_async`] to wait for the results. |
118 | 46 | //!
|
119 | 47 | //! One consequence of the ownership being passed through to C code is that if the work cancellation
|
120 | 48 | //! mechanism is used on a work queue, the work items themselves will be leaked.
|
121 | 49 | //!
|
122 |
| -//! The Future mechanism in Rust relies on the use of [`Pin`] to ensure that work items are not |
123 |
| -//! moved. We have the same requirements here, although currently, the pin is only applied while |
124 |
| -//! the future is run, and we do not expose the `Box` that we use, thus preventing moves of the work |
125 |
| -//! items. |
| 50 | +//! These work items are also `Pin`, to ensure that the work actions are not moved. |
126 | 51 | //!
|
127 | 52 | //! ## The work queues themselves
|
128 | 53 | //!
|
|
170 | 95 | //! [`sys::sync::Semaphore`]: crate::sys::sync::Semaphore
|
171 | 96 | //! [`sync::channel`]: crate::sync::channel
|
172 | 97 | //! [`sync::Mutex`]: crate::sync::Mutex
|
173 |
| -//! [`kio::sync::Mutex`]: crate::kio::sync::Mutex |
174 |
| -//! [`kio::spawn`]: crate::kio::spawn |
175 | 98 | //! [`join`]: futures::JoinHandle::join
|
176 | 99 | //! [`join_async`]: futures::JoinHandle::join_async
|
177 | 100 |
|
|
0 commit comments