summaryrefslogtreecommitdiff
path: root/src/mutex.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/mutex.h')
-rw-r--r--src/mutex.h165
1 files changed, 165 insertions, 0 deletions
diff --git a/src/mutex.h b/src/mutex.h
new file mode 100644
index 0000000..c158d5e
--- /dev/null
+++ b/src/mutex.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2017 Richard Braun.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * Mutex module.
+ *
+ * A mutex is a thread synchronization tool that provides mutual exclusion.
+ * The code between locking and unlocking a mutex forms a critical section
+ * that only one thread may be running at a time. This is true for all
+ * critical sections created with the same mutex, i.e. only one thread may
+ * be running code in all the critical sections created with the same mutex.
+ *
+ * A mutex is initially not owned. When a thread locks a mutex, it becomes
+ * owned until unlocked, and any other thread trying to lock the mutex
+ * waits for the owner to unlock it. This is called contention. Waiting
+ * can be active, e.g. by spinning on the mutex, constantly checking if
+ * it's still locked, or passive, by sleeping until the owner unlocks
+ * and wakes up one of the waiters. Mutexes are generally passive, and
+ * this is also the case for this module.
+ *
+ * Since mutexes work by blocking execution of some threads, they are a
+ * type of lock. Despite their apparent simplicity, they are associated
+ * with a number of problems, most considered difficult when compared to
+ * other well know problems of computer science.
+ *
+ * In particular, mutexes are vulnerable to deadlocks, i.e. a situation
+ * where all threads involved are waiting for events that cannot occur.
+ * Imagine two threads, T1 and T2, and 2 mutexes, M1 and M2. Here is
+ * a representation of the execution history of those threads leading to
+ * a deadlock :
+ *
+ * T1 : lock(M1) -> lock(M2) (M2 is locked by T2)
+ * T2 : lock(M2) -> lock(M1) (M1 is locked by T1)
+ *
+ * Here, both threads are waiting for the other to unlock one of the
+ * mutexes before unlocking the other mutex, and none can make progress.
+ *
+ * The most simple solution to avoid deadlocks is to not use locks in
+ * the first place, and use other synchronization tools such as disabling
+ * preemption, or resorting to lock-free algorithms, but this is often
+ * not possible (preemption cannot normally be disabled outside kernel
+ * code, and lock-free algorithms are difficult to implement and may
+ * lack other dependencies like atomic instructions on small processors).
+ *
+ * Another simple solution is to avoid holding two mutexes at the same
+ * time. It can often be achieved, but not always. Sometimes, holding
+ * multiple mutexes at the same time is a requirement. In such cases,
+ * the most common solution is to establish a locking order, since a
+ * deadlock may only occur if mutexes are acquired in different orders.
+ * In the example above, the locking order could be represented as
+ * "M1 -> M2", stating that, when hodling both mutexes, M1 must always
+ * be locked before M2.
+ *
+ * Another common problem with mutexes is unbounded priority inversion.
+ * Imagine 2 threads, T1 and T2 with priorities 1 and 2 respectively
+ * (higher value means higher priority). On a real-time system with
+ * a simple fixed priority scheduler, T2 would preempt T1 whenever it
+ * is active. But if it then tries to lock a mutex owned by T1, it will
+ * have to wait for T1 to unlock the mutex. This is priority inversion,
+ * because T1 is running instead of T2 despite having a lower priority.
+ * Priority inversion itself is common and often unavoidable in many
+ * cases, and if kept reliably short, it normally doesn't disturb
+ * real-time behaviour. But if there is no guarantee that the critical
+ * section is time-bounded, real-time behaviour may not be achieved.
+ * The classic example is three threads, T1, T2, and T3, with priorities
+ * 1, 2, and 3 respectively, and T3 locking a mutex already owned by T1 :
+ *
+ * time ->
+ * T3 lock(M) + run ...
+ * T2 run + run + |
+ * T1 lock(M) - run + unlock(M) +
+ * +: preemption ^^^
+ * duration not known/bounded
+ *
+ * Here, T3 has to wait for T1 to unlock the mutex before making progress,
+ * but T2 may preempt T1 because of its higher priority. The time T2 runs,
+ * keeping T1 from making progress, is likely not known and unbounded, and
+ * it indirectly delays T3, despite T3 having a higher priority. This is
+ * unbounded priority inversion. It can be avoided at somewhat high cost
+ * with priority ceiling/inheritance, or by avoiding the use of mutexes,
+ * relying instead on e.g. message queues using preemption for
+ * synchronization.
+ *
+ * The implementation of this module is very simple and doesn't prevent
+ * unbounded priority inversions.
+ *
+ * When deciding whether to use a mutex or to disable preemption for
+ * mutual exclusion, keep in mind that all real-world mutex implementations
+ * disable preemption internally. As a result, it is best to use mutexes
+ * when critical sections are noticeably more expensive than the overhead
+ * of locking. Another parameter is whether or not the critical section
+ * must remain preemptible.
+ *
+ * This mutex interface matches the "fast" kind of POSIX mutexes. In
+ * particular, a mutex cannot be locked recursively.
+ */
+
+#ifndef _MUTEX_H
+#define _MUTEX_H
+
+#include <stdbool.h>
+
+#include <lib/list.h>
+
+#include "thread.h"
+
+/*
+ * Mutex type.
+ *
+ * All members are private.
+ */
+struct mutex {
+ struct list waiters;
+ struct thread *owner;
+ bool locked;
+};
+
+/*
+ * Initialize a mutex.
+ */
+void mutex_init(struct mutex *mutex);
+
+/*
+ * Lock a mutex.
+ *
+ * If the given mutex is already locked, the calling thread blocks (by
+ * sleeping) and resumes once it holds the mutex.
+ */
+void mutex_lock(struct mutex *mutex);
+
+/*
+ * Try to lock a mutex.
+ *
+ * This is the non-blocking variant of mutex_lock().
+ *
+ * Return 0 on success, ERROR_BUSY if locking the mutex failed.
+ */
+int mutex_trylock(struct mutex *mutex);
+
+/*
+ * Unlock a mutex.
+ *
+ * The calling thread must be the mutex owner.
+ */
+void mutex_unlock(struct mutex *mutex);
+
+#endif /* _MUTEX_H */