Advantages & differences#

Pigweed’s OS abstraction layers (pw_sync, pw_thread, and pw_chrono) provide type-safe C++ interfaces with robust compile-time and runtime checking. When targeting Zephyr, these APIs offer significant design and safety advantages over raw native Zephyr APIs.

Synchronization vs Zephyr#

Note

For internal design details, configuration options, and constraints, see the pw_sync_zephyr backend documentation.

C++ RAII and safety guardrails#

Zephyr’s native synchronization APIs (such as k_mutex, k_sem, and k_event) are raw C-level interfaces. They do not natively support C++ Resource Acquisition Is Initialization (RAII) conventions, compile-time thread safety auditing, or object-oriented lifetime management.

Pigweed’s pw::sync::Mutex and pw::sync::TimedMutex wrap Zephyr’s native primitives into C++ classes conforming to standard C++ BasicLockable requirements. This adds several safety guardrails:

  • RAII compatibility: Enables standard C++ lock managers (such as std::lock_guard and std::unique_lock) to manage lock lifetimes automatically, preventing deadlocks caused by forgotten unlocks on early return paths.

  • Strict non-recursive locking: Zephyr’s native k_mutex supports reentrancy (recursive locking) by default, which can hide structural design flaws. Pigweed’s Mutex enforces strict non-recursive semantics, asserting against recursive lock attempts in debug builds via PW_DASSERT(native_type_.lock_count == 1).

  • Interrupt-context guardrails: Locking or unlocking a mutex within an interrupt handler causes undefined behavior. Pigweed’s Mutex asserts that it is not called from interrupt context via PW_DASSERT(!interrupt::InInterruptContext()).

  • Unlock assertions: Pigweed asserts that releasing the lock succeeded via PW_ASSERT(k_mutex_unlock(&native_type_) == 0).

Compile-time lock safety via thread-safety annotations#

Native Zephyr mutexes are C-level structures with no integration with C++ compile-time analysis tools. The compiler cannot verify whether a shared variable is accessed under the correct lock, nor can it enforce consistent lock acquisition orders, making data races and deadlocks easy to introduce.

Pigweed’s C++ synchronization wrappers (such as pw::sync::Mutex and pw::sync::InterruptSpinLock) integrate natively with Clang’s static thread safety analysis. By annotating members with attributes like PW_GUARDED_BY and methods with PW_EXCLUSIVE_LOCKS_REQUIRED, the compiler statically checks and warns against missing locks or improper release sequences at compile-time.

For detailed configuration requirements and lists of supported lock safety macros, refer to the thread-safety lock annotations documentation.

Port-safe interrupt spinlocks#

Pigweed’s pw::sync::InterruptSpinLock provides a unified, port-safe spinlock implementation wrapping Zephyr’s native k_spinlock. It prevents accidental recursive locking inside the same execution context through debug assertions, providing a consistent and safe critical-section mechanism across all targets.

Timeout overflow protection#

Zephyr timeouts are typically represented as ticks or milliseconds. If a relative timeout exceeds the maximum value representable by the native kernel type (especially on configurations without 64-bit timeouts), it can overflow or be truncated.

Pigweed’s timed synchronization APIs (such as TimedMutex::try_lock_for) automatically detect durations that exceed native tick limits and split them into smaller loop iterations under the hood, ensuring arbitrarily long timeouts behave correctly.

Sleeping vs Zephyr#

Note

For details on thread options, stack allocations, and iteration, see the pw_thread_zephyr backend documentation.

Safe sleep and yield APIs#

In native Zephyr, calling sleep or yield functions from an interrupt context leads to crashes or undefined kernel state.

Pigweed’s pw::this_thread::sleep_for(), sleep_until(), and pw::thread::yield() enforce safety by asserting they are only invoked from thread contexts via PW_DCHECK(get_id() != Thread::id()).

Safe arbitrarily long durations#

Similar to timed synchronization primitives, Pigweed’s sleep APIs automatically split relative sleep timeouts exceeding kernel limits into loop iterations, preventing numeric overflow of ticks.

Time & timers vs Zephyr#

Note

For configuration requirements and operational expectations, see the pw_chrono_zephyr backend documentation.

Non-overflowing system clock#

Zephyr’s native tick counter (obtained via k_uptime_ticks()) returns a signed 64-bit integer (int64_t). At standard tick rates, this counter will not overflow for millions of years.

Pigweed’s pw::chrono::SystemClock maps directly to this signed 64-bit tick counter, providing a portable standard interface for time arithmetic without the wraparound and overflow issues common to platforms with 32-bit native clocks.

Race-free system timer destructor#

Native Zephyr k_timer structures are stopped via k_timer_stop(). However, if the timer’s callback is currently executing in interrupt context (ISR) when the timer is stopped or destroyed, it can trigger race conditions or use-after-free bugs in application code.

