uthread_yield() takes a func* and arg
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 18 Apr 2012 21:34:23 +0000 (14:34 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 18 Apr 2012 22:42:43 +0000 (15:42 -0700)
We had a variety of ways to pass info across the uth_yield->__uth_yield
boundary.  Instead of doing all of that, we just tell uthread_yield to
run a function when it gets to vcore context.

The purpose of this function is to somehow deal with the uthread and its
reason for blocking.  This could be because it blocked on a syscall, or
perhaps for some 'external' reason, such as blocking on a mutex.  This
setup allows the mutex code to not be aware of 2LS-particulars, though
it still needs to know about uthreads.  This will help us with mutexes
and libraries that aren't bound to a particular 2LS, as well as simplify
many different mechanisms (like the pth specific stuff for join/exit).

user/parlib/include/uthread.h
user/parlib/uthread.c
user/pthread/pthread.c

index f659b50..092a3e8 100644 (file)
 #define UT_RUNNING             1
 #define UT_NOT_RUNNING 2
 
+/* Externally blocked thread reasons (for uthread_has_blocked()) */
+#define UTH_EXT_BLK_MUTEX                      1
+#define UTH_EXT_BLK_JUSTICE                    2       /* whatever.  might need more options */
+
 /* Bare necessities of a user thread.  2LSs should allocate a bigger struct and
  * cast their threads to uthreads when talking with vcore code.  Vcore/default
  * 2LS code won't touch udata or beyond. */
@@ -22,6 +26,8 @@ struct uthread {
        int flags;
        struct syscall *sysc;   /* syscall we're blocking on, if any */
        int state;
+       void (*yield_func)(struct uthread*, void*);
+       void *yield_arg;
 };
 typedef struct uthread uthread_t;
 extern __thread struct uthread *current_uthread;
@@ -31,9 +37,9 @@ struct schedule_ops {
        /* Functions supporting thread ops */
        void (*sched_entry)(void);
        void (*thread_runnable)(struct uthread *);
-       void (*thread_yield)(struct uthread *);
        void (*thread_paused)(struct uthread *);
        void (*thread_blockon_sysc)(struct uthread *, void *);
+       void (*thread_has_blocked)(struct uthread *, int);
        /* Functions event handling wants */
        void (*preempt_pending)(void);
        void (*spawn_thread)(uintptr_t pc_start, void *data);   /* don't run yet */
@@ -56,7 +62,8 @@ void uthread_slim_init(void);
 /* Call this when you are done with a uthread, forever, but before you free it */
 void uthread_cleanup(struct uthread *uthread);
 void uthread_runnable(struct uthread *uthread);
-void uthread_yield(bool save_state);
+void uthread_yield(bool save_state, void (*yield_func)(struct uthread*, void*),
+                   void *yield_arg);
 
 /* Utility functions */
 bool __check_preempt_pending(uint32_t vcoreid);        /* careful: check the code */
index 309752e..2f957e1 100644 (file)
@@ -216,10 +216,27 @@ void uthread_runnable(struct uthread *uthread)
        sched_ops->thread_runnable(uthread);
 }
 
+/* Informs the 2LS that its thread blocked, and it is not under the control of
+ * the 2LS.  This is for informational purposes, and some semantic meaning
+ * should be passed by flags (from uthread.h's UTH_EXT_BLK_xxx options).
+ * Eventually, whoever calls this will call uthread_runnable(), giving the
+ * thread back to the 2LS.
+ *
+ * If code outside the 2LS has blocked a thread (via uthread_yield) and ran its
+ * own callback/yield_func instead of some 2LS code, that callback needs to
+ * call this.
+ *
+ * AKA: obviously_a_uthread_has_blocked_in_lincoln_park() */
+void uthread_has_blocked(struct uthread *uthread, int flags)
+{
+       if (sched_ops->thread_has_blocked)
+               sched_ops->thread_has_blocked(uthread, flags);
+}
+
 /* Need to have this as a separate, non-inlined function since we clobber the
  * stack pointer before calling it, and don't want the compiler to play games
  * with my hart. */
-static void __attribute__((noinline, noreturn)) 
+static void __attribute__((noinline, noreturn))
 __uthread_yield(void)
 {
        struct uthread *uthread = current_uthread;
@@ -229,19 +246,10 @@ __uthread_yield(void)
         * uthread_destroy() */
        uthread->flags &= ~UTHREAD_DONT_MIGRATE;
        uthread->state = UT_NOT_RUNNING;
-       /* Determine if we're blocking on a syscall or just yielding.  Might end
-        * up doing this differently when/if we have more ways to yield. */
-       if (uthread->sysc) {
-               assert(sched_ops->thread_blockon_sysc);
-               sched_ops->thread_blockon_sysc(uthread, uthread->sysc);
-               /* make sure you don't touch uthread after that sched ops call */
-       } else { /* generic yield */
-               assert(sched_ops->thread_yield);
-               /* 2LS will save the thread somewhere for restarting.  Later on,
-                * we'll probably have a generic function for all sorts of waiting.
-                */
-               sched_ops->thread_yield(uthread);
-       }
+       /* Do whatever the yielder wanted us to do */
+       assert(uthread->yield_func);
+       uthread->yield_func(uthread, uthread->yield_arg);
+       /* Make sure you do not touch uthread after that func call */
        /* Leave the current vcore completely */
        current_uthread = NULL;
        /* Go back to the entry point, where we can handle notifications or
@@ -249,14 +257,25 @@ __uthread_yield(void)
        uthread_vcore_entry();
 }
 
-/* Calling thread yields.  Both exiting and yielding calls this, the difference
- * is the thread's state (in the flags). */
-void uthread_yield(bool save_state)
+/* Calling thread yields for some reason.  Set 'save_state' if you want to ever
+ * run the thread again.  Once in vcore context in __uthread_yield, yield_func
+ * will get called with the uthread and yield_arg passed to it.  This way, you
+ * can do whatever you want when you get into vcore context, which can be
+ * thread_blockon_sysc, unlocking mutexes, joining, whatever.
+ *
+ * If you do *not* pass a 2LS sched op or other 2LS function as yield_func,
+ * then you must also call uthread_has_blocked(flags), which will let the 2LS
+ * know a thread blocked beyond its control (and why). */
+void uthread_yield(bool save_state, void (*yield_func)(struct uthread*, void*),
+                   void *yield_arg)
 {
        struct uthread *uthread = current_uthread;
        volatile bool yielding = TRUE; /* signal to short circuit when restarting */
        assert(!in_vcore_context());
        assert(uthread->state == UT_RUNNING);
+       /* Pass info to ourselves across the uth_yield -> __uth_yield transition. */
+       uthread->yield_func = yield_func;
+       uthread->yield_arg = yield_arg;
        /* Don't migrate this thread to another vcore, since it depends on being on
         * the same vcore throughout (once it disables notifs).  The race is that we
         * read vcoreid, then get interrupted / migrated before disabling notifs. */
@@ -289,7 +308,7 @@ void uthread_yield(bool save_state)
        /* Change to the transition context (both TLS and stack). */
        extern void** vcore_thread_control_blocks;
        set_tls_desc(vcore_thread_control_blocks[vcoreid], vcoreid);
-       assert(current_uthread == uthread);     
+       assert(current_uthread == uthread);
        assert(in_vcore_context());     /* technically, we aren't fully in vcore context */
        /* After this, make sure you don't use local variables.  Also, make sure the
         * compiler doesn't use them without telling you (TODO).
@@ -349,9 +368,10 @@ void __ros_mcp_syscall_blockon(struct syscall *sysc)
        /* double check before doing all this crap */
        if (atomic_read(&sysc->flags) & (SC_DONE | SC_PROGRESS))
                return;
-       /* So yield knows we are blocking on something */
+       /* Debugging: so we can match sysc when it tries to wake us up later */
        current_uthread->sysc = sysc;
-       uthread_yield(TRUE);
+       /* yield, calling 2ls-blockon(cur_uth, sysc) on the other side */
+       uthread_yield(TRUE, sched_ops->thread_blockon_sysc, sysc);
 }
 
 /* Helper for run_current and run_uthread.  Make sure the uthread you want to
index 01d5796..0d0612a 100644 (file)
@@ -35,9 +35,9 @@ static inline void spin_to_sleep(unsigned int spins, unsigned int *spun);
 /* Pthread 2LS operations */
 void pth_sched_entry(void);
 void pth_thread_runnable(struct uthread *uthread);
-void pth_thread_yield(struct uthread *uthread);
 void pth_thread_paused(struct uthread *uthread);
 void pth_thread_blockon_sysc(struct uthread *uthread, void *sysc);
+void pth_thread_has_blocked(struct uthread *uthread, int flags);
 void pth_preempt_pending(void);
 void pth_spawn_thread(uintptr_t pc_start, void *data);
 
@@ -47,9 +47,9 @@ static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type);
 struct schedule_ops pthread_sched_ops = {
        pth_sched_entry,
        pth_thread_runnable,
-       pth_thread_yield,
        pth_thread_paused,
        pth_thread_blockon_sysc,
+       pth_thread_has_blocked,
        0, /* pth_preempt_pending, */
        0, /* pth_spawn_thread, */
 };
@@ -148,7 +148,8 @@ void pth_thread_runnable(struct uthread *uthread)
 /* The calling thread is yielding.  Do what you need to do to restart (like put
  * yourself on a runqueue), or do some accounting.  Eventually, this might be a
  * little more generic than just yield. */
-void pth_thread_yield(struct uthread *uthread)
+/* TODO: keeping this around temporarily */
+static void pth_thread_yield(struct uthread *uthread, void *junk)
 {
        struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
        struct pthread_tcb *temp_pth = 0;       /* used for exiting AND joining */
@@ -226,14 +227,6 @@ void pth_thread_paused(struct uthread *uthread)
        uthread_runnable(uthread);
 }
 
-void pth_preempt_pending(void)
-{
-}
-
-void pth_spawn_thread(uintptr_t pc_start, void *data)
-{
-}
-
 /* Restarts a uthread hanging off a syscall.  For the simple pthread case, we
  * just make it runnable and let the main scheduler code handle it. */
 static void restart_thread(struct syscall *sysc)
@@ -242,7 +235,7 @@ static void restart_thread(struct syscall *sysc)
        /* uthread stuff here: */
        assert(ut_restartee);
        assert(((struct pthread_tcb*)ut_restartee)->state == PTH_BLK_SYSC);
-       assert(ut_restartee->sysc == sysc);
+       assert(ut_restartee->sysc == sysc);     /* set in uthread.c */
        ut_restartee->sysc = 0; /* so we don't 'reblock' on this later */
        uthread_runnable(ut_restartee);
 }
