Add a 2LS-independent mutex interface
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 5 Jan 2016 15:14:56 +0000 (10:14 -0500)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 14 Jan 2016 21:04:46 +0000 (16:04 -0500)
We need a way to wait on a mutex from 2LS-independent code.  Unlike
spinlocks, mutexes might need a custom implementation for a given 2LS.

We now provide a generic mutex code that most 2LSs can use.  Others can
override these methods if they desire.

Note that the current pthread mutex implementation is a glorified
busy-wait.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
user/parlib/include/uthread.h
user/parlib/mutex.c [new file with mode: 0644]

index e7de284..252cc0a 100644 (file)
@@ -39,9 +39,11 @@ struct uthread {
        char err_str[MAX_ERRSTR_LEN];
 };
 extern __thread struct uthread *current_uthread;
+typedef void* uth_mutex_t;
 
 /* 2L-Scheduler operations.  Examples in pthread.c. */
 struct schedule_ops {
+       /**** These functions must be defined ****/
        /* Functions supporting thread ops */
        void (*sched_entry)(void);
        void (*thread_runnable)(struct uthread *);
@@ -50,6 +52,12 @@ struct schedule_ops {
        void (*thread_has_blocked)(struct uthread *, int);
        void (*thread_refl_fault)(struct uthread *, unsigned int, unsigned int,
                                                          unsigned long);
+       /**** Defining these functions is optional. ****/
+       /* 2LSs can leave the mutex funcs empty for a default implementation */
+       uth_mutex_t (*mutex_alloc)(void);
+       void (*mutex_free)(uth_mutex_t);
+       void (*mutex_lock)(uth_mutex_t);
+       void (*mutex_unlock)(uth_mutex_t);
        /* Functions event handling wants */
        void (*preempt_pending)(void);
 };
@@ -119,4 +127,11 @@ static inline void init_uthread_ctx(struct uthread *uth, void (*entry)(void),
        val;                                                                       \
 })
 
+/* Generic Uthread Mutexes.  2LSs implement their own methods, but we need a
+ * 2LS-independent interface and default implementation. */
+uth_mutex_t uth_mutex_alloc(void);
+void uth_mutex_free(uth_mutex_t m);
+void uth_mutex_lock(uth_mutex_t m);
+void uth_mutex_unlock(uth_mutex_t m);
+
 __END_DECLS
diff --git a/user/parlib/mutex.c b/user/parlib/mutex.c
new file mode 100644 (file)
index 0000000..7902c61
--- /dev/null
@@ -0,0 +1,125 @@
+/* Copyright (c) 2016 Google, Inc.
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details. */
+
+/* Generic Uthread Mutexes.  2LSs implement their own methods, but we need a
+ * 2LS-independent interface and default implementation. */
+
+#include <parlib/uthread.h>
+#include <sys/queue.h>
+#include <parlib/spinlock.h>
+#include <malloc.h>
+
+struct uth_default_mtx;
+struct uth_mtx_link {
+       TAILQ_ENTRY(uth_mtx_link)       next;
+       struct uth_default_mtx          *mtx;
+       struct uthread                          *uth;
+};
+
+struct uth_default_mtx {
+       struct spin_pdr_lock            lock;
+       TAILQ_HEAD(t, uth_mtx_link)     waiters;
+       bool                                            locked;
+};
+
+static struct uth_default_mtx *uth_default_mtx_alloc(void)
+{
+       struct uth_default_mtx *mtx;
+
+       mtx = malloc(sizeof(struct uth_default_mtx));
+       assert(mtx);
+       spin_pdr_init(&mtx->lock);
+       TAILQ_INIT(&mtx->waiters);
+       mtx->locked = FALSE;
+       return mtx;
+}
+
+static void uth_default_mtx_free(struct uth_default_mtx *mtx)
+{
+       assert(TAILQ_EMPTY(&mtx->waiters));
+       free(mtx);
+}
+
+static void __mutex_cb(struct uthread *uth, void *arg)
+{
+       struct uth_mtx_link *link = (struct uth_mtx_link*)arg;
+       struct uth_default_mtx *mtx = link->mtx;
+
+       /* We need to tell the 2LS that its thread blocked.  We need to do this
+        * before unlocking the mtx, since as soon as we unlock, the mtx could be
+        * released and our thread restarted.
+        *
+        * Also note the lock-ordering rule.  The mtx lock is grabbed before any
+        * locks the 2LS might grab. */
+       uthread_has_blocked(uth, UTH_EXT_BLK_MUTEX);
+       spin_pdr_unlock(&mtx->lock);
+}
+
+static void uth_default_mtx_lock(struct uth_default_mtx *mtx)
+{
+       struct uth_mtx_link link;
+
+       spin_pdr_lock(&mtx->lock);
+       if (!mtx->locked) {
+               mtx->locked = TRUE;
+               spin_pdr_unlock(&mtx->lock);
+               return;
+       }
+       link.mtx = mtx;
+       link.uth = current_uthread;
+       TAILQ_INSERT_TAIL(&mtx->waiters, &link, next);
+       /* the unlock is done in the yield callback.  as always, we need to do this
+        * part in vcore context, since as soon as we unlock the uthread could
+        * restart.  (atomically yield and unlock). */
+       uthread_yield(TRUE, __mutex_cb, &link);
+}
+
+static void uth_default_mtx_unlock(struct uth_default_mtx *mtx)
+{
+       struct uth_mtx_link *first;
+
+       spin_pdr_lock(&mtx->lock);
+       first = TAILQ_FIRST(&mtx->waiters);
+       if (first)
+               TAILQ_REMOVE(&mtx->waiters, first, next);
+       else
+               mtx->locked = FALSE;
+       spin_pdr_unlock(&mtx->lock);
+       if (first)
+               uthread_runnable(first->uth);
+}
+
+uth_mutex_t uth_mutex_alloc(void)
+{
+       if (sched_ops->mutex_alloc)
+               return sched_ops->mutex_alloc();
+       return (uth_mutex_t)uth_default_mtx_alloc();
+}
+
+void uth_mutex_free(uth_mutex_t m)
+{
+       if (sched_ops->mutex_free) {
+               sched_ops->mutex_free(m);
+               return;
+       }
+       uth_default_mtx_free((struct uth_default_mtx*)m);
+}
+
+void uth_mutex_lock(uth_mutex_t m)
+{
+       if (sched_ops->mutex_lock) {
+               sched_ops->mutex_lock(m);
+               return;
+       }
+       uth_default_mtx_lock((struct uth_default_mtx*)m);
+}
+
+void uth_mutex_unlock(uth_mutex_t m)
+{
+       if (sched_ops->mutex_unlock) {
+               sched_ops->mutex_unlock(m);
+               return;
+       }
+       uth_default_mtx_unlock((struct uth_default_mtx*)m);
+}