Preemption of user cores
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 12 Apr 2010 23:06:48 +0000 (16:06 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:42 +0000 (17:35 -0700)
This provides the basic mechanisms for preempting and restarting a
vcore.  It's been lightly tested for revoking a single core, the entire
gang, cores in notification handlers, and cores needing to be notified
when they start up.

Documentation/process-internals.txt
Documentation/processes.txt
kern/arch/i686/process.c
kern/arch/i686/trap.c
kern/arch/sparc/process.c
kern/include/process.h
kern/include/trap.h
kern/src/process.c
tests/mhello.c

index d80488c..b9ff0e5 100644 (file)
@@ -446,7 +446,7 @@ that notification hits, it will be for a proc that isn't current, and will be
 ignored (it will get run the next time that vcore fires up, handled below).
 
 There is a slight chance that the same proc will run on that pcore, but with a
-different vcore.  In the off chance this happens, the new vcore will get a
+different vcoreid.  In the off chance this happens, the new vcore will get a
 spurious notification.  Userspace needs to be able to handle spurious
 notifications anyways, (there are a couple other cases, and in general it's
 not hard to do), so this is not a problem.  Instead of trying to have the
@@ -528,12 +528,21 @@ preempt-critical locks.
 ---------------------------
 It is possible that notifications will mix with preemptions or come while a
 process is not running.  Ultimately, the process wants to be notified on a
-given vcore.  Whenever we send an active notification, we set a flag in
-procdata (notif_pending).  If the vcore is offline, we don't bother sending the IPI/notif message.  The kernel will make sure it runs
-the notification handler (as well as restoring the preempt_tf) the next time
-that vcore is restarted.  Note that userspace can toggle this, so they can
-handle the notifications from a different core if it likes, or they can
-independently send a notification.
+given vcore.  Whenever we send an active notification, we set a flag in procdata
+(notif_pending).  If the vcore is offline, we don't bother sending the IPI/notif
+message.  The kernel will make sure it runs the notification handler (as well as
+restoring the preempt_tf) the next time that vcore is restarted.  Note that
+userspace can toggle this, so they can handle the notifications from a different
+core if it likes, or they can independently send a notification.
+
+Note we use notif_pending to detect if an IPI was missed while notifs were
+disabled (this is done in pop_ros_tf() by userspace).  The overall meaning of
+notif_pending is that a vcore wants to be IPI'd.  The IPI could be in-flight, or
+it could be missed.  Since notification IPIs can be spurious, when we have
+potential races, we err on the side of sending.  This happens when pop_ros_tf()
+notifies itself, and when the kernel starts a vcore in it's notif handler if it
+was preempted and notif was pending.  In the latter case, the kernel will put
+the preempt_tf in the notif_tf, so userspace can restart that at its leisure.
 
 If a vcore has a preempt_pending, we will still send the active notification
 (IPI).  The core ought to get a notification for the preemption anyway, so we
index c435258..da62ec9 100644 (file)
@@ -477,15 +477,16 @@ trapframe.  The reason is that a preemption can come in at any time (such as
 right after returning from a preemption).
 
 To maintain this invariant, when the kernel starts a vcore, it will run the
-context that is in the preempt trapframe if the "preempt_tf_sorted" (name will
-change) flag is not set.  A process needs to be careful of a race here.  If
-they are trying to deal with a preempt trapframe (must be from another vcore,
-btw), the kernel could start to run that trapframe (in case it is granting a
-core request / proc_startcore()ing).  When the kernel prepares to use the
-trapframe (which it will do regardless of userspace activities), it will up
-the flag/counter.  If the process notices a change in that flag, it ought to
-abort its operation.  It can up the counter on its own when it no longer wants
-the kernel to run that context (this means it can get clobbered).
+context that is in the preempt trapframe if the "preempt_tf_valid" seq_ctr is
+not set.  A process needs to be careful of a race here.  If they are trying to
+deal with a preempt trapframe (must be from another vcore, btw), the kernel
+could start to run that trapframe (in case it is granting a core request /
+proc_startcore()ing).  When the kernel prepares to use the trapframe (which it
+will do regardless of userspace activities), it will up the seq_ctr.  We use a
+seq_ctr (mostly just a counter) to avoid ABA-related issues.  If the process
+notices a change in that flag, it ought to abort its operation.  It can up the
+counter on its own when it no longer wants the kernel to run that context (this
+means the preempt_tf can get clobbered).
 
 4.4: Other trickiness
 -------------------------------
@@ -542,17 +543,17 @@ hardware interrupts (on x86, at least).
 There will be cases where the trapframe in the preempt_tf slot is actually a
 notification handler, which was running on the transition stack of that
 particular vcore.  Userspace needs to be careful about restarting contexts
-that were on those cores.  They can tell by examining the stack pointer to see
-if it was on a transition stack.  Alternatively, if notifications are masked,
-it is also likely they in a notification handler.  The real concern is the
-transition stack.  If a vcore is processing on the transition stack of another
-vcore, there is a risk that the vcore comes back up and starts clobbering the
-transition stack.
-
-To avoid this, userspace should allocate a new transition stack and switch the
+that were on those cores on different cores.  They can tell by examining the
+stack pointer to see if it was on a transition stack.  Alternatively, if
+notifications are masked, it is also likely they in a notification handler.  The
+real concern is the transition stack.  If a vcore is processing on the
+transition stack of another vcore, there is a risk that the vcore comes back up
+and starts clobbering the transition stack.
+
+To avoid this, userspace could allocate a new transition stack and switch the
 target vcore to use that new stack (in procdata).  The only time (for now)
 that the kernel cares about a transition stack is when it is popping a tf on a
-new or freshly notified vcore.
+new or freshly notified vcore.  Something similar will need to be done with TLS.
 
 This all should be a rare occurance, since the vcore should see the
 preempt_pending when it starts its notification and yield, instead of being
@@ -565,6 +566,10 @@ also may need only one trapframe slot).  Userspace probably cannot guarantee
 that, so we'll have to deal with it.  Avoiding the deadlock on a spinlock is
 much more reasonable (and we can provide the locking function).
 
