Kernel can detect degenerate SCPs (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 21 Mar 2012 19:44:40 +0000 (12:44 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 21 Mar 2012 19:44:40 +0000 (12:44 -0700)
Meaning, the SCP can't go into vcore context yet (very early in the life
of a process).  Once userspace turns off the VC_SCP_NOVCCTX flag, the
kernel will put the SCP into vc ctx, when appropriate (notified).

Documentation/async_events.txt
kern/include/ros/event.h
kern/src/process.c
user/parlib/uthread.c
user/pthread/pthread.c

index 7678afc..e7fde52 100644 (file)
@@ -4,7 +4,8 @@ Barret Rhoden
 1. Overview
 2. Async Syscalls and I/O
 3. Event Delivery / Notification
-4. Misc Things That Aren't Sorted Completely:
+4. Single-core Process (SCP) Events
+5. Misc Things That Aren't Sorted Completely:
 
 1. Overview
 ====================
@@ -689,14 +690,94 @@ 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. Single-core Process (SCP) Events:
 ====================
-4.1 What about short handlers?
+4.1 Basics:
+---------------------------------------
+Event delivery is important for SCP's blocking syscalls.  It can also be used
+(in the future) to deliver POSIX signals, which would just be another kernel
+event.
+
+SCPs can receive events just like MCPs.  For the most part, the code paths are
+the same on both sides of the K/U interface.  The kernel sends events (which
+can detect an SCP and will send it to vcore0), the kernel will make sure you
+can't yield/miss an event, etc.  Userspace preps vcore context in advance, and
+can do all the things vcore context does: handle events, select thread to run.
+For an SCP, there is only one thread to run.
+
+4.2 Degenerate Event Delivery:
+---------------------------------------
+That being said, there are a few tricky things.  First, there is a time before
+the SCP is ready to fully receive events.  Specifically, before
+vcore_event_init(), which is called out of glibc's _start.  More importantly,
+the runtime linker never calls that function, yet it wants to block.
+
+The important thing to note is that there are a few parts to event delivery:
+registration (user), sending the event (kernel), making sure the proc wakes up
+(kernel), and actually handling the event (user).  For syscalls, the only thing
+the process (even rtld) needs is the first three.  Registration is easy - can be
+done with nothing more than kernel headers (no need for parlib) for NO_MSG ev_qs
+(no need to init the UCQ).  Event handling is trickier, and requires parlib
+(which rtld can't link against).  To support processes that could register for
+events, but not handle them (or even enter vcore context), the kernel needed a
+few changes (checking the VC_SCP_NOVCCTX flag) so that it would wake the
+process, but never put it in vcore context.  
+
+This degenerate event handling just wakes the process up, at which point it can
+check on its syscall.  Very early in the process's life, it'll init vcore0's
+UCQ and be able to handle full events, enter vcore context, etc.
+
+Once the SCP is up and running, it can receive events like normal.  One thing to
+note is that the SCPs are not using a handle_syscall() event handler, like the
+MCPs do.  They are only using the event to get the process restarted, at which
+point their vcore 0 restarts thread0.  One consequence of this is that if a
+process receives an unrelated event while blocking on a syscall, it'll handle
+that event, then restart thread0.  Thread0 will see its syscall isn't complete,
+and then re-block.  (It also re-registers its ev_q, which is harmless).  When
+that syscall is finally done, the kernel will send an event and wake it up
+again.
+
+4.3 Extra Tidbits:
+---------------------------------------
+One minor point: SCPs can't receive INDIRs, at least for now.  The kernel event
+code short circuits all the fallback business and just spams vcore0's public
+mbox.  If we ever change that, we need to be sure to post a notif_pending to
+vcore0 (that's the signal to trigger a wakeup).
+
+If we receive an event right as we transition from SCP to MCP, vcore0 could get
+spammed with a message that is never received.  Right now, it's not a problem,
+since vcore0 is the first vcore that will get woken up as an MCP.  This could be
+an issue if we ever allow transitions from MCP back to SCP.
+
+On a related note, it's now wrong for SCPs to sys_yield(FALSE) (not being nice,
+meaning they are waiting for an event) in a loop that does not check events or
+otherwise allow them to break out of that loop.  This should be fairly obvious.
+A little more subtle is that these loops also need to sort out notif_pending.
+If you are trying to yield and still have an old notif_pending set, the kernel
+won't let you yield (it thinks you are missing the notif).  For the degenerate
+mode, (VC_SCP_NOVCCTX is set on vcore0), the kernel will handle dealing with
+this flag.
+
+Finally, note that while the SCP is in vcore context, it has none of the
+guarantees of an MCP.  It's somewhat meaningless to talk about being gang
+scheduled or knowing about the state of other vcores.  If you're running, you're
+on a physical core.  You may get unexpected interrupts, descheduled, etc.  Aside
+from the guarantees and being the only vcore, the main differences are really up
+to the kernel scheduler.  In that sense, we have somewhat of a new state for
+processes - SCPs that can enter vcore context.  From the user's perspective,
+they look a lot like an MCP, and the degenerate/early mode SCPs are like the
+old, dumb SCPs.  The big difference for userspace is that there isn't a 2LS yet
+(will need to reinit things slightly).  The kernel treats SCPs and MCPs very
+differently too, but that may not always be the case.
+
+5. Misc Things That Aren't Sorted Completely:
+====================
+5.1 What about short handlers?
 ---------------------------------------
 Once we sort the other issues, we can ask for them via a flag in the event_q,
 and run the handler in the event_q struct.
 
-4.2 What about blocking on a syscall?
+5.2 What about blocking on a syscall?
 ---------------------------------------
 The current plan is to set a flag, and let the kernel go from there.  The
 kernel knows which process it is, since that info is saved in the kthread that
index e0d3b8c..e038267 100644 (file)
@@ -102,6 +102,7 @@ struct event_queue_big {
 #define VC_PREEMPTED                   0x002                           /* VC is preempted */
 #define VC_RECOVERING                  0x004                           /* VC being recovered */
 #define VC_UTHREAD_STEALING            0x008                           /* Uthread being stolen */
+#define VC_SCP_NOVCCTX                 0x010                           /* can't go into vc ctx */
 
 /* Per-core data about preemptions and notifications */
 struct preempt_data {
index 0734d9e..b12130a 100644 (file)
@@ -38,6 +38,7 @@ static uint32_t get_vcoreid(struct proc *p, uint32_t pcoreid);
 static uint32_t try_get_pcoreid(struct proc *p, uint32_t vcoreid);
 static uint32_t get_pcoreid(struct proc *p, uint32_t vcoreid);
 static void __proc_free(struct kref *kref);
+static bool scp_is_vcctx_ready(struct preempt_data *vcpd);
 
 /* PID management. */
 #define PID_MAX 32767 // goes from 0 to 32767, with 0 reserved
@@ -209,6 +210,9 @@ static void proc_init_procinfo(struct proc* p)
 static void proc_init_procdata(struct proc *p)
 {
        memset(p->procdata, 0, sizeof(struct procdata));
+       /* processes can't go into vc context on vc 0 til they unset this.  This is
+        * for processes that block before initing uthread code (like rtld). */
+       atomic_set(&p->procdata->vcore_preempt_data[0].flags, VC_SCP_NOVCCTX);
 }
 
 /* Allocates and initializes a process, with the given parent.  Currently
@@ -410,6 +414,14 @@ static void __set_proc_current(struct proc *p)
        }
 }
 
+/* Flag says if vcore context is not ready, which is set in init_procdata.  The
+ * process must turn off this flag on vcore0 at some point.  It's off by default
+ * on all other vcores. */
+static bool scp_is_vcctx_ready(struct preempt_data *vcpd)
+{
+       return !(atomic_read(&vcpd->flags) & VC_SCP_NOVCCTX);
+}
+
 /* Dispatches a _S process to run on the current core.  This should never be
  * called to "restart" a core.   
  *
@@ -459,8 +471,10 @@ void proc_run_s(struct proc *p)
                        pcpui->owning_proc = p;
                        /* TODO: (HSS) set silly state here (__startcore does it instantly) */
                        /* similar to the old __startcore, start them in vcore context if
-                        * they have notifs and aren't already in vcore context. */
-                       if (!vcpd->notif_disabled && vcpd->notif_pending) {
+                        * they have notifs and aren't already in vcore context.  o/w, start
+                        * them wherever they were before (could be either vc ctx or not) */
+                       if (!vcpd->notif_disabled && vcpd->notif_pending
+                                                 && scp_is_vcctx_ready(vcpd)) {
                                vcpd->notif_disabled = TRUE;
                                /* save the _S's tf in the notify slot, build and pop a new one
                                 * in actual/cur_tf. */
@@ -470,6 +484,12 @@ void proc_run_s(struct proc *p)
                                proc_init_trapframe(pcpui->cur_tf, 0, p->env_entry,
                                                    vcpd->transition_stack);
                        } else {
+                               /* If they have no transition stack, then they can't receive
+                                * events.  The most they are getting is a wakeup from the
+                                * kernel.  They won't even turn off notif_pending, so we'll do
+                                * that for them. */
+                               if (!scp_is_vcctx_ready(vcpd))
+                                       vcpd->notif_pending = FALSE;
                                /* this is one of the few times cur_tf != &actual_tf */
                                pcpui->cur_tf = &p->env_tf;
                        }
@@ -909,8 +929,13 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
                                /* this check is an early optimization (check, signal, check
                                 * again pattern).  We could also lock before spamming the
                                 * vcore in event.c */
-                               if (vcpd->notif_pending)
+                               if (vcpd->notif_pending) {
+                                       /* they can't handle events, just need to prevent a yield.
+                                        * (note the notif_pendings are collapsed). */
+                                       if (!scp_is_vcctx_ready(vcpd))
+                                               vcpd->notif_pending = FALSE;
                                        goto out_failed;
+                               }
                                /* syncing with event's SCP code.  we set waiting, then check
                                 * pending.  they set pending, then check waiting.  it's not
                                 * possible for us to miss the notif *and* for them to miss
@@ -920,6 +945,8 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
                                wrmb(); /* don't let the state write pass the notif read */ 
                                if (vcpd->notif_pending) {
                                        __proc_set_state(p, PROC_RUNNING_S);
+                                       if (!scp_is_vcctx_ready(vcpd))
+                                               vcpd->notif_pending = FALSE;
                                        goto out_failed;
                                }
                                /* if we're here, we want to sleep.  a concurrent event that
@@ -1799,6 +1826,10 @@ void __notify(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2)
         * after we unmap. */
        vcoreid = get_vcoreid(p, coreid);
        vcpd = &p->procdata->vcore_preempt_data[vcoreid];
+       /* for SCPs that haven't (and might never) call vc_event_init, like rtld.
+        * this is harmless for MCPS to check this */
+       if (!scp_is_vcctx_ready(vcpd))
+               return;
        printd("received active notification for proc %d's vcore %d on pcore %d\n",
               p->procinfo->pid, vcoreid, coreid);
        /* sort signals.  notifs are now masked, like an interrupt gate */
index c791200..70187af 100644 (file)
@@ -84,6 +84,23 @@ int uthread_lib_init(struct uthread *uthread)
        return 0;
 }
 
+/* Helper: tells the kernel our SCP is capable of going into vcore context on
+ * vcore 0.  Pairs with k/s/process.c scp_is_vcctx_ready(). */
+static void scp_vcctx_ready(void)
+{
+       struct preempt_data *vcpd = vcpd_of(0);
+       long old_flags;
+       /* the CAS is a bit overkill; keeping it around in case people use this
+        * code in other situations. */
+       do {
+               old_flags = atomic_read(&vcpd->flags);
+               /* Spin if the kernel is mucking with the flags */
+               while (old_flags & VC_K_LOCK)
+                       old_flags = atomic_read(&vcpd->flags);
+       } while (!atomic_cas(&vcpd->flags, old_flags,
+                            old_flags & ~VC_SCP_NOVCCTX));
+}
+
 /* Slim-init - sets up basic uthreading for when we are in _S mode and before
  * we set up the 2LS.  Some apps may not have a 2LS and thus never do the full
  * vcore/2LS/uthread init. */
@@ -93,6 +110,7 @@ void uthread_slim_init(void)
        /* TODO: consider a vcore_init_vc0 call.  Init the vcore system */
        assert(!vcore_init());
        uthread_manage_thread0(uthread);
+       scp_vcctx_ready();
 }
 
 /* 2LSs shouldn't call uthread_vcore_entry directly */
index 4765c6c..88fb284 100644 (file)
@@ -235,6 +235,14 @@ static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type)
 {
        struct syscall *sysc;
        assert(in_vcore_context());
+       /* if we just got a bit (not a msg), it should be because the process is
+        * still an SCP and hasn't started using the MCP ev_q yet (using the simple
+        * ev_q and glibc's blockon) or because the bit is still set from an old
+        * ev_q (blocking syscalls from before we could enter vcore ctx).  Either
+        * way, just return.  Note that if you screwed up the pth ev_q and made it
+        * NO_MSG, you'll never notice (we used to assert(ev_msg)). */
+       if (!ev_msg)
+               return;
        /* It's a bug if we don't have a msg (we're handling a syscall bit-event) */
        assert(ev_msg);
        /* Get the sysc from the message and just restart it */