Threading & Synchronization Primitives
Relevant source files
The following files were used as context for generating this wiki page:
The Zephyr RTOS provides a robust set of threading and synchronization primitives designed to manage resource access and communication in a resource-constrained environment. These primitives facilitate thread separation, memory protection, and secure execution, ensuring that individual threads operate within defined permissions. The architecture follows a monolithic design where the kernel and application services are compiled into a single static binary, but execution is partitioned into privileged and unprivileged environments to enhance system security.
Synchronization in Zephyr is built upon core kernel objects such as semaphores, mutexes, and message queues. These objects are integrated with the kernel's scheduler to manage thread states (e.g., waiting or ready) and ensure predictable behavior during context switches. The system utilizes hardware-enforced memory protection and stack guards to detect overruns and prevent unauthorized access between threads.
Sources: doc/security/security-overview.rst, doc/security/secure-coding.rst
Thread Execution and Protection
Zephyr's threading model emphasizes the principle of Least Privilege, where each thread is granted the minimum subset of permissions required for its task. This is achieved through thread separation and memory partitioning, which are enforced at the time of context switching.
Memory and Stack Protection
Execution protection is categorized into several distinct tasks to prevent security violations:
- Memory Separation: Memory is partitioned into regions with specific attributes based on ownership. Threads only have access to the regions they control.
- Stack Protection: Stack guards provide mechanisms to detect and trap stack overruns, ensuring threads cannot access the stacks of others.
- Privilege Levels: The system can be split into privileged and unprivileged execution environments, limiting the potential impact of a compromised thread.
Sources: doc/security/security-overview.rst:114-142, doc/security/secure-coding.rst:67-71
The diagram shows the separation between privileged kernel services and unprivileged user threads, with access between threads restricted by the kernel.
Synchronization Primitives
Semaphores
Semaphores are used for signaling and resource counting. They allow threads to wait for a signal from another thread or an Interrupt Service Routine (ISR). A semaphore can be "given" (incremented) to wake a waiting thread or "taken" (decremented) to acquire a resource.
Sources: kernel/sem.c, doc/project/documentation.rst:69-82
| API Function | Description | Context Allowed |
|---|---|---|
k_sem_give | Increments the semaphore count and wakes a waiting thread. | Thread, ISR |
k_sem_take | Decrements the count or waits until the semaphore is available. | Thread |
Mutexes
Mutexes provide mutual exclusion to shared resources. Unlike simple semaphores, mutexes in Zephyr often implement priority inheritance to prevent priority inversion, where a lower-priority thread holding a mutex prevents a higher-priority thread from executing.
Sources: kernel/mutex.c, doc/develop/languages/cpp/index.rst:55-57
Message Queues
Message queues allow threads to exchange data asynchronously. They consist of a ring buffer that stores messages of a fixed size.
Sources: kernel/msg_q.c
This sequence illustrates the flow of data between two threads using a kernel message queue.
Language Support and Implementation
Zephyr supports both C and C++ for application development, though C++ usage is restricted to application-level code and is not used for core kernel or driver initialization.
C++ Restrictions
While Zephyr supports a subset of C++ (including inheritance, virtual functions, and the STL), it does not support OS-specific C++ standard library classes like std::thread or std::mutex directly. Developers must instead use the Zephyr kernel APIs for these functionalities to ensure proper integration with the RTOS scheduler and memory protection schemes.
Sources: doc/develop/languages/cpp/index.rst:49-65
Initialization Flow
Static global object constructors in C++ are initialized after drivers are ready but before the application's main() function is called. This ensures that any synchronization primitives used within a global object have a functional kernel environment to rely upon.
Sources: doc/develop/languages/cpp/index.rst:67-71
Summary
Threading and synchronization in Zephyr are defined by a "Secure by Default" paradigm. By combining hardware-enforced memory protection with robust software primitives like mutexes and semaphores, the system ensures that threads operate safely and predictably. The monolithic architecture simplifies the system by removing the need for dynamic loaders while utilizing granular configuration options (Kconfig) to disable unused services and reduce the attack surface.
Sources: doc/security/security-overview.rst, doc/security/secure-coding.rst