| .. _docs-os_abstraction_layers: |
| |
| ===================== |
| OS Abstraction Layers |
| ===================== |
| Pigweed’s operating system abstraction layers are portable and configurable |
| building blocks, giving users full control while maintaining high performance |
| and low overhead. |
| |
| Although we primarily target smaller-footprint MMU-less 32-bit microcontrollers, |
| the OS abstraction layers are written to work on everything from single-core |
| bare metal low end microcontrollers to asymmetric multiprocessing (AMP) and |
| symmetric multiprocessing (SMP) embedded systems using Real Time Operating |
| Systems (RTOS). They even fully work on your developer workstation on Linux, |
| Windows, or MacOS! |
| |
| Pigweed has ports for the following systems: |
| |
| .. list-table:: |
| |
| * - **Environment** |
| - **Status** |
| * - STL (Mac, Window, & Linux) |
| - **✔ Supported** |
| * - `FreeRTOS <https://www.freertos.org/>`_ |
| - **✔ Supported** |
| * - `Azure RTOS (formerly ThreadX) <https://azure.microsoft.com/en-us/services/rtos/>`_ |
| - **✔ Supported** |
| * - `SEGGER embOS <https://www.segger.com/products/rtos/embos/>`_ |
| - **✔ Supported** |
| * - Baremetal |
| - *In Progress* |
| * - `Zephyr <https://www.zephyrproject.org/>`_ |
| - Planned |
| * - `CMSIS-RTOS API v2 & RTX5 <https://www.keil.com/pack/doc/CMSIS/RTOS2/html/index.html>`_ |
| - Planned |
| |
| Pigweed's OS abstraction layers are divided by the **functional grouping of the |
| primitives**. Many of our APIs are similar or **nearly identical to C++'s |
| Standard Template Library (STL)** with the notable exception that we do not |
| support exceptions. We opted to follow the STL's APIs partially because they |
| are relatively well thought out and many developers are already familiar with |
| them, but also because this means they are compatible with existing helpers in |
| the STL; for example, ``std::lock_guard``. |
| |
| --------------- |
| Time Primitives |
| --------------- |
| The :ref:`module-pw_chrono` module provides the building blocks for expressing |
| durations, timestamps, and acquiring the current time. This in turn is used by |
| other modules, including :ref:`module-pw_sync` and :ref:`module-pw_thread` as |
| the basis for any time bound APIs (i.e. with timeouts and/or deadlines). Note |
| that this module is optional and bare metal targets may opt not to use this. |
| |
| .. list-table:: |
| |
| * - **Supported On** |
| - **SystemClock** |
| * - FreeRTOS |
| - :ref:`module-pw_chrono_freertos` |
| * - ThreadX |
| - :ref:`module-pw_chrono_threadx` |
| * - embOS |
| - :ref:`module-pw_chrono_embos` |
| * - STL |
| - :ref:`module-pw_chrono_stl` |
| * - Zephyr |
| - Planned |
| * - CMSIS-RTOS API v2 & RTX5 |
| - Planned |
| * - Baremetal |
| - Planned |
| |
| |
| System Clock |
| ============ |
| For RTOS and HAL interactions, we provide a ``pw::chrono::SystemClock`` facade |
| which provides 64 bit timestamps and duration support along with a C API. For |
| C++ there is an optional virtual wrapper, ``pw::chrono::VirtualSystemClock``, |
| around the singleton clock facade to enable dependency injection. |
| |
| .. code-block:: cpp |
| |
| #include <chrono> |
| |
| #include "pw_thread/sleep.h" |
| |
| using namespace std::literals::chrono_literals; |
| |
| void ThisSleeps() { |
| pw::thread::sleep_for(42ms); |
| } |
| |
| Unlike the STL's time bound templated APIs which are not specific to a |
| particular clock, Pigweed's time bound APIs are strongly typed to use the |
| ``pw::chrono::SystemClock``'s ``duration`` and ``time_points`` directly. |
| |
| .. code-block:: cpp |
| |
| #include "pw_chrono/system_clock.h" |
| |
| bool HasThisPointInTimePassed(const SystemClock::time_point timestamp) { |
| return SystemClock::now() > timestamp; |
| } |
| |
| -------------------------- |
| Synchronization Primitives |
| -------------------------- |
| The :ref:`module-pw_sync` provides the building blocks for synchronizing between |
| threads and/or interrupts through signaling primitives and critical section lock |
| primitives. |
| |
| Critical Section Lock Primitives |
| ================================ |
| Pigweed's locks support Clang's thread safety lock annotations and the STL's |
| RAII helpers. |
| |
| .. list-table:: |
| |
| * - **Supported On** |
| - **Mutex** |
| - **TimedMutex** |
| - **InterruptSpinLock** |
| * - FreeRTOS |
| - :ref:`module-pw_sync_freertos` |
| - :ref:`module-pw_sync_freertos` |
| - :ref:`module-pw_sync_freertos` |
| * - ThreadX |
| - :ref:`module-pw_sync_threadx` |
| - :ref:`module-pw_sync_threadx` |
| - :ref:`module-pw_sync_threadx` |
| * - embOS |
| - :ref:`module-pw_sync_embos` |
| - :ref:`module-pw_sync_embos` |
| - :ref:`module-pw_sync_embos` |
| * - STL |
| - :ref:`module-pw_sync_stl` |
| - :ref:`module-pw_sync_stl` |
| - :ref:`module-pw_sync_stl` |
| * - Zephyr |
| - Planned |
| - Planned |
| - Planned |
| * - CMSIS-RTOS API v2 & RTX5 |
| - Planned |
| - Planned |
| - Planned |
| * - Baremetal |
| - Planned, not ready for use |
| - ✗ |
| - Planned, not ready for use |
| |
| |
| Thread Safe Mutex |
| ----------------- |
| The ``pw::sync::Mutex`` protects shared data from being simultaneously accessed |
| by multiple threads. Optionally, the ``pw::sync::TimedMutex`` can be used as an |
| extension with timeout and deadline based semantics. |
| |
| .. code-block:: cpp |
| |
| #include <mutex> |
| |
| #include "pw_sync/mutex.h" |
| |
| pw::sync::Mutex mutex; |
| |
| void ThreadSafeCriticalSection() { |
| std::lock_guard lock(mutex); |
| NotThreadSafeCriticalSection(); |
| } |
| |
| Interrupt Safe InterruptSpinLock |
| -------------------------------- |
| The ``pw::sync::InterruptSpinLock`` protects shared data from being |
| simultaneously accessed by multiple threads and/or interrupts as a targeted |
| global lock, with the exception of Non-Maskable Interrupts (NMIs). Unlike global |
| interrupt locks, this also works safely and efficiently on SMP systems. |
| |
| .. code-block:: cpp |
| |
| #include <mutex> |
| |
| #include "pw_sync/interrupt_spin_lock.h" |
| |
| pw::sync::InterruptSpinLock interrupt_spin_lock; |
| |
| void InterruptSafeCriticalSection() { |
| std::lock_guard lock(interrupt_spin_lock); |
| NotThreadSafeCriticalSection(); |
| } |
| |
| Signaling Primitives |
| ==================== |
| Native signaling primitives tend to vary more compared to critical section locks |
| across different platforms. For example, although common signaling primitives |
| like semaphores are in most if not all RTOSes and even POSIX, it was not in the |
| STL before C++20. Likewise many C++ developers are surprised that conditional |
| variables tend to not be natively supported on RTOSes. Although you can usually |
| build any signaling primitive based on other native signaling primitives, |
| this may come with non-trivial added overhead in ROM, RAM, and execution |
| efficiency. |
| |
| For this reason, Pigweed intends to provide some simpler signaling primitives |
| which exist to solve a narrow programming need but can be implemented as |
| efficiently as possible for the platform that it is used on. This simpler but |
| highly portable class of signaling primitives is intended to ensure that a |
| portability efficiency tradeoff does not have to be made up front. |
| |
| .. list-table:: |
| |
| * - **Supported On** |
| - **ThreadNotification** |
| - **TimedThreadNotification** |
| - **CountingSemaphore** |
| - **BinarySemaphore** |
| * - FreeRTOS |
| - :ref:`module-pw_sync_freertos` |
| - :ref:`module-pw_sync_freertos` |
| - :ref:`module-pw_sync_freertos` |
| - :ref:`module-pw_sync_freertos` |
| * - ThreadX |
| - :ref:`module-pw_sync_threadx` |
| - :ref:`module-pw_sync_threadx` |
| - :ref:`module-pw_sync_threadx` |
| - :ref:`module-pw_sync_threadx` |
| * - embOS |
| - :ref:`module-pw_sync_embos` |
| - :ref:`module-pw_sync_embos` |
| - :ref:`module-pw_sync_embos` |
| - :ref:`module-pw_sync_embos` |
| * - STL |
| - :ref:`module-pw_sync_stl` |
| - :ref:`module-pw_sync_stl` |
| - :ref:`module-pw_sync_stl` |
| - :ref:`module-pw_sync_stl` |
| * - Zephyr |
| - Planned |
| - Planned |
| - Planned |
| - Planned |
| * - CMSIS-RTOS API v2 & RTX5 |
| - Planned |
| - Planned |
| - Planned |
| - Planned |
| * - Baremetal |
| - Planned |
| - ✗ |
| - TBD |
| - TBD |
| |
| Thread Notification |
| ------------------- |
| Pigweed intends to provide the ``pw::sync::ThreadNotification`` and |
| ``pw::sync::TimedThreadNotification`` facades which permit a single consumer to |
| block until an event occurs. This should be backed by the most efficient native |
| primitive for a target, regardless of whether that is a semaphore, event flag |
| group, condition variable, direct task notification with a critical section, or |
| something else. |
| |
| Counting Semaphore |
| ------------------ |
| The ``pw::sync::CountingSemaphore`` is a synchronization primitive that can be |
| used for counting events and/or resource management where receiver(s) can block |
| on acquire until notifier(s) signal by invoking release. |
| |
| .. code-block:: cpp |
| |
| #include "pw_sync/counting_semaphore.h" |
| |
| pw::sync::CountingSemaphore event_semaphore; |
| |
| void NotifyEventOccurred() { |
| event_semaphore.release(); |
| } |
| |
| void HandleEventsForever() { |
| while (true) { |
| event_semaphore.acquire(); |
| HandleEvent(); |
| } |
| } |
| |
| Binary Semaphore |
| ---------------- |
| The ``pw::sync::BinarySemaphore`` is a specialization of the counting semaphore |
| with an arbitrary token limit of 1, meaning it's either full or empty. |
| |
| .. code-block:: cpp |
| |
| #include "pw_sync/binary_semaphore.h" |
| |
| pw::sync::BinarySemaphore do_foo_semaphore; |
| |
| void NotifyResultReady() { |
| result_ready_semaphore.release(); |
| } |
| |
| void BlockUntilResultReady() { |
| result_ready_semaphore.acquire(); |
| } |
| |
| -------------------- |
| Threading Primitives |
| -------------------- |
| The :ref:`module-pw_thread` module provides the building blocks for creating and |
| using threads including yielding and sleeping. |
| |
| .. list-table:: |
| |
| * - **Supported On** |
| - **Thread Creation** |
| - **Thread Id/Sleep/Yield** |
| * - FreeRTOS |
| - :ref:`module-pw_thread_freertos` |
| - :ref:`module-pw_thread_freertos` |
| * - ThreadX |
| - :ref:`module-pw_thread_threadx` |
| - :ref:`module-pw_thread_threadx` |
| * - embOS |
| - :ref:`module-pw_thread_embos` |
| - :ref:`module-pw_thread_embos` |
| * - STL |
| - :ref:`module-pw_thread_stl` |
| - :ref:`module-pw_thread_stl` |
| * - Zephyr |
| - Planned |
| - Planned |
| * - CMSIS-RTOS API v2 & RTX5 |
| - Planned |
| - Planned |
| * - Baremetal |
| - ✗ |
| - ✗ |
| |
| Thread Creation |
| =============== |
| The ``pw::thread::Thread``’s API is C++11 STL ``std::thread`` like. Unlike |
| ``std::thread``, the Pigweed's API requires ``pw::thread::Options`` as an |
| argument for creating a thread. This is used to give the user full control over |
| the native OS's threading options without getting in your way. |
| |
| .. code-block:: cpp |
| |
| #include "pw_thread/detached_thread.h" |
| #include "pw_thread_freertos/context.h" |
| #include "pw_thread_freertos/options.h" |
| |
| pw::thread::freertos::ContextWithStack<42> example_thread_context; |
| |
| void StartDetachedExampleThread() { |
| pw::thread::DetachedThread( |
| pw::thread::freertos::Options() |
| .set_name("static_example_thread") |
| .set_priority(kFooPriority) |
| .set_static_context(example_thread_context), |
| example_thread_function); |
| } |
| |
| Controlling the current thread |
| ============================== |
| Beyond thread creation, Pigweed offers support for sleeping, identifying, and |
| yielding the current thread. |
| |
| .. code-block:: cpp |
| |
| #include "pw_thread/yield.h" |
| |
| void CooperativeBusyLooper() { |
| while (true) { |
| DoChunkOfWork(); |
| pw::this_thread::yield(); |
| } |
| } |
| |
| ------------------ |
| Execution Contexts |
| ------------------ |
| Code runs in *execution contexts*. Common examples of execution contexts on |
| microcontrollers are **thread context** and **interrupt context**, though there |
| are others. Since OS abstractions deal with concurrency, it's important to |
| understand what API primitives are safe to call in what contexts. Since the |
| number of execution contexts is too large for Pigweed to cover exhaustively, |
| Pigweed has the following classes of APIs: |
| |
| **Thread Safe APIs** - These APIs are safe to use in any execution context where |
| one can use blocking or yielding APIs such as sleeping, blocking on a mutex |
| waiting on a semaphore. |
| |
| **Interrupt (IRQ) Safe APIs** - These APIs can be used in any execution context |
| which cannot use blocking and yielding APIs. These APIs must protect themselves |
| from preemption from maskable interrupts, etc. This includes critical section |
| thread contexts in addition to "real" interrupt contexts. Our definition |
| explicitly excludes any interrupts which are not masked when holding a SpinLock, |
| those are all considered non-maskable interrupts. An interrupt safe API may |
| always be safely used in a context which permits thread safe APIs. |
| |
| **Non-Maskable Interrupt (NMI) Safe APIs** - Like the Interrupt Safe APIs, these |
| can be used in any execution context which cannot use blocking or yielding APIs. |
| In addition, these may be used by interrupts which are not masked when for |
| example holding a SpinLock like CPU exceptions or C++/POSIX signals. These tend |
| to come with significant overhead and restrictions compared to regular interrupt |
| safe APIs as they **cannot rely on critical sections**, instead |
| only atomic signaling can be used. An interrupt safe API may always be |
| used in a context which permits interrupt safe and thread safe APIs. |
| |
| On naming |
| ========= |
| Instead of having context specific APIs like FreeRTOS's ``...FromISR()``, |
| Pigweed has a single API which validates the context requirements through |
| ``DASSERT`` and ``DCHECK`` in the backends (user configurable). We did this for |
| a few reasons: |
| |
| #. **Too many contexts** - Since there are contexts beyond just thread, |
| interrupt, and NMI, having context-specific APIs would be a hard to |
| maintain. The proliferation of postfixed APIs (``...FromISR``, |
| ``...FromNMI``, ``...FromThreadCriticalSection``, and so on) would also be |
| confusing for users. |
| |
| #. **Must verify context anyway** - Backends are required to enforce context |
| requirements with ``DCHECK`` or related calls, so we chose a simple API |
| which happens to match both the C++'s STL and Google's Abseil. |
| |
| #. **Multi-context code** - Code running in multiple contexts would need to be |
| duplicated for each context if the APIs were postfixed, or duplicated with |
| macros. The authors chose the duplication/macro route in previous projects |
| and found it clunky and hard to maintain. |
| |
| ----------------------------- |
| Construction & Initialization |
| ----------------------------- |
| **TL;DR: Pigweed OS primitives are initialized through C++ construction.** |
| |
| We have chosen to go with a model which initializes the synchronization |
| primitive during C++ object construction. This means that there is a requirement |
| in order for static instantiation to be safe that the user ensures that any |
| necessary kernel and/or platform initialization is done before the global static |
| constructors are run which would include construction of the C++ synchronization |
| primitives. |
| |
| In addition this model for now assumes that Pigweed code will always be used to |
| construct synchronization primitives used with Pigweed modules. Note that with |
| this model the backend provider can decide if they want to statically |
| preallocate space for the primitives or rely on dynamic allocation strategies. |
| If we discover at a later point that this is not sufficiently portable than we |
| can either produce an optional constructor that takes in a reference to an |
| existing native synchronization type and wastes a little bit RAM or we can |
| refactor the existing class into two layers where one is a StaticMutex for |
| example and the other is a Mutex which only holds a handle to the native mutex |
| type. This would then permit users who cannot construct their synchronization |
| primitives to skip the optional static layer. |
| |
| Kernel / Platform Initialization Before C++ Global Static Constructors |
| ====================================================================== |
| What is this kernel and/or platform initialization that must be done first? |
| |
| It's not uncommon for an RTOS to require some initialization functions to be |
| invoked before more of its API can be safely used. For example for CMSIS RTOSv2 |
| ``osKernelInitialize()`` must be invoked before anything but two basic getters |
| are called. Similarly, Segger's embOS requires ``OS_Init()`` to be invoked first |
| before any other embOS API. |
| |
| .. Note:: |
| To get around this one should invoke these initialization functions earlier |
| and/or delay the static C++ constructors to meet this ordering requirement. As |
| an example if you were using :ref:`module-pw_boot_cortex_m`, then |
| ``pw_boot_PreStaticConstructorInit()`` would be a great place to invoke kernel |
| initialization. |
| |
| ------- |
| Roadmap |
| ------- |
| Pigweed is still actively expanding and improving its OS Abstraction Layers. |
| That being said, the following concrete areas are being worked on and can be |
| expected to land at some point in the future: |
| |
| 1. We'd like to offer a system clock based timer abstraction facade which can be |
| used on either an RTOS or a hardware timer. |
| 2. We are evaluating a less-portable but very useful portability facade for |
| event flags / groups. This would make it even easier to ensure all firmware |
| can be fully executed on the host. |
| 3. Cooperative cancellation thread joining along with a ``std::jthread`` like |
| wrapper is in progress. |
| 4. We'd like to add support for queues, message queues, and similar channel |
| abstractions which also support interprocessor communication in a transparent |
| manner. |
| 5. We're interested in supporting asynchronous worker queues and worker queue |
| pools. |
| 6. Migrate HAL and similar APIs to use deadlines for the backend virtual |
| interfaces to permit a smaller vtable which supports both public timeout and |
| deadline semantics. |
| 7. Baremetal support is partially in place today, but it's not ready for use. |
| 8. Most of our APIs today are focused around synchronous blocking APIs, however |
| we would love to extend this to include asynchronous APIs. |