summaryrefslogtreecommitdiff
path: root/src/mutex.c
diff options
context:
space:
mode:
authorRichard Braun <rbraun@sceen.net>2017-10-14 23:45:04 +0200
committerRichard Braun <rbraun@sceen.net>2018-01-04 01:57:38 +0100
commit9437f135da9fab16180fc64cdd64e2a3bb3d5b7a (patch)
tree8cd3d9e769c2af24463d58e8ba416aae9de9ce7b /src/mutex.c
Initial commit
Diffstat (limited to 'src/mutex.c')
-rw-r--r--src/mutex.c151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/mutex.c b/src/mutex.c
new file mode 100644
index 0000000..d70bd82
--- /dev/null
+++ b/src/mutex.c
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <lib/list.h>
+#include <lib/macros.h>
+
+#include "error.h"
+#include "mutex.h"
+#include "thread.h"
+
+/*
+ * Structure used to bind a waiting thread and a mutex.
+ *
+ * The purpose of this structure is to avoid adding the node to the thread
+ * structure. Instead, it's allocated from the stack and only exists while
+ * the thread is waiting for the mutex to be unlocked.
+ *
+ * When the owner unlocks the mutex, it finds threads to wake up by
+ * accessing the mutex list of waiters.
+ *
+ * Preemption must be disabled when accessing a waiter.
+ */
+struct mutex_waiter {
+ struct list node;
+ struct thread *thread;
+};
+
+static void
+mutex_waiter_init(struct mutex_waiter *waiter, struct thread *thread)
+{
+ waiter->thread = thread;
+}
+
+static void
+mutex_waiter_wakeup(struct mutex_waiter *waiter)
+{
+ thread_wakeup(waiter->thread);
+}
+
+void
+mutex_init(struct mutex *mutex)
+{
+ list_init(&mutex->waiters);
+ mutex->locked = false;
+}
+
+static void
+mutex_set_owner(struct mutex *mutex, struct thread *thread)
+{
+ assert(!mutex->owner);
+ assert(!mutex->locked);
+
+ mutex->owner = thread;
+ mutex->locked = true;
+}
+
+static void
+mutex_clear_owner(struct mutex *mutex)
+{
+ assert(mutex->owner == thread_self());
+ assert(mutex->locked);
+
+ mutex->owner = NULL;
+ mutex->locked = false;
+}
+
+void
+mutex_lock(struct mutex *mutex)
+{
+ struct thread *thread;
+
+ thread = thread_self();
+
+ thread_preempt_disable();
+
+ if (mutex->locked) {
+ struct mutex_waiter waiter;
+
+ mutex_waiter_init(&waiter, thread);
+ list_insert_tail(&mutex->waiters, &waiter.node);
+
+ do {
+ thread_sleep();
+ } while (mutex->locked);
+
+ list_remove(&waiter.node);
+ }
+
+ mutex_set_owner(mutex, thread);
+
+ thread_preempt_enable();
+}
+
+int
+mutex_trylock(struct mutex *mutex)
+{
+ int error;
+
+ thread_preempt_disable();
+
+ if (mutex->locked) {
+ error = ERROR_BUSY;
+ } else {
+ error = 0;
+ mutex_set_owner(mutex, thread_self());
+ }
+
+ thread_preempt_enable();
+
+ return error;
+}
+
+void
+mutex_unlock(struct mutex *mutex)
+{
+ struct mutex_waiter *waiter;
+
+ thread_preempt_disable();
+
+ mutex_clear_owner(mutex);
+
+ if (!list_empty(&mutex->waiters)) {
+ waiter = list_first_entry(&mutex->waiters, struct mutex_waiter, node);
+ mutex_waiter_wakeup(waiter);
+ }
+
+ thread_preempt_enable();
+}