Loading...
Searching...
No Matches

Mutex for thread synchronization. More...

Detailed Description

Mutex for thread synchronization.

Warning
By default, no mitigation against priority inversion is employed. If your application is subject to priority inversion and cannot tolerate the additional delay this can cause, use module core_mutex_priority_inheritance to employ priority inheritance as mitigation.

Mutex Implementation Basics

Data Structures and Encoding

A mutex_t contains basically a point, which can have one of the following values:

  1. NULL, in case it is unlocked
  2. MUTEX_LOCKED in case it is locked, but no other thread is waiting on it
  3. A pointer to the head of single linked list of threads (or more precisely their thread_t structures) blocked waiting for obtaining the mutex. This list is terminated by NULL, not by MUTEX_LOCKED

The same information graphically:

Unlocked mutex:
+-------+
| Mutex | --> NULL
+-------+
Locked mutex, no waiters:
+-------+
| Mutex | --> MUTEX_LOCKED
+-------+
Locked mutex, one waiter:
+-------+ +--------+
| Mutex | --> | Waiter | --> NULL
+-------+ +--------+
Locked mutex, 2 waiters:
+-------+ +--------+ +--------+
| Mutex | --> | Waiter | --> | Waiter | --> NULL
+-------+ +--------+ +--------+
Mutex struct within mqtt paho.
Definition paho_mqtt.h:144

Obtaining a Mutex

If a mutex_lock() is called, one of the following happens:

  1. If the mutex was unlocked (value of NULL), its value is changed to MUTEX_LOCKED and the call to mutex_lock() returns right away without blocking.
  2. If the mutex has a value of MUTEX_LOCKED, it will be changed to point to the thread_t of the running thread. The single item list is terminated by setting the thread_t::rq_entry.next of the running thread to NULL. The running thread blocks as described below.
  3. Otherwise, the current thread is inserted into the list of waiting threads sorted by thread priority. The running thread blocks as described below.

In case 2) and 3), the running thread will mark itself as blocked (waiting for a mutex) and yields. Once control is transferred back to this thread (which is done in the call to mutex_unlock()), it has the mutex and the function mutex_lock() returns.

Returning a Mutex

If mutex_unlock() is called, one of the following happens:

  1. If the mutex was already unlocked (value of NULL), the call returns without modifying the mutex.
  2. If the mutex was locked without waiters (value of MUTEX_LOCKED), it is unlocked by setting its value to NULL.
  3. Otherwise the first thread_t from the linked list of waiters is removed from the list.
    • This thread is the one with the highest priority, as the list is sorted by priority.
    • This thread's status is set to pending and its added to the appropriate run queue.
    • If that thread was the last item in the list, the mutex is set to MUTEX_LOCK.
    • The scheduler is run, so that if the unblocked waiting thread can run now, in case it has a higher priority than the running thread.

Debugging deadlocks

The module core_mutex_debug can be used to print on whom mutex_lock() is waiting. This information includes the thread ID of the owner and the program counter (PC) from where mutex_lock() was called. Note that the information is only valid if:

Files

file  mutex.h
 Mutex for thread synchronization.
 

Data Structures

struct  mutex_t
 Mutex structure. More...
 
struct  mutex_cancel_t
 A cancellation structure for use with mutex_lock_cancelable and mutex_cancel. More...
 

Macros

#define MUTEX_INIT   { .queue = { .next = NULL } }
 Static initializer for mutex_t.
 
#define MUTEX_INIT_LOCKED   { .queue = { .next = MUTEX_LOCKED } }
 Static initializer for mutex_t with a locked mutex.
 

Functions

bool mutex_lock_internal (mutex_t *mutex, bool block)
 Internal function implementing mutex_lock and mutex_trylock.
 
static void mutex_init (mutex_t *mutex)
 Initializes a mutex object.
 
static void mutex_init_locked (mutex_t *mutex)
 Initializes a mutex object in a locked state.
 
static mutex_cancel_t mutex_cancel_init (mutex_t *mutex)
 Initialize a mutex cancellation structure.
 
