Support for blocking and restarting uthreads (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 14 Mar 2011 18:41:56 +0000 (11:41 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:36:00 +0000 (17:36 -0700)
Test it out with SYS_block.  Uthreads will call out to the 2LS to block
on syscalls.  Pthread code has an example of what to do (though it
doesn't deal with missed messages/overflow yet).

This also introduces a 'state' variable for uthreads, which is mostly
used for debugging in various places.

There's an odd bug floating around still, noticed most recently in
being in vcore_context in thread_yield.  It isn't obvious, relies on
timing, and is very rare.  If you get it regularly, call me.

Rebuild your cross compiler, or at least reinstall your kernel headers.

kern/include/ros/syscall.h
kern/src/kthread.c
kern/src/syscall.c
user/parlib/include/uthread.h
user/parlib/mcs.c
user/parlib/uthread.c
user/pthread/pthread.c

index 6423536..70a0304 100644 (file)
@@ -7,8 +7,9 @@
 #include <ros/event.h>
 
 /* Flags for an individual syscall */
-#define SC_DONE                                        0x0001
-#define SC_PROGRESS                            0x0002
+#define SC_DONE                                        0x0001          /* SC is done */
+#define SC_PROGRESS                            0x0002          /* SC made progress */
+#define SC_UEVENT                              0x0004          /* user has an ev_q */
 
 struct syscall {
        unsigned int                            num;
index 4c58310..bf70264 100644 (file)
@@ -197,6 +197,14 @@ void __launch_kthread(struct trapframe *tf, uint32_t srcid, void *a0, void *a1,
                        /* we're running the kthread from a different proc.  For now, we
                         * can't be _M, since that would be taking away someone's vcore to
                         * process another process's work. */
+                       /* Keep in mind this can happen if you yield your core after
+                        * submitting work (like sys_block()) that will complete on the
+                        * current core, and then someone else gets it.  TODO: might be
+                        * other cases. */
+                       if (cur_proc->state != PROC_RUNNING_S) {
+                               printk("cur_proc: %08p, kthread->proc: %08p\n", cur_proc,
+                                      kthread->proc);
+                       }
                        assert(cur_proc->state == PROC_RUNNING_S);
                        spin_lock(&cur_proc->proc_lock);
                        /* Wrap up / yield the current _S proc, which calls schedule_proc */
index 541adcd..4fb2a96 100644 (file)
@@ -121,8 +121,8 @@ static int sys_block(void)
 
        register_interrupt_handler(interrupt_handlers, LAPIC_TIMER_DEFAULT_VECTOR,
                                   x86_unblock_handler, sem);
-       /* This fakes a 100ms delay.  Though it might be less, esp in _M mode.  TODO
-        * KVM-timing. */
+       /* This fakes a 100ms delay.  Though it might be less, esp in _M mode.
+        * TODO KVM-timing. */
        set_core_timer(100000); /* in microseconds */
        printk("[kernel] sys_block(), sleeping at %llu\n", read_tsc());
        sleep_on(sem);
@@ -1418,7 +1418,7 @@ intreg_t syscall(struct proc *p, uintreg_t sc_num, uintreg_t a0, uintreg_t a1,
                }
        }
        if (sc_num > max_syscall || syscall_table[sc_num].call == NULL)
-               panic("Invalid syscall number %d for proc %x!", sc_num, *p);
+               panic("Invalid syscall number %d for proc %x!", sc_num, p);
 
        return syscall_table[sc_num].call(p, a0, a1, a2, a3, a4, a5);
 }
@@ -1433,7 +1433,9 @@ static void run_local_syscall(struct syscall *sysc)
        pcpui->cur_sysc = sysc;                 /* let the core know which sysc it is */
        sysc->retval = syscall(pcpui->cur_proc, sysc->num, sysc->arg0, sysc->arg1,
                               sysc->arg2, sysc->arg3, sysc->arg4, sysc->arg5);
-       sysc->flags |= SC_DONE;
+       /* Atomically turn on the SC_DONE flag.  Need the atomics since we're racing
+        * with userspace for the event_queue registration. */
+       atomic_or_int(&sysc->flags, SC_DONE); 
        signal_syscall(sysc, pcpui->cur_proc);
        /* Can unpin at this point */
 }
@@ -1462,12 +1464,16 @@ void signal_syscall(struct syscall *sysc, struct proc *p)
 {
        struct event_queue *ev_q;
        struct event_msg local_msg;
-       ev_q = sysc->ev_q;
-       if (ev_q) {
-               memset(&local_msg, 0, sizeof(struct event_msg));
-               local_msg.ev_type = EV_SYSCALL;
-               local_msg.ev_arg3 = sysc;
-               send_event(p, ev_q, &local_msg, 0);
+       /* User sets the ev_q then atomically sets the flag (races with SC_DONE) */
+       if (sysc->flags & SC_UEVENT) {
+               rmb();
+               ev_q = sysc->ev_q;
+               if (ev_q) {
+                       memset(&local_msg, 0, sizeof(struct event_msg));
+                       local_msg.ev_type = EV_SYSCALL;
+                       local_msg.ev_arg3 = sysc;
+                       send_event(p, ev_q, &local_msg, 0);
+               }
        }
 }
 
index cb34699..fc678b2 100644 (file)
@@ -2,10 +2,18 @@
 #define _UTHREAD_H
 
 #include <vcore.h>
+#include <ros/syscall.h>
 
 #define UTHREAD_DONT_MIGRATE           0x001 /* don't move to another vcore */
 #define UTHREAD_DYING                          0x002 /* uthread is exiting */
 
+/* Thread States */
+#define UT_CREATED     1
+#define UT_RUNNABLE    2
+#define UT_RUNNING     3
+#define UT_BLOCKED     4
+#define UT_DYING       5
+
 /* 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. */
@@ -14,6 +22,8 @@ struct uthread {
        struct ancillary_state as;
        void *tls_desc;
        int flags;
+       struct syscall *sysc;   /* syscall we're blocking on, if any */
+       int state;
 };
 extern __thread struct uthread *current_uthread;
 
@@ -49,6 +59,7 @@ void ros_syscall_blockon(struct syscall *sysc);
 
 /* Utility function.  Event code also calls this. */
 bool check_preempt_pending(uint32_t vcoreid);
+bool register_evq(struct syscall *sysc, struct event_queue *ev_q);
 
 /* Helpers, which sched_entry() can call */
 void run_current_uthread(void);
index f74262c..17bba43 100644 (file)
@@ -62,7 +62,7 @@ void mcs_lock_notifsafe(struct mcs_lock *lock, struct mcs_lock_qnode *qnode)
 void mcs_unlock_notifsafe(struct mcs_lock *lock, struct mcs_lock_qnode *qnode)
 {
        mcs_lock_unlock(lock, qnode);
-       if (!in_vcore_context())
+       if (!in_vcore_context() && num_vcores() > 0)
                enable_notifs(vcore_id());
 }
 
index ca4e541..aaafd2c 100644 (file)
@@ -1,4 +1,5 @@
 #include <ros/arch/membar.h>
+#include <arch/atomic.h>
 #include <parlib.h>
 #include <vcore.h>
 #include <uthread.h>
@@ -28,6 +29,8 @@ static int uthread_init(void)
        uthread->tls_desc = get_tls_desc(0);
        /* Save a pointer to the uthread in its own TLS */
        current_uthread = uthread;
+       /* Thread is currently running (it is 'us') */
+       uthread->state = UT_RUNNING;
        /* Change temporarily to vcore0s tls region so we can save the newly created
         * tcb into its current_uthread variable and then restore it.  One minor
         * issue is that vcore0's transition-TLS isn't TLS_INITed yet.  Until it is
@@ -85,6 +88,9 @@ struct uthread *uthread_create(void (*func)(void), void *udata)
        assert(!in_vcore_context());
        assert(sched_ops->thread_create);
        struct uthread *new_thread = sched_ops->thread_create(func, udata);
+       new_thread->state = UT_CREATED;
+       /* They should have zero'd the uthread.  Let's check critical things: */
+       assert(!new_thread->flags && !new_thread->sysc);
        /* Get a TLS */
        assert(!__uthread_allocate_tls(new_thread));
        /* Switch into the new guys TLS and let it know who it is */
@@ -111,7 +117,10 @@ void uthread_runnable(struct uthread *uthread)
 {
        /* Allow the 2LS to make the thread runnable, and do whatever. */
        assert(sched_ops->thread_runnable);
+       uthread->state = UT_RUNNABLE;
        sched_ops->thread_runnable(uthread);
+
+       /* TODO: consider moving this to the 2LS */
        /* Ask the 2LS how many vcores it wants, and put in the request. */
        assert(sched_ops->vcores_wanted);
        vcore_request(sched_ops->vcores_wanted());
@@ -124,14 +133,31 @@ static void __attribute__((noinline, noreturn))
 __uthread_yield(struct uthread *uthread)
 {
        assert(in_vcore_context());
-       /* Do slightly different things depending on whether or not we're exiting */
+       assert(uthread->state == UT_RUNNING);
+       assert(uthread == current_uthread);
+       /* Do slightly different things depending on whether or not we're exiting.
+        * While it is tempting to get rid of the UTHREAD_DYING flag and have
+        * callers just set the thread state, we'd lose the ability to assert
+        * UT_RUNNING, which helps catch bugs. */
        if (!(uthread->flags & UTHREAD_DYING)) {
                uthread->flags &= ~UTHREAD_DONT_MIGRATE;
-               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);
+               /* 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) {
+                       uthread->state = UT_BLOCKED;
+                       assert(sched_ops->thread_blockon_sysc);
+                       sched_ops->thread_blockon_sysc(uthread->sysc);
+               } else { /* generic yield */
+                       uthread->state = UT_RUNNABLE;
+                       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);
+               }
        } else { /* DYING */
+               printd("[U] thread %08p on vcore %d is DYING!\n", uthread, vcore_id());
+               uthread->state = UT_DYING;
                /* we alloc and manage the TLS, so lets get rid of it */
                __uthread_free_tls(uthread);
                /* 2LS specific cleanup */
@@ -193,7 +219,7 @@ void uthread_yield(void)
        __uthread_yield(current_uthread);
        /* Should never get here */
        assert(0);
-       /* Will jump here when the pthread's trapframe is restarted/popped. */
+       /* Will jump here when the uthread's trapframe is restarted/popped. */
 yield_return_path:
        printd("[U] Uthread %08p returning from a yield!\n", uthread);
 }
@@ -203,6 +229,7 @@ yield_return_path:
 void uthread_exit(void)
 {
        current_uthread->flags |= UTHREAD_DYING;
+       assert(!in_vcore_context());    /* try and catch the yield bug */
        uthread_yield();
 }
 
@@ -223,8 +250,10 @@ void ros_syscall_blockon(struct syscall *sysc)
        /* double check before doing all this crap */
        if (sysc->flags & (SC_DONE | SC_PROGRESS))
                return;
-       /* TODO: switch to vcore context, then do shit */
-       sched_ops->thread_blockon_sysc(sysc);
+       /* So yield knows we are blocking on something */
+       current_uthread->sysc = sysc;
+       assert(!in_vcore_context());    /* try and catch the yield bug */
+       uthread_yield();
 }
 
 /* Runs whatever thread is vcore's current_uthread */
@@ -233,6 +262,7 @@ void run_current_uthread(void)
        uint32_t vcoreid = vcore_id();
        struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
        assert(current_uthread);
+       assert(current_uthread->state == UT_RUNNING);
        printd("[U] Vcore %d is restarting uthread %d\n", vcoreid, uthread->id);
        clear_notif_pending(vcoreid);
        set_tls_desc(current_uthread->tls_desc, vcoreid);
@@ -245,10 +275,17 @@ void run_current_uthread(void)
 void run_uthread(struct uthread *uthread)
 {
        assert(uthread != current_uthread);
+       if (uthread->state != UT_RUNNABLE) {
+               /* had vcore3 throw this, when the UT blocked on vcore1 and didn't come
+                * back up yet (kernel didn't wake up, didn't send IPI) */
+               printf("Uthread %08p not runnable (was %d) in run_uthread on vcore %d!\n",
+                      uthread, uthread->state, vcore_id());
+       }
+       assert(uthread->state == UT_RUNNABLE);
+       uthread->state = UT_RUNNING;
        /* Save a ptr to the pthread running in the transition context's TLS */
        uint32_t vcoreid = vcore_id();
        struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
-       printd("[U] Vcore %d is starting uthread %d\n", vcoreid, uthread->id);
        current_uthread = uthread;
        clear_notif_pending(vcoreid);
        set_tls_desc(uthread->tls_desc, vcoreid);
@@ -278,6 +315,26 @@ bool check_preempt_pending(uint32_t vcoreid)
        return retval;
 }
 
+/* Attempts to register ev_q with sysc, so long as sysc is not done/progress.
+ * Returns true if it succeeded, and false otherwise. */
+bool register_evq(struct syscall *sysc, struct event_queue *ev_q)
+{
+       int old_flags;
+       sysc->ev_q = ev_q;
+       wmb();
+       /* Try and set the SC_UEVENT flag (so the kernel knows to look at ev_q) */
+       do {
+               old_flags = sysc->flags;
+               /* If the kernel finishes while we are trying to sign up for an event,
+                * we need to bail out */
+               if (old_flags & (SC_DONE | SC_PROGRESS)) {
+                       sysc->ev_q = 0;         /* not necessary, but might help with bugs */
+                       return FALSE;
+               }
+       } while (!atomic_comp_swap(&sysc->flags, old_flags, old_flags | SC_UEVENT));
+       return TRUE;
+}
+
 /* TLS helpers */
 static int __uthread_allocate_tls(struct uthread *uthread)
 {
index 94c754b..fc260a9 100644 (file)
@@ -23,6 +23,9 @@ pthread_once_t init_once = PTHREAD_ONCE_INIT;
 int threads_ready = 0;
 int threads_active = 0;
 
+/* Array of syscall event queues, one per vcore, alloced in pth_init() */
+struct event_queue *sysc_evq;
+
 /* Helper / local functions */
 static int get_next_pid(void);
 static inline void spin_to_sleep(unsigned int spins, unsigned int *spun);
@@ -39,6 +42,10 @@ void pth_preempt_pending(void);
 void pth_spawn_thread(uintptr_t pc_start, void *data);
 void pth_blockon_sysc(struct syscall *sysc);
 
+/* Event Handlers */
+static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type,
+                               bool overflow);
+
 struct schedule_ops pthread_sched_ops = {
        pth_init,
        pth_sched_entry,
@@ -72,6 +79,18 @@ struct uthread *pth_init(void)
         * to use parts of event.c to do what you want. */
        enable_kevent(EV_USER_IPI, 0, EVENT_IPI);
 
+       /* Handle syscall events.  Using small ev_qs, with no internal ev_mbox. */
+       ev_handlers[EV_SYSCALL] = pth_handle_syscall;
+       sysc_evq = malloc(sizeof(struct event_queue) * max_vcores());
+       assert(sysc_evq);
+       /* Set up each of the per-vcore syscall event queues so that they point to
+        * the VCPD/default vcore mailbox (for now)  Note you'll need the vcore to
+        * be online to get the events (for now). */
+       for (int i = 0; i < max_vcores(); i++) {
+               sysc_evq[i].ev_mbox =  &__procdata.vcore_preempt_data[i].ev_mbox;
+               sysc_evq[i].ev_flags = EVENT_IPI;               /* totally up to you */
+               sysc_evq[i].ev_vcore = i;
+       }
        /* Create a pthread_tcb for the main thread */
        pthread_t t = (pthread_t)calloc(1, sizeof(struct pthread_tcb));
        t->id = get_next_pid();
@@ -90,6 +109,7 @@ struct uthread *pth_init(void)
  * event.c (table of function pointers, stuff like that). */
 void __attribute__((noreturn)) pth_sched_entry(void)
 {
+       uint32_t vcoreid = vcore_id();
        if (current_uthread) {
                run_current_uthread();
                assert(0);
@@ -112,9 +132,13 @@ void __attribute__((noreturn)) pth_sched_entry(void)
        if (!new_thread) {
                /* TODO: consider doing something more intelligent here */
                printd("[P] No threads, vcore %d is yielding\n", vcore_id());
-               sys_yield(0);
+               /* Not actually yielding - just spin for now, so we can get syscall
+                * unblocking events */
+               vcore_idle();
+               //sys_yield(0);
                assert(0);
        }
+       assert(((struct uthread*)new_thread)->state != UT_RUNNING);
        run_uthread((struct uthread*)new_thread);
        assert(0);
 }
@@ -220,33 +244,69 @@ void pth_spawn_thread(uintptr_t pc_start, void *data)
 {
 }
 
-/* Eventually, this will be called from vcore context, after the current thread
- * has yielded and is trying to block on sysc. */
+/* 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.
+ *
+ * TODO: ought to have a queue of waiters on syscalls, and this should remove it
+ * from the list.  The list is needed in case we miss syscall events. */
+static void restart_thread(struct syscall *sysc)
+{
+       struct uthread *restartee = (struct uthread*)sysc->u_data;
+       assert(restartee);
+       assert(restartee->state == UT_BLOCKED);
+       assert(restartee->sysc == sysc);
+       restartee->sysc = 0;    /* so we don't 'reblock' on this later */
+       uthread_runnable(restartee);
+}
+
+/* This handler is usually run in vcore context, though I can imagine it being
+ * called by a uthread in some other threading library. */
+static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type,
+                               bool overflow)
+{
+       struct syscall *sysc;
+       assert(in_vcore_context());
+       /* TODO: handle overflow!! */
+       if (overflow)
+               printf("FUUUUUUUUUUUUUUUUCK, OVERFLOW!!!!!!!\n");
+       if (!ev_msg) /* just as bad as overflow */
+               return;
+       sysc = ev_msg->ev_arg3;
+       assert(sysc);
+       restart_thread(sysc);
+}
+
+/* This will be called from vcore context, after the current thread has yielded
+ * and is trying to block on sysc.  Need to put it somewhere were we can wake it
+ * up when the sysc is done.  For now, we'll have the kernel send us an event
+ * when the syscall is done. */
 void pth_blockon_sysc(struct syscall *sysc)
 {
-       printf("We tried to block on a syscall!\n");    
-       /* for now, just spin.  2LS stuff in later commits */
-       __ros_syscall_blockon(sysc);
+       int old_flags;
+       bool need_to_restart = FALSE;
 
-       #if 0
-       // testing shit.  should dont_migrate for this
-       uint32_t vcoreid = vcore_id();
-       struct event_queue local_ev_q = {0}, *ev_q = &local_ev_q;
-       ev_q->ev_mbox = &__procdata.vcore_preempt_data[vcoreid].ev_mbox;
-       ev_q->ev_flags = EVENT_IPI;
-       ev_q->ev_vcore = vcoreid;
+       assert(current_uthread->state == UT_BLOCKED);
+       /* rip from the active queue */
+       struct mcs_lock_qnode local_qn = {0};
+       struct pthread_tcb *pthread = (struct pthread_tcb*)current_uthread;
+       mcs_lock_notifsafe(&queue_lock, &local_qn);
+       threads_active--;
+       TAILQ_REMOVE(&active_queue, pthread, next);
+       mcs_unlock_notifsafe(&queue_lock, &local_qn);
+
+       /* TODO: need to register the sysc or uthread in case we lose the
+        * message.  can put it on a (per-core) tailq or something and rip it
+        * out when it unblocks. */
+
+       /* Set things up so we can wake this thread up later */
        sysc->u_data = current_uthread;
-       wmb();
-       sysc->ev_q = ev_q;
-       if (sysc->flags & (SC_DONE | SC_PROGRESS)) {
-               // try and atomically swap out our u_data.  if we got it, we restart/
-               // handle it.  o/w, the receiver of the message restarts.  we can't just
-               // let them do it, since the kernel might have checked the ev_q before
-               // we wrote it (but after it wrote the flags).
-       } else {
-               //we're done
+       /* Register our vcore's syscall ev_q to hear about this syscall. */
+       if (!register_evq(sysc, &sysc_evq[vcore_id()])) {
+               /* Lost the race with the call being done.  The kernel won't send the
+                * event.  Just restart him. */
+               restart_thread(sysc);
        }
-       #endif
+       /* GIANT WARNING: do not touch the thread after this point. */
 }
 
 /* Pthread interface stuff and helpers */
@@ -331,6 +391,7 @@ int pthread_join(pthread_t thread, void** retval)
 
 int pthread_yield(void)
 {
+       assert(!in_vcore_context());    /* try and catch the yield bug */
        uthread_yield();
        return 0;
 }