@@ -297,6 +290,27 @@ void pth_thread_blockon_sysc(struct uthread *uthread, void *syscall)
        /* GIANT WARNING: do not touch the thread after this point. */
 }
 
+void pth_thread_has_blocked(struct uthread *uthread, int flags)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       /* could imagine doing something with the flags.  For now, we just treat all
+        * externally blocked reasons as 'MUTEX'.  Whatever we do here, we are
+        * mostly communicating to our future selves in pth_thread_runnable(), which
+        * gets called by whoever triggered this callback */
+       pthread->state = PTH_BLK_MUTEX;
+       /* Just for yucks: */
+       if (flags == UTH_EXT_BLK_JUSTICE)
+               printf("For great justice!\n");
+}
+
+void pth_preempt_pending(void)
+{
+}
+
+void pth_spawn_thread(uintptr_t pc_start, void *data)
+{
+}
+
 /* Pthread interface stuff and helpers */
 
 int pthread_attr_init(pthread_attr_t *a)
@@ -489,7 +503,7 @@ int pthread_join(pthread_t thread, void** retval)
                /* Time to join, set things up so pth_thread_yield() knows what to do */
                caller->state = PTH_BLK_JOINING;
                caller->join_target = thread;
-               uthread_yield(TRUE);
+               uthread_yield(TRUE, pth_thread_yield, 0);
                /* When we return/restart, the thread will be done */
        } else {
                assert(thread->joiner == thread);       /* sanity check */
@@ -504,7 +518,7 @@ int pthread_yield(void)
 {
        struct pthread_tcb *caller = (struct pthread_tcb*)current_uthread;
        caller->state = PTH_BLK_YIELDING;
-       uthread_yield(TRUE);
+       uthread_yield(TRUE, pth_thread_yield, 0);
        return 0;
 }
 
@@ -688,7 +702,7 @@ void pthread_exit(void *ret)
        pthread->retval = ret;
        /* So our pth_thread_yield knows we want to exit */
        pthread->state = PTH_EXITING;
-       uthread_yield(FALSE);
+       uthread_yield(FALSE, pth_thread_yield, 0);
 }
 
 int pthread_once(pthread_once_t* once_control, void (*init_routine)(void))