Skip to content

Commit 1446aea

Browse files
committed
core/mutex: separate documentation
1 parent b78bb23 commit 1446aea

File tree

2 files changed

+110
-109
lines changed

2 files changed

+110
-109
lines changed

core/include/mutex.h

Lines changed: 3 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -8,116 +8,10 @@
88
*/
99

1010
/**
11-
* @defgroup core_sync_mutex Mutex
12-
* @ingroup core_sync
13-
* @brief Mutex for thread synchronization
14-
*
15-
* @warning By default, no mitigation against priority inversion is
16-
* employed. If your application is subject to priority inversion
17-
* and cannot tolerate the additional delay this can cause, use
18-
* module `core_mutex_priority_inheritance` to employ
19-
* priority inheritance as mitigation.
20-
*
21-
* Mutex Implementation Basics
22-
* ===========================
23-
*
24-
* Data Structures and Encoding
25-
* ----------------------------
26-
*
27-
* A `mutex_t` contains basically a point, which can have one of the following
28-
* values:
29-
*
30-
* 1. `NULL`, in case it is unlocked
31-
* 2. `MUTEX_LOCKED` in case it is locked, but no other thread is waiting on it
32-
* 3. A pointer to the head of single linked list of threads (or more precisely
33-
* their `thread_t` structures) blocked waiting for obtaining the mutex. This
34-
* list is terminated by `NULL`, not by `MUTEX_LOCKED`
35-
*
36-
* The same information graphically:
37-
*
38-
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39-
* Unlocked mutex:
40-
* +-------+
41-
* | Mutex | --> NULL
42-
* +-------+
43-
*
44-
* Locked mutex, no waiters:
45-
* +-------+
46-
* | Mutex | --> MUTEX_LOCKED
47-
* +-------+
48-
*
49-
* Locked mutex, one waiter:
50-
* +-------+ +--------+
51-
* | Mutex | --> | Waiter | --> NULL
52-
* +-------+ +--------+
53-
*
54-
* Locked mutex, 2 waiters:
55-
* +-------+ +--------+ +--------+
56-
* | Mutex | --> | Waiter | --> | Waiter | --> NULL
57-
* +-------+ +--------+ +--------+
58-
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59-
*
60-
* Obtaining a Mutex
61-
* -----------------
62-
*
63-
* If a `mutex_lock()` is called, one of the following happens:
64-
*
65-
* 1. If the mutex was unlocked (value of `NULL`), its value is changed to
66-
* `MUTEX_LOCKED` and the call to `mutex_lock()` returns right away without
67-
* blocking.
68-
* 2. If the mutex has a value of `MUTEX_LOCKED`, it will be changed to point to
69-
* the `thread_t` of the running thread. The single item list is terminated
70-
* by setting the `thread_t::rq_entry.next` of the running thread to `NULL`.
71-
* The running thread blocks as described below.
72-
* 3. Otherwise, the current thread is inserted into the list of waiting
73-
* threads sorted by thread priority. The running thread blocks as described
74-
* below.
75-
*
76-
* In case 2) and 3), the running thread will mark itself as blocked (waiting
77-
* for a mutex) and yields. Once control is transferred back to this thread
78-
* (which is done in the call to `mutex_unlock()`), it has the mutex and the
79-
* function `mutex_lock()` returns.
80-
*
81-
* Returning a Mutex
82-
* -----------------
83-
*
84-
* If `mutex_unlock()` is called, one of the following happens:
85-
*
86-
* 1. If the mutex was already unlocked (value of `NULL`), the call returns
87-
* without modifying the mutex.
88-
* 2. If the mutex was locked without waiters (value of `MUTEX_LOCKED`), it is
89-
* unlocked by setting its value to `NULL`.
90-
* 3. Otherwise the first `thread_t` from the linked list of waiters is removed
91-
* from the list.
92-
* - This thread is the one with the highest priority, as the list is sorted
93-
* by priority.
94-
* - This thread's status is set to pending and its added to the appropriate
95-
* run queue.
96-
* - If that thread was the last item in the list, the mutex is set to
97-
* `MUTEX_LOCK`.
98-
* - The scheduler is run, so that if the unblocked waiting thread can
99-
* run now, in case it has a higher priority than the running thread.
100-
*
101-
* Debugging deadlocks
102-
* -------------------
103-
*
104-
* The module `core_mutex_debug` can be used to print on whom `mutex_lock()`
105-
* is waiting. This information includes the thread ID of the owner and the
106-
* program counter (PC) from where `mutex_lock()` was called. Note that the
107-
* information is only valid if:
108-
*
109-
* - The mutex was locked by a thread, and not e.g. by `MUTEX_INIT_LOCKED`
110-
* - The function `cpu_get_caller_pc()` is implemented for the target
111-
* architecture. (The thread ID will remain valid, though.)
112-
* - The caller PC is briefly 0 when the current owner passes over ownership
113-
* to the next thread, but that thread didn't get CPU time yet to write its
114-
* PC into the data structure. Even worse, on architectures where an aligned
115-
* function-pointer-sized write is not atomic, the value may briefly be
116-
* bogus. Chances are close to zero this ever hits and since this only
117-
* effects debug output, the ostrich algorithm was chosen here.
118-
*
11+
* @addtogroup core_sync_mutex
11912
* @{
120-
*
13+
*/
14+
/**
12115
* @file
12216
* @brief Mutex for thread synchronization
12317
*

core/mutex.doc.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
@defgroup core_sync_mutex Mutex
2+
@ingroup core_sync
3+
@brief Mutex for thread synchronization
4+
5+
@warning By default, no mitigation against priority inversion is
6+
employed. If your application is subject to priority inversion
7+
and cannot tolerate the additional delay this can cause, use
8+
module `core_mutex_priority_inheritance` to employ
9+
priority inheritance as mitigation.
10+
11+
Mutex Implementation Basics
12+
===========================
13+
14+
Data Structures and Encoding
15+
----------------------------
16+
17+
A `mutex_t` contains basically a point, which can have one of the following
18+
values:
19+
20+
1. `NULL`, in case it is unlocked
21+
2. `MUTEX_LOCKED` in case it is locked, but no other thread is waiting on it
22+
3. A pointer to the head of single linked list of threads (or more precisely
23+
their `thread_t` structures) blocked waiting for obtaining the mutex. This
24+
list is terminated by `NULL`, not by `MUTEX_LOCKED`
25+
26+
The same information graphically:
27+
28+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29+
Unlocked mutex:
30+
+-------+
31+
| Mutex | --> NULL
32+
+-------+
33+
34+
Locked mutex, no waiters:
35+
+-------+
36+
| Mutex | --> MUTEX_LOCKED
37+
+-------+
38+
39+
Locked mutex, one waiter:
40+
+-------+ +--------+
41+
| Mutex | --> | Waiter | --> NULL
42+
+-------+ +--------+
43+
44+
Locked mutex, 2 waiters:
45+
+-------+ +--------+ +--------+
46+
| Mutex | --> | Waiter | --> | Waiter | --> NULL
47+
+-------+ +--------+ +--------+
48+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
49+
50+
Obtaining a Mutex
51+
-----------------
52+
53+
If a `mutex_lock()` is called, one of the following happens:
54+
55+
1. If the mutex was unlocked (value of `NULL`), its value is changed to
56+
`MUTEX_LOCKED` and the call to `mutex_lock()` returns right away without
57+
blocking.
58+
2. If the mutex has a value of `MUTEX_LOCKED`, it will be changed to point to
59+
the `thread_t` of the running thread. The single item list is terminated
60+
by setting the `thread_t::rq_entry.next` of the running thread to `NULL`.
61+
The running thread blocks as described below.
62+
3. Otherwise, the current thread is inserted into the list of waiting
63+
threads sorted by thread priority. The running thread blocks as described
64+
below.
65+
66+
In case 2) and 3), the running thread will mark itself as blocked (waiting
67+
for a mutex) and yields. Once control is transferred back to this thread
68+
(which is done in the call to `mutex_unlock()`), it has the mutex and the
69+
function `mutex_lock()` returns.
70+
71+
Returning a Mutex
72+
-----------------
73+
74+
If `mutex_unlock()` is called, one of the following happens:
75+
76+
1. If the mutex was already unlocked (value of `NULL`), the call returns
77+
without modifying the mutex.
78+
2. If the mutex was locked without waiters (value of `MUTEX_LOCKED`), it is
79+
unlocked by setting its value to `NULL`.
80+
3. Otherwise the first `thread_t` from the linked list of waiters is removed
81+
from the list.
82+
- This thread is the one with the highest priority, as the list is sorted
83+
by priority.
84+
- This thread's status is set to pending and its added to the appropriate
85+
run queue.
86+
- If that thread was the last item in the list, the mutex is set to
87+
`MUTEX_LOCK`.
88+
- The scheduler is run, so that if the unblocked waiting thread can
89+
run now, in case it has a higher priority than the running thread.
90+
91+
Debugging deadlocks
92+
-------------------
93+
94+
The module `core_mutex_debug` can be used to print on whom `mutex_lock()`
95+
is waiting. This information includes the thread ID of the owner and the
96+
program counter (PC) from where `mutex_lock()` was called. Note that the
97+
information is only valid if:
98+
99+
- The mutex was locked by a thread, and not e.g. by `MUTEX_INIT_LOCKED`
100+
- The function `cpu_get_caller_pc()` is implemented for the target
101+
architecture. (The thread ID will remain valid, though.)
102+
- The caller PC is briefly 0 when the current owner passes over ownership
103+
to the next thread, but that thread didn't get CPU time yet to write its
104+
PC into the data structure. Even worse, on architectures where an aligned
105+
function-pointer-sized write is not atomic, the value may briefly be
106+
bogus. Chances are close to zero this ever hits and since this only
107+
effects debug output, the ostrich algorithm was chosen here.

0 commit comments

Comments
 (0)