Quickly return/pop DONT_MIGRATE uthreads
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 6 Oct 2011 00:20:32 +0000 (17:20 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:36:08 +0000 (17:36 -0700)
Two rules:

1) Don't check messages/handle events when you have a DONT_MIGRATE
uthread.  This will become important in the future.

2) All uses of DONT_MIGRATE must reenable notifs (and check messages) at
some point.  One such case is uthread_yield().  Another is
mcs_unlock_notifsafe().

This patch handles the case where a uthread turns on DONT_MIGRATE and
gets IPI'd before it can disable_notif.  We simply return to it, leaving
notif_pending turned on so that we make sure we will go back into vcore
context and check messages at the next safe opportunity.  That makes
this the one time we can leave vcore context with notif_pending: since
we know we'll get back to it right away.

Another way to look at it is that we lost the race, and got the IPI
before disabling.  This allows us to pretend like we won the race
(barring any bugs...).

Documentation/async_events.txt
user/parlib/event.c
user/parlib/include/i686/vcore.h
user/parlib/include/sparc/vcore.h
user/parlib/uthread.c
user/parlib/vcore.c

index a081470..5fa6bd6 100644 (file)
@@ -601,6 +601,36 @@ functions in the kernel, much like the Round Robin set will need to do.  No
 need to force things to fit just for the sake of using a 'solution'.  We want
 tools to make solutions, not packaged solutions.
 
