What is the difference between a mutex, a binary semaphore, and a counting semaphore?
These three primitives serve different purposes, and confusing them is one of the most common embedded interview mistakes.
A counting semaphore maintains an integer count. give (or post) increments the count; take (or wait) decrements it, blocking if the count is zero. It is used to manage a pool of identical resources (e.g., 5 DMA channels, 3 UART ports) or to count events (e.g., "3 packets have arrived").
A binary semaphore is a counting semaphore with a maximum count of 1. It is primarily used for signaling between tasks or between an ISR and a task. For example, a UART receive ISR gives the semaphore to wake up a processing task. The key point: any task (or ISR) can give it, and any task can take it — there is no concept of ownership.
A mutex (mutual exclusion) is used to protect a shared resource and has the concept of ownership — the task that locks the mutex must be the one to unlock it. This ownership property enables priority inheritance: if a high-priority task blocks on a mutex held by a low-priority task, the RTOS temporarily raises the low-priority task's priority to prevent priority inversion. Binary semaphores do not have this mechanism. In FreeRTOS, xSemaphoreCreateMutex() creates a mutex with priority inheritance, while xSemaphoreCreateBinary() creates a binary semaphore without it.
A common interview trap: "Can you use a binary semaphore instead of a mutex?" Technically yes, but you lose priority inheritance and ownership enforcement, making your system vulnerable to priority inversion and accidental releases by the wrong task.
Source: Operating Systems & RTOS Q&A