static int mutex_trylock (mutex_t *mutex)
 Tries to get a mutex, non-blocking.
 
static void mutex_lock (mutex_t *mutex)
 Locks a mutex, blocking.
 
int mutex_lock_cancelable (mutex_cancel_t *mc)
 Locks a mutex, blocking.
 
void mutex_unlock (mutex_t *mutex)
 Unlocks the mutex.
 
void mutex_unlock_and_sleep (mutex_t *mutex)
 Unlocks the mutex and sends the current thread to sleep.
 
void mutex_cancel (mutex_cancel_t *mc)
 Cancels a call to mutex_lock_cancelable.
 

Macro Definition Documentation

◆ MUTEX_INIT

#define MUTEX_INIT   { .queue = { .next = NULL } }

Static initializer for mutex_t.

This initializer is preferable to mutex_init().

Definition at line 224 of file mutex.h.

◆ MUTEX_INIT_LOCKED

#define MUTEX_INIT_LOCKED   { .queue = { .next = MUTEX_LOCKED } }

Static initializer for mutex_t with a locked mutex.

Definition at line 229 of file mutex.h.

Function Documentation

◆ mutex_cancel()

void mutex_cancel ( mutex_cancel_t mc)

Cancels a call to mutex_lock_cancelable.

Parameters
[in,out]mcMutex cancellation structure referring to the thread calling mutex_lock_cancelable and to the mutex to cancel the operation on
Note
This function is considered internal. Out of tree users should be aware that breaking API changes or removal of this API without an deprecation period might happen.
Precondition
mc is used to cancel at most one call to mutex_lock_cancelable. (You can reinitialize the same memory to safely reuse it.)
Warning
You MUST NOT call this function once the thread referred to by mc reuses the mutex object referred to by mc (not counting the call to mutex_lock_cancelable mc was used in).
Note
It is safe to call this function from IRQ context, e.g. from a timer interrupt.
It is safe to call this function more than once on the same mc while it is still valid (see the warning above). The first call will cancel the operation and subsequent calls will have no effect.

If thread is currently running (or pending), a subsequent call from thread to mutex_lock_cancelable will also fail

Canonical use:

static void timeout_cb(void *arg) {
}
uint32_t timeout)
{
ztimer_t t = { .callback = timeout_cb, .arg = &mc };
ztimer_set(clock, &t, timeout);
return -ECANCELED;
}
ztimer_remove(clock, &t);
return 0;
}
void mutex_cancel(mutex_cancel_t *mc)
Cancels a call to mutex_lock_cancelable.
static mutex_cancel_t mutex_cancel_init(mutex_t *mutex)
Initialize a mutex cancellation structure.
Definition mutex.h:277
int mutex_lock_cancelable(mutex_cancel_t *mc)
Locks a mutex, blocking.
#define ECANCELED
Operation canceled.
Definition errno.h:80
uint32_t ztimer_set(ztimer_clock_t *clock, ztimer_t *timer, uint32_t val)
Set a timer on a clock.
bool ztimer_remove(ztimer_clock_t *clock, ztimer_t *timer)
Remove a timer from a clock.
int ztimer_mutex_lock_timeout(ztimer_clock_t *clock, mutex_t *mutex, uint32_t timeout)
Try to lock the given mutex, but give up after timeout.
A cancellation structure for use with mutex_lock_cancelable and mutex_cancel.
Definition mutex.h:213
Mutex structure.
Definition mutex.h:146
ztimer device structure
Definition ztimer.h:369
ztimer structure
Definition ztimer.h:318
ztimer_callback_t callback
timer callback function pointer
Definition ztimer.h:320

In the above example a simple implementation of how to implement mutex locking with a timeout is given. There are two corner cases:

  1. The call to mutex_cancel could occur before the call to mutex_lock_cancelable. (E.g. for timeout == 0.)
  2. The call to mutex_cancel could occur right after the mutex was successfully obtained, but before ztimer_remove() was executed.