+3.8 UTHREAD_DONT_MIGRATE
+---------------------------------------
+DONT_MIGRATE exists to allow uthreads to disable notifications/IPIs and enter
+vcore context.  It is needed since you need to read vcoreid to disable notifs,
+but once you read it, you need to not move to another vcore.  Here are a few
+rules/guidelines.
+
+We turn off the flag so that we can disable notifs, but turn the flag back on
+before enabling.  The thread won't get migrated in that instant since notifs are
+off.  But if it was the other way, we could miss a message (because we skipped
+an opportunity to be dropped into vcore context to read a message).
+
+Don't check messages/handle events when you have a DONT_MIGRATE uthread.  There
+are issues with preemption recovery if you do.  In short, if two uthreads are
+both DONT_MIGRATE with notifs enabled on two different vcores, and one vcore
+gets preempted while the other gets an IPI telling it to recover the other one,
+both could keep bouncing back and forth if they handle their preemption
+*messages* without dealing with their own DONT_MIGRATEs first.
+
+All uses of DONT_MIGRATE must reenable notifs (and check messages) at some
+point.  One such case is uthread_yield().  Another is mcs_unlock_notifsafe().
+Note that mcs_notif_safe locks have uthreads that can't migrate for a
+potentially long time.  notifs are also disabled, so it's not a big deal.  It's
+basically just the same as if you were in vcore context (though technically you
+aren't) when it comes to preemption recovery: we'll just need to restart the
+vcore via a syscall.  Also note that it would be a real pain in the ass to
+migrate a notif_safe locking uthread.  The whole point of it is in case it grabs
+a lock that would be held by vcore context, and there's no way to know it isn't
+a lock on the restart-path.
+
 4. Misc Things That Aren't Sorted Completely:
 ====================
 4.1 What about short handlers?
index 04b27af..d435d50 100644 (file)
@@ -183,11 +183,12 @@ static int handle_mbox(struct event_mbox *ev_mbox, unsigned int flags)
 {
        int retval = 0;
        uint32_t vcoreid = vcore_id();
-
+       /* Make sure no one made it in here with a DONT_MIGRATE */
+       if (current_uthread)
+               assert(!(current_uthread->flags & UTHREAD_DONT_MIGRATE));
        printd("[event] handling ev_mbox %08p on vcore %d\n", ev_mbox, vcore_id());
        /* Handle full messages.  Will deal with bits later. */
        retval = handle_mbox_msgs(ev_mbox);
-
        /* Process all bits, if they requested NOMSG.  o/w, we'll skip the bitmask
         * scan.
         *
index dd6f16f..bc32fdf 100644 (file)
@@ -125,6 +125,52 @@ static inline void pop_ros_tf(struct user_trapframe *tf, uint32_t vcoreid)
                      : "memory");
 }
 
+/* Like the regular pop_ros_tf, but this one doesn't check or clear
+ * notif_pending. */
+static inline void pop_ros_tf_raw(struct user_trapframe *tf, uint32_t vcoreid)
+{
+       struct restart_helper *rst;
+       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
+       if (!tf->tf_cs) { /* sysenter TF.  esp and eip are in other regs. */
+               tf->tf_esp = tf->tf_regs.reg_ebp;
+               tf->tf_eip = tf->tf_regs.reg_edx;
+       }
+       /* The stuff we need to write will be below the current stack of the utf */
+       rst = (struct restart_helper*)((void*)tf->tf_esp -
+                                      sizeof(struct restart_helper));
+       /* Fill in the info we'll need later */
+       rst->notif_enab_loc = (uint32_t)&vcpd->notif_enabled;
+       rst->eax_save = 0;                      /* avoid bugs */
+       rst->eflags = tf->tf_eflags;
+       rst->eip = tf->tf_eip;
+
+       asm volatile ("movl %0,%%esp;        " /* jump esp to the utf */
+                     "popal;                " /* restore normal registers */
+                     "addl $0x24,%%esp;     " /* move to the esp slot in the tf */
+                     "popl %%esp;           " /* change to the utf's %esp */
+                     "subl $0x08,%%esp;     " /* move esp to below eax's slot */
+                     "pushl %%eax;          " /* save eax, will clobber soon */
+                                 "movl %2,%%eax;        " /* sizeof struct syscall */
+                                 "addl $0x0c,%%eax;     " /* more offset btw eax/notif_en_loc*/
+                     "subl %%eax,%%esp;     " /* move to notif_en_loc slot */
+                     "popl %%eax;           " /* load notif_enabaled addr */
+                     "movb $0x01,(%%eax);   " /* enable notifications */
+                                 /* Here's where we differ from the regular pop_ros_tf().  We
+                                  * do the same pops/esp moves, just to keep things similar
+                                  * and simple, but don't do test, clear notif_pending, or
+                                  * call a syscall. */
+                                 /* From here down, we can get interrupted and restarted */
+                     "popl %%eax;           " /* get notif_pending status */
+                                 "popl %%eax;           " /* discard &sysc (on non-sc path) */
+                     "addl %2,%%esp;        " /* jump over the sysc (both paths) */
+                     "popl %%eax;           " /* restore tf's %eax */
+                                 "popfl;                " /* restore utf's eflags */
+                     "ret;                  " /* return to the new PC */
+                     :
+                     : "g"(tf), "i"(T_SYSCALL), "i"(sizeof(struct syscall))
+                     : "memory");
+}
+
 /* Save the current context/registers into the given tf, setting the pc of the
  * tf to the end of this function.  You only need to save that which you later
  * restore with pop_ros_tf(). */
index d73cfeb..a88d84d 100644 (file)
@@ -64,6 +64,34 @@ static inline void pop_ros_tf(struct user_trapframe *tf, uint32_t vcoreid)
        asm volatile ("mov %0, %%o0; ta 4" : : "r"(tf) : "memory");
 }
 
+/* Like the regular pop_ros_tf, but this one doesn't check or clear
+ * notif_pending.  TODO: someone from sparc should look at this. */
+static inline void pop_ros_tf_raw(struct user_trapframe *tf, uint32_t vcoreid)
+{
+       // since we're changing the stack, move stuff into regs for now
+       register uint32_t _vcoreid = vcoreid;
+       register struct user_trapframe* _tf = tf;
+
+       set_stack_pointer((void*)tf->gpr[14]);
+
+       tf = _tf;
+       vcoreid = _vcoreid;
+       struct preempt_data* vcpd = &__procdata.vcore_preempt_data[vcoreid];
+
+       // if this is a trap frame we just init'ed, we need to set up TLS
+       if(tf->gpr[7] == 0)
+               tf->gpr[7] = (uint32_t)get_tls_desc(vcoreid);
+       else
+               assert(tf->gpr[7] == (uint32_t)get_tls_desc(vcoreid));
+
+       vcpd->notif_enabled = true;
+       /* This is just like the regular one, but we don't bother with
+        * notif_pending.  This comment is where it was dealt with. */
+
+       // tell the kernel to load the new trapframe
+       asm volatile ("mov %0, %%o0; ta 4" : : "r"(tf) : "memory");
+}
+
 /* Save the current context/registers into the given tf, setting the pc of the
  * tf to the end of this function.  You only need to save that which you later
  * restore with pop_ros_tf(). */
index 821ca9f..fd47485 100644 (file)
@@ -16,6 +16,7 @@ __thread struct uthread *current_uthread = 0;
 static int __uthread_allocate_tls(struct uthread *uthread);
 static int __uthread_reinit_tls(struct uthread *uthread);
 static void __uthread_free_tls(struct uthread *uthread);
+static void __run_current_uthread_raw(void);
 
 /* The real 2LS calls this, passing in a uthread representing thread0.  When it
  * returns, you're in _M mode, still running thread0, on vcore0 */
@@ -61,11 +62,15 @@ int uthread_lib_init(struct uthread *uthread)
 void __attribute__((noreturn)) uthread_vcore_entry(void)
 {
        uint32_t vcoreid = vcore_id();
-
        /* Should always have notifications disabled when coming in here. */
        assert(!notif_is_enabled(vcoreid));
        assert(in_vcore_context());
-
+       /* If we have a current uthread that is DONT_MIGRATE, pop it real quick and
+        * let it disable notifs (like it wants to).  It's important that we don't
+        * check messages/handle events with a DONT_MIGRATE uthread. */
+       if (current_uthread && (current_uthread->flags & UTHREAD_DONT_MIGRATE))
+               __run_current_uthread_raw();
+       /* Otherwise, go about our usual vcore business (messages, etc). */
        check_preempt_pending(vcoreid);
        handle_events(vcoreid);
        assert(in_vcore_context());     /* double check, in case an event changed it */
@@ -266,6 +271,22 @@ void run_current_uthread(void)
        assert(0);
 }
 
+/* Runs the uthread, but doesn't care about notif pending.  Only call this when
+ * there was a DONT_MIGRATE uthread, or a similar situation where the uthread
+ * will check messages soon (like calling enable_notifs()). */
+static void __run_current_uthread_raw(void)
+{
+       uint32_t vcoreid = vcore_id();
+       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
+       /* We need to manually say we have a notif pending, so we eventually return
+        * to vcore context.  (note the kernel turned it off for us) */
+       vcpd->notif_pending = TRUE;
+       set_tls_desc(current_uthread->tls_desc, vcoreid);
+       /* Pop the user trap frame */
+       pop_ros_tf_raw(&vcpd->notif_tf, vcoreid);
+       assert(0);
+}
+
 /* Launches the uthread on the vcore.  Don't call this on current_uthread. */
 void run_uthread(struct uthread *uthread)
 {
index a5c62b2..0f8e4ac 100644 (file)
@@ -248,6 +248,9 @@ void enable_notifs(uint32_t vcoreid)
 {
        __enable_notifs(vcoreid);
        wrmb(); /* need to read after the write that enabled notifs */
+       /* Note we could get migrated before executing this.  If that happens, our
+        * vcore had gone into vcore context (which is what we wanted), and this
+        * self_notify to our old vcore is spurious and harmless. */
        if (__procdata.vcore_preempt_data[vcoreid].notif_pending)
                sys_self_notify(vcoreid, EV_NONE, 0);
 }