Pigweed’s pw::chrono::SystemTimer backend schedules its callbacks using Zephyr’s delayable work-queue API (k_work_delayable). This allows the SystemTimer destructor to call k_work_cancel_delayable_sync(), which cancels the work item and blocks until any currently running callback completes. This guarantees that once the timer object is destroyed, the callback is definitively inactive, eliminating a common class of RTOS concurrency bugs. Note that while callbacks run in a work queue thread-context to support synchronous cancellation, they must still conform to the SystemTimer contract and never attempt to block.

Asserts & logging vs Zephyr#

Note

For configuration details on assertion and logging routing, see the pw_assert_zephyr and pw_log_zephyr backend documentation.

Stable and multi-language tokenization#

Zephyr offers a dictionary-based logging option for format string compression. However, Zephyr’s tokenization is based on mapping the memory addresses of format strings within the compiled binary. Consequently, any code change or recompilation shifts these addresses, creating a new token database that must exactly match the specific binary. Managing these ephemeral databases across many released firmware versions is challenging, and host-side decoding is mostly limited to Zephyr’s own Python tooling.

In contrast, Pigweed’s pw_tokenizer uses stable string hashes computed directly from the format string content:

  • Stable token databases: Recompiling the binary or changing unrelated code does not invalidate the token database. Adding or modifying a log statement only adds new entries to the database without altering existing ones. This allows you to maintain a single, cumulative token database across all historical builds.

  • Broad language support: Pigweed provides official detokenization libraries in C++, Python, Java, and JavaScript. This enables host applications, web interfaces, and mobile companion apps (such as an Android or iOS app receiving logs over Bluetooth) to easily decode log streams directly.

Portable runtime vs. Zephyr OS simulator#

Zephyr provides a host execution model via its native_sim (or native_posix) targets. However, this target compiles the entire Zephyr kernel, scheduler, and subsystems into a simulated host executable. It is designed solely for testing and simulation, and is not suitable for running or shipping to production on host platforms. It also requires the entire Zephyr build stack and host-side Python tooling.

In contrast, Pigweed’s OS abstraction layers are designed from the beginning as a porting layer, intended to run in production across multiple underlying operating systems, and multiple build systems. When targeting host platforms (macOS, Windows, or Linux), Pigweed resolves these facades to standard C++ STL backends (such as std::mutex and std::thread) with minimal dependencies.

This approach allows you to compile and run your core application logic on host platforms with near-zero overhead, and deploy that same code to production on host systems (e.g., as part of a companion application or server backend)—a workflow that is not possible with Zephyr’s native simulation.

For example, pw_rpc has shipped to millions of production devices running inside a Linux application (as part of a larger project). A Zephyr-centric solution cannot easily be ported to these production host environments due to its heavy dependence on Zephyr’s custom build system (West) and simulated kernel architecture.

Other functionality#

Some native Zephyr features are not wrapped by generic facades in Pigweed. In most cases, Pigweed offers modern, target-agnostic C++ alternatives that should be used instead:

Events (k_event) and polling (k_poll)#

Pigweed does not provide a multi-bit event-flag or poll facade. Instead, developers can achieve similar signaling and waiting behaviors using:

  • pw::sync::ThreadNotification: For simple thread unblocking/signaling.

  • pw::sync::BinarySemaphore / pw::sync::CountingSemaphore: For signaling and resource sharing.

  • Cooperative multitasking via pw_async2: For waiting on multiple asynchronous resources or events without blocking threads.

Message queues (k_msgq) and mailboxes (k_mbox)#

Pigweed does not wrap Zephyr’s message queues or mailboxes. Instead, developers should use Pigweed’s type-safe and allocation-free containers and utilities:

  • pw_containers: For standard queues, circular buffers, or deques.

  • pw::work_queue::WorkQueue: For offloading tasks to a thread.

  • pw::ring_buffer::Reader: For circular byte-buffers.

  • pw_rpc: For robust, structured, and cross-process/cross-device messaging.

FIFO and LIFO (k_fifo / k_lifo)#

Zephyr’s C-style FIFO and LIFO queues are not wrapped by Pigweed. Developers should prefer standard, type-safe C++ container classes (e.g., those in pw_containers) to avoid raw pointer casting.

Condition variables (k_condvar)#

Pigweed does not provide a ConditionVariable facade. Synchronization should be handled via:

  • pw::sync::ThreadNotification: For waiting on a single event.

  • Primitives from pw_async2: For more complex cooperative waiting and signaling.

Repeating/periodic software timers#

pw::chrono::SystemTimer is exclusively a one-shot timer. Pigweed does not wrap Zephyr’s native periodic or auto-reload software timers. Periodic timer behavior should be implemented by rescheduling the SystemTimer from within its expiry callback.