+Another thing to keep in mind is that userspace probably won't want to restart a
+notification handler on a different core.  It's conceivable that they want to
+take a regular user thread and context and restart it, not a transition context.
+
 4.4.4: Userspace Yield Races
 -------------------------------
 Imagine a vcore realizes it is getting preempted soon, so it starts to yield.
@@ -579,9 +584,9 @@ yield and simply return to userspace.
 4.4.5: Userspace m_yield
 -------------------------------
 There are a variety of ways to implement an m_yield (yield the entire MCP).
-We could have a "no niceness" yield - just immediately preempt, but there is a danger of the
-locking business.  We could do the usual delay game, though if userspace is
-requesting its yield, arguably we don't need to give warning. 
+We could have a "no niceness" yield - just immediately preempt, but there is a
+danger of the locking business.  We could do the usual delay game, though if
+userspace is requesting its yield, arguably we don't need to give warning. 
 
 Another approach would be to not have an explicit m_yield call.  Instead, we
 can provide a notify_all call, where the notification sent to every vcore is
index f5c7724..56d5b34 100644 (file)
@@ -33,6 +33,22 @@ void proc_init_trapframe(trapframe_t *tf, uint32_t vcoreid,
        tf->tf_regs.reg_eax = vcoreid;
 }
 
+void proc_secure_trapframe(struct trapframe *tf)
+{
+       /* we normally don't need to set the non-CS regs, but they could be
+        * gibberish and cause a GPF.  gs can still be gibberish, but we don't
+        * necessarily know what it ought to be (we could check, but that's a pain).
+        * the code protecting the kernel from TLS related things ought to be able
+        * to handle GPFs on popping gs. TODO: (TLSV) */
+       tf->tf_ds = GD_UD | 3;
+       tf->tf_es = GD_UD | 3;
+       tf->tf_fs = 0;
+       //tf->tf_gs = whatevs.  ignoring this.
+       tf->tf_ss = GD_UD | 3;
+       tf->tf_cs = GD_UT | 3;
+       tf->tf_eflags |= 0x00000200; // bit 9 is the interrupts-enabled
+}
+
 /* For cases that we won't return from a syscall via the normal path, and need
  * to set the syscall return value in the registers manually.  Like in a syscall
  * moving to RUNNING_M */
index c4111cd..f5275cf 100644 (file)
@@ -212,6 +212,14 @@ trap_dispatch(trapframe_t *tf)
        return;
 }
 
