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