• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _docs-os_abstraction_layers:
2
3=====================
4OS Abstraction Layers
5=====================
6Pigweed’s operating system abstraction layers are portable and configurable
7building blocks, giving users full control while maintaining high performance
8and low overhead.
9
10Although we primarily target smaller-footprint MMU-less 32-bit microcontrollers,
11the OS abstraction layers are written to work on everything from single-core
12bare metal low end microcontrollers to asymmetric multiprocessing (AMP) and
13symmetric multiprocessing (SMP) embedded systems using Real Time Operating
14Systems (RTOS). They even fully work on your developer workstation on Linux,
15Windows, or MacOS!
16
17Pigweed has ports for the following systems:
18
19.. list-table::
20
21  * - **Environment**
22    - **Status**
23  * - STL (Mac, Window, & Linux)
24    - **✔ Supported**
25  * - `FreeRTOS <https://www.freertos.org/>`_
26    - **✔ Supported**
27  * - `Azure RTOS (formerly ThreadX) <https://azure.microsoft.com/en-us/services/rtos/>`_
28    - **✔ Supported**
29  * - `SEGGER embOS <https://www.segger.com/products/rtos/embos/>`_
30    - **✔ Supported**
31  * - Baremetal
32    - *In Progress*
33  * - `Zephyr <https://www.zephyrproject.org/>`_
34    - Planned
35  * - `CMSIS-RTOS API v2 & RTX5 <https://www.keil.com/pack/doc/CMSIS/RTOS2/html/index.html>`_
36    - Planned
37
38Pigweed's OS abstraction layers are divided by the **functional grouping of the
39primitives**. Many of our APIs are similar or **nearly identical to C++'s
40Standard Template Library (STL)** with the notable exception that we do not
41support exceptions. We opted to follow the STL's APIs partially because they
42are relatively well thought out and many developers are already familiar with
43them, but also because this means they are compatible with existing helpers in
44the STL; for example, ``std::lock_guard``.
45
46---------------
47Time Primitives
48---------------
49The :ref:`module-pw_chrono` module provides the building blocks for expressing
50durations, timestamps, and acquiring the current time. This in turn is used by
51other modules, including  :ref:`module-pw_sync` and :ref:`module-pw_thread` as
52the basis for any time bound APIs (i.e. with timeouts and/or deadlines). Note
53that this module is optional and bare metal targets may opt not to use this.
54
55.. list-table::
56
57  * - **Supported On**
58    - **SystemClock**
59  * - FreeRTOS
60    - :ref:`module-pw_chrono_freertos`
61  * - ThreadX
62    - :ref:`module-pw_chrono_threadx`
63  * - embOS
64    - :ref:`module-pw_chrono_embos`
65  * - STL
66    - :ref:`module-pw_chrono_stl`
67  * - Zephyr
68    - Planned
69  * - CMSIS-RTOS API v2 & RTX5
70    - Planned
71  * - Baremetal
72    - Planned
73
74
75System Clock
76============
77For RTOS and HAL interactions, we provide a ``pw::chrono::SystemClock`` facade
78which provides 64 bit timestamps and duration support along with a C API. For
79C++ there is an optional virtual wrapper, ``pw::chrono::VirtualSystemClock``,
80around the singleton clock facade to enable dependency injection.
81
82.. code-block:: cpp
83
84  #include <chrono>
85
86  #include "pw_thread/sleep.h"
87
88  using namespace std::literals::chrono_literals;
89
90  void ThisSleeps() {
91    pw::thread::sleep_for(42ms);
92  }
93
94Unlike the STL's time bound templated APIs which are not specific to a
95particular clock, Pigweed's time bound APIs are strongly typed to use the
96``pw::chrono::SystemClock``'s ``duration`` and ``time_points`` directly.
97
98.. code-block:: cpp
99
100  #include "pw_chrono/system_clock.h"
101
102  bool HasThisPointInTimePassed(const SystemClock::time_point timestamp) {
103    return SystemClock::now() > timestamp;
104  }
105
106--------------------------
107Synchronization Primitives
108--------------------------
109The :ref:`module-pw_sync` provides the building blocks for synchronizing between
110threads and/or interrupts through signaling primitives and critical section lock
111primitives.
112
113Critical Section Lock Primitives
114================================
115Pigweed's locks support Clang's thread safety lock annotations and the STL's
116RAII helpers.
117
118.. list-table::
119
120  * - **Supported On**
121    - **Mutex**
122    - **TimedMutex**
123    - **InterruptSpinLock**
124  * - FreeRTOS
125    - :ref:`module-pw_sync_freertos`
126    - :ref:`module-pw_sync_freertos`
127    - :ref:`module-pw_sync_freertos`
128  * - ThreadX
129    - :ref:`module-pw_sync_threadx`
130    - :ref:`module-pw_sync_threadx`
131    - :ref:`module-pw_sync_threadx`
132  * - embOS
133    - :ref:`module-pw_sync_embos`
134    - :ref:`module-pw_sync_embos`
135    - :ref:`module-pw_sync_embos`
136  * - STL
137    - :ref:`module-pw_sync_stl`
138    - :ref:`module-pw_sync_stl`
139    - :ref:`module-pw_sync_stl`
140  * - Zephyr
141    - Planned
142    - Planned
143    - Planned
144  * - CMSIS-RTOS API v2 & RTX5
145    - Planned
146    - Planned
147    - Planned
148  * - Baremetal
149    - Planned, not ready for use
150    - ✗
151    - Planned, not ready for use
152
153
154Thread Safe Mutex
155-----------------
156The ``pw::sync::Mutex`` protects shared data from being simultaneously accessed
157by multiple threads. Optionally, the ``pw::sync::TimedMutex`` can be used as an
158extension with timeout and deadline based semantics.
159
160.. code-block:: cpp
161
162  #include <mutex>
163
164  #include "pw_sync/mutex.h"
165
166  pw::sync::Mutex mutex;
167
168  void ThreadSafeCriticalSection() {
169    std::lock_guard lock(mutex);
170    NotThreadSafeCriticalSection();
171  }
172
173Interrupt Safe InterruptSpinLock
174--------------------------------
175The ``pw::sync::InterruptSpinLock`` protects shared data from being
176simultaneously accessed by multiple threads and/or interrupts as a targeted
177global lock, with the exception of Non-Maskable Interrupts (NMIs). Unlike global
178interrupt locks, this also works safely and efficiently on SMP systems.
179
180.. code-block:: cpp
181
182  #include <mutex>
183
184  #include "pw_sync/interrupt_spin_lock.h"
185
186  pw::sync::InterruptSpinLock interrupt_spin_lock;
187
188  void InterruptSafeCriticalSection() {
189    std::lock_guard lock(interrupt_spin_lock);
190    NotThreadSafeCriticalSection();
191  }
192
193Signaling Primitives
194====================
195Native signaling primitives tend to vary more compared to critical section locks
196across different platforms. For example, although common signaling primitives
197like semaphores are in most if not all RTOSes and even POSIX, it was not in the
198STL before C++20. Likewise many C++ developers are surprised that conditional
199variables tend to not be natively supported on RTOSes. Although you can usually
200build any signaling primitive based on other native signaling primitives,
201this may come with non-trivial added overhead in ROM, RAM, and execution
202efficiency.
203
204For this reason, Pigweed intends to provide some simpler signaling primitives
205which exist to solve a narrow programming need but can be implemented as
206efficiently as possible for the platform that it is used on. This simpler but
207highly portable class of signaling primitives is intended to ensure that a
208portability efficiency tradeoff does not have to be made up front.
209
210.. list-table::
211
212  * - **Supported On**
213    - **ThreadNotification**
214    - **TimedThreadNotification**
215    - **CountingSemaphore**
216    - **BinarySemaphore**
217  * - FreeRTOS
218    - :ref:`module-pw_sync_freertos`
219    - :ref:`module-pw_sync_freertos`
220    - :ref:`module-pw_sync_freertos`
221    - :ref:`module-pw_sync_freertos`
222  * - ThreadX
223    - :ref:`module-pw_sync_threadx`
224    - :ref:`module-pw_sync_threadx`
225    - :ref:`module-pw_sync_threadx`
226    - :ref:`module-pw_sync_threadx`
227  * - embOS
228    - :ref:`module-pw_sync_embos`
229    - :ref:`module-pw_sync_embos`
230    - :ref:`module-pw_sync_embos`
231    - :ref:`module-pw_sync_embos`
232  * - STL
233    - :ref:`module-pw_sync_stl`
234    - :ref:`module-pw_sync_stl`
235    - :ref:`module-pw_sync_stl`
236    - :ref:`module-pw_sync_stl`
237  * - Zephyr
238    - Planned
239    - Planned
240    - Planned
241    - Planned
242  * - CMSIS-RTOS API v2 & RTX5
243    - Planned
244    - Planned
245    - Planned
246    - Planned
247  * - Baremetal
248    - Planned
249    - ✗
250    - TBD
251    - TBD
252
253Thread Notification
254-------------------
255Pigweed intends to provide the ``pw::sync::ThreadNotification`` and
256``pw::sync::TimedThreadNotification`` facades which permit a single consumer to
257block until an event occurs. This should be backed by the most efficient native
258primitive for a target, regardless of whether that is a semaphore, event flag
259group, condition variable, direct task notification with a critical section, or
260something else.
261
262Counting Semaphore
263------------------
264The ``pw::sync::CountingSemaphore`` is a synchronization primitive that can be
265used for counting events and/or resource management where receiver(s) can block
266on acquire until notifier(s) signal by invoking release.
267
268.. code-block:: cpp
269
270  #include "pw_sync/counting_semaphore.h"
271
272  pw::sync::CountingSemaphore event_semaphore;
273
274  void NotifyEventOccurred() {
275    event_semaphore.release();
276  }
277
278  void HandleEventsForever() {
279    while (true) {
280      event_semaphore.acquire();
281      HandleEvent();
282    }
283  }
284
285Binary Semaphore
286----------------
287The ``pw::sync::BinarySemaphore`` is a specialization of the counting semaphore
288with an arbitrary token limit of 1, meaning it's either full or empty.
289
290.. code-block:: cpp
291
292  #include "pw_sync/binary_semaphore.h"
293
294  pw::sync::BinarySemaphore do_foo_semaphore;
295
296  void NotifyResultReady() {
297    result_ready_semaphore.release();
298  }
299
300  void BlockUntilResultReady() {
301    result_ready_semaphore.acquire();
302  }
303
304--------------------
305Threading Primitives
306--------------------
307The :ref:`module-pw_thread` module provides the building blocks for creating and
308using threads including yielding and sleeping.
309
310.. list-table::
311
312  * - **Supported On**
313    - **Thread Creation**
314    - **Thread Id/Sleep/Yield**
315  * - FreeRTOS
316    - :ref:`module-pw_thread_freertos`
317    - :ref:`module-pw_thread_freertos`
318  * - ThreadX
319    - :ref:`module-pw_thread_threadx`
320    - :ref:`module-pw_thread_threadx`
321  * - embOS
322    - :ref:`module-pw_thread_embos`
323    - :ref:`module-pw_thread_embos`
324  * - STL
325    - :ref:`module-pw_thread_stl`
326    - :ref:`module-pw_thread_stl`
327  * - Zephyr
328    - Planned
329    - Planned
330  * - CMSIS-RTOS API v2 & RTX5
331    - Planned
332    - Planned
333  * - Baremetal
334    - ✗
335    - ✗
336
337Thread Creation
338===============
339The ``pw::thread::Thread``’s API is C++11 STL ``std::thread`` like. Unlike
340``std::thread``, the Pigweed's API requires ``pw::thread::Options`` as an
341argument for creating a thread. This is used to give the user full control over
342the native OS's threading options without getting in your way.
343
344.. code-block:: cpp
345
346  #include "pw_thread/detached_thread.h"
347  #include "pw_thread_freertos/context.h"
348  #include "pw_thread_freertos/options.h"
349
350  pw::thread::freertos::ContextWithStack<42> example_thread_context;
351
352  void StartDetachedExampleThread() {
353     pw::thread::DetachedThread(
354       pw::thread::freertos::Options()
355           .set_name("static_example_thread")
356           .set_priority(kFooPriority)
357           .set_static_context(example_thread_context),
358       example_thread_function);
359  }
360
361Controlling the current thread
362==============================
363Beyond thread creation, Pigweed offers support for sleeping, identifying, and
364yielding the current thread.
365
366.. code-block:: cpp
367
368  #include "pw_thread/yield.h"
369
370  void CooperativeBusyLooper() {
371    while (true) {
372      DoChunkOfWork();
373      pw::this_thread::yield();
374    }
375  }
376
377------------------
378Execution Contexts
379------------------
380Code runs in *execution contexts*. Common examples of execution contexts on
381microcontrollers are **thread context** and **interrupt context**, though there
382are others. Since OS abstractions deal with concurrency, it's important to
383understand what API primitives are safe to call in what contexts.  Since the
384number of execution contexts is too large for Pigweed to cover exhaustively,
385Pigweed has the following classes of APIs:
386
387**Thread Safe APIs** - These APIs are safe to use in any execution context where
388one can use blocking or yielding APIs such as sleeping, blocking on a mutex
389waiting on a semaphore.
390
391**Interrupt (IRQ) Safe APIs** - These APIs can be used in any execution context
392which cannot use blocking and yielding APIs. These APIs must protect themselves
393from preemption from maskable interrupts, etc. This includes critical section
394thread contexts in addition to "real" interrupt contexts. Our definition
395explicitly excludes any interrupts which are not masked when holding a SpinLock,
396those are all considered non-maskable interrupts. An interrupt safe API may
397always be safely used in a context which permits thread safe APIs.
398
399**Non-Maskable Interrupt (NMI) Safe APIs** - Like the Interrupt Safe APIs, these
400can be used in any execution context which cannot use blocking or yielding APIs.
401In addition, these may be used by interrupts which are not masked when for
402example holding a SpinLock like CPU exceptions or C++/POSIX signals. These tend
403to come with significant overhead and restrictions compared to regular interrupt
404safe APIs as they **cannot rely on critical sections**, instead
405only atomic signaling can be used. An interrupt safe API may always be
406used in a context which permits interrupt safe and thread safe APIs.
407
408On naming
409=========
410Instead of having context specific APIs like FreeRTOS's ``...FromISR()``,
411Pigweed has a single API which validates the context requirements through
412``DASSERT`` and ``DCHECK`` in the backends (user configurable). We did this for
413a few reasons:
414
415#. **Too many contexts** - Since there are contexts beyond just thread,
416   interrupt, and NMI, having context-specific APIs would be a hard to
417   maintain. The proliferation of postfixed APIs (``...FromISR``,
418   ``...FromNMI``, ``...FromThreadCriticalSection``, and so on) would also be
419   confusing for users.
420
421#. **Must verify context anyway** - Backends are required to enforce context
422   requirements with ``DCHECK`` or related calls, so we chose a simple API
423   which happens to match both the C++'s STL and Google's Abseil.
424
425#. **Multi-context code** - Code running in multiple contexts would need to be
426   duplicated for each context if the APIs were postfixed, or duplicated with
427   macros. The authors chose the duplication/macro route in previous projects
428   and found it clunky and hard to maintain.
429
430-----------------------------
431Construction & Initialization
432-----------------------------
433**TL;DR: Pigweed OS primitives are initialized through C++ construction.**
434
435We have chosen to go with a model which initializes the synchronization
436primitive during C++ object construction. This means that there is a requirement
437in order for static instantiation to be safe that the user ensures that any
438necessary kernel and/or platform initialization is done before the global static
439constructors are run which would include construction of the C++ synchronization
440primitives.
441
442In addition this model for now assumes that Pigweed code will always be used to
443construct synchronization primitives used with Pigweed modules. Note that with
444this model the backend provider can decide if they want to statically
445preallocate space for the primitives or rely on dynamic allocation strategies.
446If we discover at a later point that this is not sufficiently portable than we
447can either produce an optional constructor that takes in a reference to an
448existing native synchronization type and wastes a little bit RAM or we can
449refactor the existing class into two layers where one is a StaticMutex for
450example and the other is a Mutex which only holds a handle to the native mutex
451type. This would then permit users who cannot construct their synchronization
452primitives to skip the optional static layer.
453
454Kernel / Platform Initialization Before C++ Global Static Constructors
455======================================================================
456What is this kernel and/or platform initialization that must be done first?
457
458It's not uncommon for an RTOS to require some initialization functions to be
459invoked before more of its API can be safely used. For example for CMSIS RTOSv2
460``osKernelInitialize()`` must be invoked before anything but two basic getters
461are called. Similarly, Segger's embOS requires ``OS_Init()`` to be invoked first
462before any other embOS API.
463
464.. Note::
465  To get around this one should invoke these initialization functions earlier
466  and/or delay the static C++ constructors to meet this ordering requirement. As
467  an example if you were using :ref:`module-pw_boot_cortex_m`, then
468  ``pw_boot_PreStaticConstructorInit()`` would be a great place to invoke kernel
469  initialization.
470
471-------
472Roadmap
473-------
474Pigweed is still actively expanding and improving its OS Abstraction Layers.
475That being said, the following concrete areas are being worked on and can be
476expected to land at some point in the future:
477
4781. We'd like to offer a system clock based timer abstraction facade which can be
479   used on either an RTOS or a hardware timer.
4802. We are evaluating a less-portable but very useful portability facade for
481   event flags / groups. This would make it even easier to ensure all firmware
482   can be fully executed on the host.
4833. Cooperative cancellation thread joining along with a ``std::jthread`` like
484   wrapper is in progress.
4854. We'd like to add support for queues, message queues, and similar channel
486   abstractions which also support interprocessor communication in a transparent
487   manner.
4885. We're interested in supporting asynchronous worker queues and worker queue
489   pools.
4906. Migrate HAL and similar APIs to use deadlines for the backend virtual
491   interfaces to permit a smaller vtable which supports both public timeout and
492   deadline semantics.
4937. Baremetal support is partially in place today, but it's not ready for use.
4948. Most of our APIs today are focused around synchronous blocking APIs, however
495   we would love to extend this to include asynchronous APIs.
496