EmbeddedRelated.com

Semaphore

Category: Rtos | Also known as: semaphores, binary semaphore, counting semaphore

A semaphore is a synchronization primitive that uses an integer counter to control access to shared resources or to signal events between tasks or between an ISR and a task. The counter is manipulated atomically through two operations traditionally called "wait" (decrement, blocking if zero) and "signal" (increment, potentially unblocking a waiting task).

In practice

Semaphores appear in two common forms in embedded RTOS work. A **binary semaphore** has a maximum count of 1 and behaves like a flag: one entity posts it, another pends on it. It is a common pattern for ISR-to-task synchronization -- the ISR calls the signal operation (which is typically ISR-safe in FreeRTOS, CMSIS-RTOS2, and most other RTOSes), and a dedicated task blocks on the wait operation, waking only when the ISR fires. Other primitives such as task notifications, event groups, or queues are also used for this purpose depending on the RTOS and design constraints. A **counting semaphore** has a maximum count greater than 1, making it suitable for tracking a pool of N identical resources (DMA buffers, UART slots, memory blocks) or for counting events that arrive faster than they are consumed.

A semaphore carries no concept of ownership: any task or ISR can signal it (provided the RTOS API offers an ISR-safe post variant and it is used correctly), and any task can wait on it. This makes it the right tool for signaling and resource counting, but the wrong tool for protecting a critical section that must be locked and unlocked by the same entity. Using a semaphore as a mutex introduces the risk of priority inversion with no protocol to resolve it -- most RTOS mutexes implement priority inheritance, and some also support priority ceiling, while semaphores do not. The series "Mutex vs. Semaphore - Part 1" and "Mutex vs. Semaphores – Part 2: The Mutex & Mutual Exclusion Problems" covers this distinction in depth.

A common pitfall on resource-constrained targets (Cortex-M0, 8-bit AVR, PIC) is excessive semaphore use when a simpler mechanism -- a volatile flag or a lock-free ring buffer -- would be sufficient and cheaper. Every semaphore object consumes RAM for its control block and adds scheduler overhead on each pend/post. "You Don't Need an RTOS (Part 3)" discusses scenarios where bare-metal signaling patterns can replace RTOS primitives entirely.

Another pitfall is calling the blocking wait operation from an ISR context. On most RTOSes, the blocking wait/pend call must not be used inside an ISR; only the non-blocking signal/post variant (often named differently, such as `xSemaphoreGiveFromISR` in FreeRTOS) is ISR-safe. Calling the blocking variant from an ISR is invalid behavior whose exact consequence depends on the RTOS and port -- it may trigger an assertion, return an error, deadlock, or cause a fault.

Frequently asked

What is the difference between a binary semaphore and a mutex?
They look similar but have different semantics and intended uses. A binary semaphore is an ownership-free signaling primitive: any task or ISR can post it and any task can pend on it, making it ideal for ISR-to-task or task-to-task event notification. A mutex enforces ownership -- only the task that acquired it may release it -- and on most RTOSes includes a priority-inheritance mechanism to prevent priority inversion. Using a binary semaphore for mutual exclusion is technically possible but dangerous in priority-based schedulers because it lacks these protections. See the blog series 'Mutex vs. Semaphore - Part 1' for a detailed treatment.
Can I call a semaphore wait (pend) from inside an ISR?
No, on virtually all RTOSes the blocking pend/wait call must not be used from an ISR because it may attempt to block the current execution context, which is undefined behavior inside an interrupt handler. ISRs should only use the non-blocking signal/post variant. FreeRTOS, for example, provides separate ISR-safe APIs such as `xSemaphoreGiveFromISR()` and requires that you call `portYIELD_FROM_ISR()` if the operation unblocked a higher-priority task.
When should I use a counting semaphore instead of a binary semaphore?
Use a counting semaphore when you need to track a quantity rather than a simple occurred/not-occurred state. Common cases include managing a pool of N identical resources (memory blocks, hardware channels), counting pending items in a producer-consumer pipeline where multiple events can queue up before a consumer processes them, or rate-limiting access to a resource with a known concurrency limit. If the maximum meaningful count is 1, a binary semaphore (or a mutex if ownership matters) is simpler and clearer.
What happens if a semaphore is posted more times than its maximum count?
Behavior depends on the RTOS. Most implementations cap the count at the configured maximum and return an error code from the post call rather than silently overflowing. FreeRTOS `xSemaphoreGive()` returns `pdFALSE` if the count is already at its maximum. Ignoring this return value is a common source of lost events, particularly in fast ISR scenarios where the consuming task is not keeping up.
Is a semaphore the right choice for protecting a shared peripheral register or data structure?
Generally no. Protecting shared mutable state usually calls for a mutex, which enforces single-owner access and typically provides priority inheritance. A semaphore's lack of ownership means it cannot guarantee that the task holding access is the only one that can release it. For short, non-blocking critical sections on bare-metal or RTOS targets, disabling interrupts temporarily (with careful bounding of the disabled window) is often simpler and lower overhead than any RTOS primitive.

Differentiators vs similar concepts

Semaphores are most commonly confused with mutexes. The core distinction is ownership: a mutex must be released by the same task that acquired it, enabling priority-inheritance protocols and making it appropriate for mutual exclusion. A semaphore has no owner, making it appropriate for signaling and resource counting but not for guarding critical sections in priority-based systems. Semaphores are also sometimes confused with event flags or event groups (available in RTOSes like FreeRTOS and Zephyr), which allow a task to wait on a bitmask of multiple independent events simultaneously -- something a semaphore cannot express directly.