Add post-and-poke synchronization to parlib
authorBarret Rhoden <brho@cs.berkeley.edu>
Sun, 9 Aug 2015 19:59:36 +0000 (15:59 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 28 Sep 2015 19:14:00 +0000 (15:14 -0400)
Adapted from the kernel's version.  You can use this in any user
context; no one ever actually waits on someone else.

user/parlib/include/poke.h [new file with mode: 0644]
user/parlib/poke.c [new file with mode: 0644]

diff --git a/user/parlib/include/poke.h b/user/parlib/include/poke.h
new file mode 100644 (file)
index 0000000..1f1481d
--- /dev/null
@@ -0,0 +1,47 @@
+/* Copyright (c) 2012 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Post work and poke synchronization.  This is a wait-free way to make sure
+ * some code is run, usually by the calling core, but potentially by any core.
+ * Under contention, everyone just posts work, and one core will carry out the
+ * work.  Callers post work (the meaning of which is particular to their
+ * subsystem), then call this function.  The function is not run concurrently
+ * with itself.
+ *
+ * As far as uthreads, vcores, and preemption go, poking is safe in uthread
+ * context and if preemptions occur.  However, a uthread running the poke
+ * function that gets preempted could delay the execution of the poke
+ * indefinitely.  In general, post-and-poke does not provide any guarantee about
+ * *when* the poke finally occurs.  If delays of this sort are a problem, then
+ * run poke() from vcore context.
+ *
+ * Adapted from the kernel's implementation. */
+
+#ifndef PARLIB_POKE_H
+#define PARLIB_POKE_H
+
+#include <ros/atomic.h>
+
+__BEGIN_DECLS
+
+struct poke_tracker {
+       atomic_t                        need_to_run;
+       atomic_t                        run_in_progress;
+       void                            (*func)(void *);
+};
+
+void poke(struct poke_tracker *tracker, void *arg);
+
+static inline void poke_init(struct poke_tracker *tracker, void (*func)(void*))
+{
+       tracker->need_to_run = 0;
+       tracker->run_in_progress = 0;
+       tracker->func = func;
+}
+
+#define POKE_INITIALIZER(f) {.func = f}
+
+__END_DECLS
+
+#endif /* PARLIB_POKE_H */
diff --git a/user/parlib/poke.c b/user/parlib/poke.c
new file mode 100644 (file)
index 0000000..d81932f
--- /dev/null
@@ -0,0 +1,66 @@
+/* Copyright (c) 2012 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Post work and poke synchronization.  This is a wait-free way to make sure
+ * some code is run, usually by the calling core, but potentially by any core.
+ * Under contention, everyone just posts work, and one core will carry out the
+ * work.  Callers post work (the meaning of which is particular to their
+ * subsystem), then call this function.  The function is not run concurrently
+ * with itself.
+ *
+ * As far as uthreads, vcores, and preemption go, poking is safe in uthread
+ * context and if preemptions occur.  However, a uthread running the poke
+ * function that gets preempted could delay the execution of the poke
+ * indefinitely.  In general, post-and-poke does not provide any guarantee about
+ * *when* the poke finally occurs.  If delays of this sort are a problem, then
+ * run poke() from vcore context.
+ *
+ * Adapted from the kernel's implementation. */
+
+#include <parlib/poke.h>
+#include <parlib/arch/atomic.h>
+#include <assert.h>
+
+/* This is the 'post (work) and poke' style of sync.  We make sure the poke
+ * tracker's function runs.  Once this returns, the func either has run or is
+ * currently running (in case someone else is running now).  We won't wait or
+ * spin or anything, and it is safe to call this recursively (deeper in the
+ * call-graph).
+ *
+ * It's up to the caller to somehow post its work.  We'll also pass arg to the
+ * func, ONLY IF the caller is the one to execute it - so there's no guarantee
+ * the func(specific_arg) combo will actually run.  It's more for info
+ * purposes/optimizations/etc.  If no one uses it, I'll get rid of it. */
+void poke(struct poke_tracker *tracker, void *arg)
+{
+       atomic_set(&tracker->need_to_run, TRUE);
+       /* will need to repeatedly do it if someone keeps posting work */
+       do {
+               /* want an wrmb() btw posting work/need_to_run and in_progress.  the
+                * swap provides the HW mb. just need a cmb, which we do in the loop to
+                * cover the iterations (even though i can't imagine the compiler
+                * reordering the check it needed to do for the branch).. */
+               cmb();
+               /* poke / make sure someone does it.  if we get a TRUE (1) back, someone
+                * is already running and will deal with the posted work.  (probably on
+                * their next loop).  if we got a 0 back, we won the race and have the
+                * 'lock'. */
+               if (atomic_swap(&tracker->run_in_progress, TRUE))
+                       return;
+               /* if we're here, then we're the one who needs to run the func. */
+               /* clear the 'need to run', since we're running it now.  new users will
+                * set it again.  this write needs to be wmb()'d after in_progress.  the
+                * swap provided the HW mb(). */
+               cmb();
+               atomic_set(&tracker->need_to_run, FALSE);       /* no internal HW mb */
+               /* run the actual function.  the poke sync makes sure only one caller is
+                * in that func at a time. */
+               assert(tracker->func);
+               tracker->func(arg);
+               wmb();  /* ensure the in_prog write comes after the run_again. */
+               atomic_set(&tracker->run_in_progress, FALSE);   /* no internal HW mb */
+               /* in_prog write must come before run_again read */
+               wrmb();
+       } while (atomic_read(&tracker->need_to_run));   /* while there's more work*/
+}