+void save_fp_state(struct ancillary_state *silly)
+{
+}
+
+void restore_fp_state(struct ancillary_state *silly)
+{
+}
+
 void
 env_push_ancillary_state(env_t* e)
 {
index 5a6a355..7728424 100644 (file)
@@ -27,6 +27,11 @@ proc_init_trapframe(trapframe_t *tf, uint32_t vcoreid,
        tf->npc = entryp+4;
 }
 
+void proc_secure_trapframe(struct trapframe *tf)
+{
+       tf->psr = PSR_S; // but PS = 0
+}
+
 /* For cases that we won't return from a syscall via the normal path, and need
  * to set the syscall return value in the registers manually.  Like in a syscall
  * moving to RUNNING_M */
index d530cc7..c619fe7 100644 (file)
@@ -145,20 +145,15 @@ void proc_decref(struct proc *SAFE p, size_t count);
 void abandon_core(void);
 
 /* Kernel message handlers for process management */
-#ifdef __IVY__
-void __startcore(trapframe_t *tf, uint32_t srcid, struct proc *CT(1) a0,
-                 trapframe_t *CT(1) a1, void *SNT a2);
-void __death(trapframe_t *tf, uint32_t srcid, void *SNT a0, void *SNT a1,
-             void *SNT a2);
-#else
 void __startcore(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
-void __death(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
 void __notify(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
-#endif
+void __preempt(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
+void __death(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
 
 /* Arch Specific */
 void proc_init_trapframe(trapframe_t *SAFE tf, uint32_t vcoreid,
                          uint32_t entryp, uint32_t stack_top);
+void proc_secure_trapframe(struct trapframe *tf);
 void proc_set_syscall_retval(trapframe_t *SAFE tf, intreg_t value);
 void __abandon_core(void);
 
index f3e92df..079cd2c 100644 (file)
@@ -36,6 +36,9 @@ void ( page_fault_handler)(trapframe_t *tf);
 void sysenter_init(void);
 extern void sysenter_handler();
 
+void save_fp_state(struct ancillary_state *silly);
+void restore_fp_state(struct ancillary_state *silly);
+
 /* Kernel messages.  Each arch implements them in their own way.  Both should be
  * guaranteeing in-order delivery.  Kept here in trap.h, since sparc is using
  * trap.h for KMs.  Eventually, both arches will use the same implementation.
index 778d1d5..c90a93c 100644 (file)
@@ -785,9 +785,9 @@ uint32_t proc_get_vcoreid(struct proc *SAFE p, uint32_t pcoreid)
 
 /* Gives process p the additional num cores listed in pcorelist.  You must be
  * RUNNABLE_M or RUNNING_M before calling this.  If you're RUNNING_M, this will
- * startup your new cores at the entry point with their virtual IDs.  If you're
- * RUNNABLE_M, you should call proc_run after this so that the process can start
- * to use its cores.
+ * startup your new cores at the entry point with their virtual IDs (or restore
+ * a preemption).  If you're RUNNABLE_M, you should call proc_run after this so
+ * that the process can start to use its cores.
  *
  * If you're *_S, make sure your core0's TF is set (which is done when coming in
  * via arch/trap.c and we are RUNNING_S), change your state, then call this.
@@ -1069,19 +1069,38 @@ void __startcore(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2)
        struct preempt_data *vcpd;
 
        assert(p_to_run);
+       /* the sender of the amsg increfed, thinking we weren't running current. */
+       if (p_to_run == current)
+               proc_decref(p_to_run, 1);
        vcoreid = get_vcoreid(p_to_run, pcoreid);
        vcpd = &p_to_run->procdata->vcore_preempt_data[vcoreid];
        printd("[kernel] startcore on physical core %d for process %d's vcore %d\n",
               pcoreid, p_to_run->pid, vcoreid);
-       memset(&local_tf, 0, sizeof(local_tf));
-       proc_init_trapframe(&local_tf, vcoreid, p_to_run->env_entry,
-                           vcpd->transition_stack);
-       /* Disable/mask active notifications for fresh vcores */
-       vcpd->notif_enabled = FALSE;
-       /* the sender of the amsg increfed, thinking we weren't running current. */
-       if (p_to_run == current)
-               proc_decref(p_to_run, 1);
-       __proc_startcore(p_to_run, &local_tf);
+
+       if (seq_is_locked(vcpd->preempt_tf_valid)) {
+               __seq_end_write(&vcpd->preempt_tf_valid); /* mark tf as invalid */
+               restore_fp_state(&vcpd->preempt_anc);
+               /* notif_pending and enabled means the proc wants to receive the IPI,
+                * but might have missed it.  copy over the tf so they can restart it
+                * later, and give them a fresh vcore. */
+               if (vcpd->notif_pending && vcpd->notif_enabled) {
+                       vcpd->notif_tf = vcpd->preempt_tf; // could memset
+                       proc_init_trapframe(&local_tf, vcoreid, p_to_run->env_entry,
+                                           vcpd->transition_stack);
+                       vcpd->notif_enabled = FALSE;
+                       vcpd->notif_pending = FALSE;
+               } else {
+                       /* copy-in the tf we'll pop, then set all security-related fields */
+                       local_tf = vcpd->preempt_tf;
+                       proc_secure_trapframe(&local_tf);
+               }
+       } else { /* not restarting from a preemption, use a fresh vcore */
+               proc_init_trapframe(&local_tf, vcoreid, p_to_run->env_entry,
+                                   vcpd->transition_stack);
+               /* Disable/mask active notifications for fresh vcores */
+               vcpd->notif_enabled = FALSE;
+       }
+       __proc_startcore(p_to_run, &local_tf); // TODO: (HSS) pass silly state *?
 }
 
 /* Bail out if it's the wrong process, or if they no longer want a notif.  Make
@@ -1129,6 +1148,35 @@ void abandon_core(void)
        smp_idle();
 }
 
+void __preempt(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2)
+{
+       struct preempt_data *vcpd;
+       uint32_t vcoreid, coreid = core_id();
+       struct proc *p = (struct proc*)a0;
+
+       if (p != current)
+               warn("__preempt arrived for a process that was not current!");
+       assert(!in_kernel(tf));
+       /* We shouldn't need to lock here, since unmapping happens on the pcore and
+        * mapping would only happen if the vcore was free, which it isn't until
+        * after we unmap. */
+       vcoreid = get_vcoreid(p, coreid);
+       vcpd = &p->procdata->vcore_preempt_data[vcoreid];
+       printk("[kernel] received __preempt for proc %d's vcore %d on pcore %d\n",
+              p->procinfo->pid, vcoreid, core_id());
+
+       /* save the old tf in the preempt slot, save the silly state, and signal the
+        * state is a valid tf.  when it is 'written,' it is valid.  Using the
+        * seq_ctrs so userspace can tell between different valid versions.  If the
+        * TF was already valid, it will panic (if CONFIGed that way). */
+       // TODO: this is assuming the struct user_tf is the same as a regular TF
+       vcpd->preempt_tf = *tf;
+       save_fp_state(&vcpd->preempt_anc);
+       __seq_start_write(&vcpd->preempt_tf_valid);
+       __unmap_vcore(p, vcoreid);
+       abandon_core();
+}
+
 /* Kernel message handler to clean up the core when a process is dying.
  * Note this leaves no trace of what was running.
  * It's okay if death comes to a core that's already idling and has no current.
index a369e81..7e0e494 100644 (file)
@@ -118,14 +118,21 @@ void hart_entry(void)
         * set the appropriate TLS.  On x86, this will involve changing the LDT
         * entry for this vcore to point to the TCB of the new user-thread. */
        if (vcoreid == 0) {
+               /* // test for preempting a notif_handler.  do it from the monitor
+               int ctr = 0;
+               while(ctr < 3) {
+                       printf("Vcore %d Spinning (%d), temp = %08x!\n", vcoreid, ctr++, temp);
+                       udelay(5000000);
+               } */
                printf("restarting vcore0 from userspace\n");
                /* Do one last check for notifs before clearing pending */
                vcpd->notif_pending = 0;
-               if (first_time) { // testing for missing a notif
+               /* // testing for missing a notif
+               if (first_time) {
                        first_time = FALSE;
                        printf("setting pending, trying to renotify etc\n");
                        vcpd->notif_pending = 1;
-               }
+               } */
                set_tls_desc(core0_tls, 0);
                /* Load silly state (Floating point) too */
                pop_ros_tf(&vcpd->notif_tf, vcoreid);