diff --git a/core/include/mutex.h b/core/include/mutex.h index c6655d15dc..79781a330c 100644 --- a/core/include/mutex.h +++ b/core/include/mutex.h @@ -8,116 +8,10 @@ */ /** - * @defgroup core_sync_mutex Mutex - * @ingroup core_sync - * @brief 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 - * +-------+ +--------+ +--------+ - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * 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: - * - * - The mutex was locked by a thread, and not e.g. by `MUTEX_INIT_LOCKED` - * - The function `cpu_get_caller_pc()` is implemented for the target - * architecture. (The thread ID will remain valid, though.) - * - The caller PC is briefly 0 when the current owner passes over ownership - * to the next thread, but that thread didn't get CPU time yet to write its - * PC into the data structure. Even worse, on architectures where an aligned - * function-pointer-sized write is not atomic, the value may briefly be - * bogus. Chances are close to zero this ever hits and since this only - * effects debug output, the ostrich algorithm was chosen here. - * + * @addtogroup core_sync_mutex * @{ - * + */ +/** * @file * @brief Mutex for thread synchronization * diff --git a/core/mutex.doc.md b/core/mutex.doc.md new file mode 100644 index 0000000000..3544e6b9f4 --- /dev/null +++ b/core/mutex.doc.md @@ -0,0 +1,107 @@ +@defgroup core_sync_mutex Mutex +@ingroup core_sync +@brief 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 ++-------+ +--------+ +--------+ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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: + +- The mutex was locked by a thread, and not e.g. by `MUTEX_INIT_LOCKED` +- The function `cpu_get_caller_pc()` is implemented for the target + architecture. (The thread ID will remain valid, though.) +- The caller PC is briefly 0 when the current owner passes over ownership + to the next thread, but that thread didn't get CPU time yet to write its + PC into the data structure. Even worse, on architectures where an aligned + function-pointer-sized write is not atomic, the value may briefly be + bogus. Chances are close to zero this ever hits and since this only + effects debug output, the ostrich algorithm was chosen here. diff --git a/dist/tools/doccheck/check.sh b/dist/tools/doccheck/check.sh index d53d191cae..6f34f6d391 100755 --- a/dist/tools/doccheck/check.sh +++ b/dist/tools/doccheck/check.sh @@ -73,8 +73,8 @@ exclude_filter() { # Check groups are correctly defined (e.g. no undefined groups and no group # defined multiple times) -ALL_RAW_DEFGROUP=$(git grep -n @defgroup -- '*.h' '*.hpp' '*.txt' '*/doc.md' 'makefiles/pseudomodules.inc.mk' 'sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c'| exclude_filter) -ALL_RAW_INGROUP=$(git grep -n '@ingroup' -- '*.h' '*.hpp' '*.txt' '*/doc.md' 'makefiles/pseudomodules.inc.mk' 'sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c'| exclude_filter) +ALL_RAW_DEFGROUP=$(git grep -n '@defgroup' -- '*.h' '*.hpp' '*.txt' '*/doc.md' '*.doc.md' 'makefiles/pseudomodules.inc.mk' 'sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c'| exclude_filter) +ALL_RAW_INGROUP=$(git grep -n '@ingroup' -- '*.h' '*.hpp' '*.txt' '*/doc.md' '*.doc.md' 'makefiles/pseudomodules.inc.mk' 'sys/net/gnrc/routing/ipv6_auto_subnets/gnrc_ipv6_auto_subnets.c'| exclude_filter) DEFINED_GROUPS=$(echo "${ALL_RAW_DEFGROUP}" | \ grep -oE '@defgroup[ ]+[^ ]+' | \ grep -oE '[^ ]+$' | \ diff --git a/doc/doxygen/riot.doxyfile b/doc/doxygen/riot.doxyfile index a579d57beb..a7765df70b 100644 --- a/doc/doxygen/riot.doxyfile +++ b/doc/doxygen/riot.doxyfile @@ -928,6 +928,7 @@ INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.txt \ doc.md \ + *.doc.md \ *.h \ *.h.in \ *.hpp diff --git a/doc/doxygen/src/css/riot.css b/doc/doxygen/src/css/riot.css index 0308e0cd60..1822c45153 100644 --- a/doc/doxygen/src/css/riot.css +++ b/doc/doxygen/src/css/riot.css @@ -522,7 +522,7 @@ table.retval > tbody > tr > td.paramname { blockquote { background-color: #eadde1; } -/* HACK: doc.md files are processed correctly, but additionally add an empty page, +/* HACK: *doc.md files are processed correctly, but additionally add an empty page, * see https://github.com/doxygen/doxygen/issues/9437 * and https://github.com/RIOT-OS/RIOT/issues/21220#issuecomment-2701284183 * can be removed as soon as the doxygen issue is fixed */ diff --git a/doc/doxygen/src/css/riot.less b/doc/doxygen/src/css/riot.less index afb2b1e6a2..634a0777d5 100644 --- a/doc/doxygen/src/css/riot.less +++ b/doc/doxygen/src/css/riot.less @@ -464,3 +464,11 @@ table.retval > tbody > tr > td.paramname { blockquote { background-color: #eadde1; } +/* HACK: *doc.md files are processed correctly, but additionally add an empty page, + * see https://github.com/doxygen/doxygen/issues/9437 + * and https://github.com/RIOT-OS/RIOT/issues/21220#issuecomment-2701284183 + * can be removed as soon as the doxygen issue is fixed */ +li:has(> .item a[class^="md_"][class$="doc.html"]), +tr:has(> .entry a[href^="md_"][href$="doc.html"]) { + display: none; +}