In the first corner case the cancellation is stored in mc. Hence, the subsequent call to mutex_lock_cancelable gets indeed canceled. In the second corner case the cancellation is also stored in mc but never used - the mutex cancellation structure mc is not allowed to be reused without reinitialization.

◆ mutex_cancel_init()

static mutex_cancel_t mutex_cancel_init ( mutex_t mutex)
inlinestatic

Initialize a mutex cancellation structure.

Parameters
mutexThe mutex that the calling thread wants to lock
Returns
The cancellation structure for use with mutex_lock_cancelable and mutex_cancel
Note
This function is considered internal. Out of tree users should be aware that breaking API changes or removal of this API without an deprecation period might happen.

Definition at line 277 of file mutex.h.

◆ mutex_init()

static void mutex_init ( mutex_t mutex)
inlinestatic

Initializes a mutex object.

For initialization of variables use MUTEX_INIT instead. Only use the function call for dynamically allocated mutexes.

Parameters
[out]mutexpre-allocated mutex structure, must not be NULL.

Definition at line 251 of file mutex.h.

◆ mutex_init_locked()

static void mutex_init_locked ( mutex_t mutex)
inlinestatic

Initializes a mutex object in a locked state.

For initialization of variables use MUTEX_INIT_LOCKED instead. Only use the function call for dynamically allocated mutexes.

Parameters
[out]mutexpre-allocated mutex structure, must not be NULL.

Definition at line 262 of file mutex.h.

◆ mutex_lock()

static void mutex_lock ( mutex_t mutex)
inlinestatic

Locks a mutex, blocking.

Parameters
[in,out]mutexMutex object to lock.
Precondition
mutex is not NULL
Mutex at mutex has been initialized
Must be called in thread context
Postcondition
The mutex is locked and held by the calling thread.

Definition at line 312 of file mutex.h.

◆ mutex_lock_cancelable()

int mutex_lock_cancelable ( mutex_cancel_t mc)

Locks a mutex, blocking.

This function can be canceled.

Parameters
[in,out]mcMutex cancellation structure to work on
Return values
0The mutex was locked by the caller
-ECANCELEDThe mutex was NOT locked, operation was canceled. See mutex_cancel
Note
This function is considered internal. Out of tree users should be aware that breaking API changes or removal of this API without an deprecation period might happen.
Precondition
Must be called in thread context
mc has been initialized with mutex_cancel_init by the calling thread.
mc has NOT been used for previous calls to this function. (Reinitialize before reusing!)
Postcondition
The mutex referred to by mc is locked and held by the calling thread, unless -ECANCELED was returned.

◆ mutex_lock_internal()

bool mutex_lock_internal ( mutex_t mutex,
bool  block 
)

Internal function implementing mutex_lock and mutex_trylock.

Do not call this function, use mutex_lock or mutex_trylock instead

Parameters
[in,out]mutexMutex object to lock.
[in]blockWhether to block
Precondition
mutex is not NULL
Mutex at mutex has been initialized
Must be called in thread context
Postcondition
The mutex is locked and held by the calling thread.
Return values
trueMutex obtained
falseMutex not obtained (only possible if block is false)

◆ mutex_trylock()

static int mutex_trylock ( mutex_t mutex)
inlinestatic

Tries to get a mutex, non-blocking.

Parameters
[in,out]mutexMutex object to lock.
Return values
1if mutex was unlocked, now it is locked.
0if the mutex was locked.
Precondition
mutex is not NULL
Mutex at mutex has been initialized
Must be called in thread context

Definition at line 296 of file mutex.h.

◆ mutex_unlock()

void mutex_unlock ( mutex_t mutex)

Unlocks the mutex.

Parameters
[in,out]mutexMutex object to unlock.
Precondition
mutex is not NULL
Note
It is safe to unlock a mutex held by a different thread.
It is safe to call this function from IRQ context.

◆ mutex_unlock_and_sleep()

void mutex_unlock_and_sleep ( mutex_t mutex)

Unlocks the mutex and sends the current thread to sleep.

Parameters
[in,out]mutexMutex object to unlock.
Precondition
mutex is not NULL
Must be called in thread context.