RTOS Mutex
0) One-line definition
Mutex = a lock to protect mutually exclusive critical sections.
The key feature is priority inversion prevention (inheritance)—this is what differentiates it from queues/semaphores.
Never use it in ISRs. (Task context only)
1) When to use—and when not to
✅ Protect shared resources (driver internal state, global data structures, file/flash handles)
✅ When tasks of different priorities contend for a resource (priority inheritance mitigates inversion)
❌ Data passing / event signaling → use Queue / TaskNotify / MessageBuffer
❌ Inside an ISR → there’s no FromISR API (have the ISR wake a task via semaphore/notify instead)
❌ Holding it across long operations / blocking I/O → seeds latency and starvation
2) Minimal examples (FreeRTOS)
Dynamic creation
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t m;
void init_mutex(void){
m = xSemaphoreCreateMutex(); // requires configUSE_MUTEXES=1
configASSERT(m != NULL);
}
void use_shared(void){
if (xSemaphoreTake(m, pdMS_TO_TICKS(5)) == pdPASS) {
// Critical section: keep it short; avoid printf/blocking calls
critical_work();
xSemaphoreGive(m); // always pair with a Give
} else {
// Timeout: retry/backoff/error handling
on_lock_timeout();
}
}
Static creation
static StaticSemaphore_t mCtrl;
static SemaphoreHandle_t m;
void init_mutex_static(void){
m = xSemaphoreCreateMutexStatic(&mCtrl);
configASSERT(m != NULL);
}
Important
Only the owning task can
xSemaphoreGive(mutex)(FreeRTOS tracks ownership).There is no
xSemaphoreGiveFromISR()for mutexes (mutexes are not allowed in ISRs).Prefer short timeouts and design a failure path; don’t default to infinite waits.
3) Priority inversion & inheritance (core concept)
Scenario: L (low-priority) holds the mutex; H (high-priority) waits on the same mutex.
Inheritance: the kernel temporarily raises L’s priority up to H’s, so L finishes the critical section and releases the mutex sooner.
In FreeRTOS, this is automatic for mutex types (with
configUSE_MUTEXES=1).Binary semaphores don’t have priority inheritance → for exclusive access, use a mutex.
4) Rules to avoid deadlocks and excessive latency
Global lock ordering
Enforce a single order
A → B → C.No reverse/cross acquisition. (prevents deadlocks)
Minimize hold time
Inside the critical section, don’t wait/sleep/receive from queues/do blocking I/O.
Do heavy computation outside the lock.
Timeout + retry policy
On
xSemaphoreTake(m, timeout)failure, back off / retry, log/alert.Blind
portMAX_DELAYcan mask deadlocks.
Try-lock pattern
- Use
xSemaphoreTake(m, 0)to fail fast and do other work first.
- Use
One lock at a time
- Ideally hold only one mutex per critical section (if you must hold multiple, obey ordering).
5) Recursive mutex
When the same task must acquire the same mutex nested (recursion/library call chains):
SemaphoreHandle_t rm = xSemaphoreCreateRecursiveMutex(); xSemaphoreTakeRecursive(rm, timeout); // ... xSemaphoreGiveRecursive(rm);Caution
You must
Giveas many times as youTaketo fully release it.Adds complexity—use only when truly necessary.
6) Mutex vs. (binary) semaphore — quick summary
| Item | Mutex | Binary semaphore |
| Purpose | Exclusive resource protection | Event signaling / simple access gating |
| Priority inheritance | Yes | No |
| Ownership | Only owner can Give | Anyone can Give |
| ISR use | Not allowed | GiveFromISR/TakeFromISR allowed (depending on use) |
Bottom line: “Resource protection = Mutex”, “Signal/wake-up = Semaphore/Notify.”
8) Practical patterns & anti-patterns
✅ Good patterns
Lock-guard idioms to guarantee release:
#define LOCK_OR_RETURN(mx, to) do{ if(xSemaphoreTake(mx, to)!=pdPASS) return; }while(0) #define UNLOCK(mx) do{ xSemaphoreGive(mx); }while(0) void foo(void){ LOCK_OR_RETURN(m, pdMS_TO_TICKS(2)); // ... critical ... UNLOCK(m); }Touch only the data truly requiring the lock; do the rest outside.
❌ Bad patterns
Holding a mutex while doing
vTaskDelay(),xQueueReceive(portMAX_DELAY),printf, flash writes, or any long operation.Trying to use a mutex in an ISR (nope).
Using a binary semaphore for resource protection (no inheritance → inversion risk).
No lock ordering → deadlocks.
9) Priority/config checklist
FreeRTOS:
configUSE_MUTEXES=1; for recursion,configUSE_RECURSIVE_MUTEXES=1.Priority placement: tasks that frequently hold shared resources shouldn’t be too low in priority (even with inheritance, latency grows).
Very short critical sections can use
taskENTER_CRITICAL(), but that delays ISRs—keep it very short.
10) Debugging / observability
Name your mutexes:
vQueueAddToRegistry((QueueHandle_t)m, "MutexFoo");to identify them in traces/debuggers.Measure hold time: log ticks before/after acquisition; refactor if excessive.
Contention metrics: timeouts/fail counts, avg/max wait times.
Final checklist
Never use a mutex in an ISR
Mutex for resource protection, Semaphore/Notify for signaling
Document/enforce global lock ordering in code reviews
Design timeouts/backoff (avoid infinite waits)
Keep critical sections short; avoid blocking inside
Use recursive mutex only when necessary (avoid overuse)