blob: 94969d5963ae530cd3b94b596760facbd7cf905b [file] [log] [blame]
.. _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 singler 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, or direct task notification with a critical section
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 abstactions 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-specefic 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 requried to enforce context
requirements with ``DHCECK`` 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.