Support for preempting and yielding btwn two procs
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 29 Apr 2010 04:08:12 +0000 (21:08 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:46 +0000 (17:35 -0700)
Ghetto support for OSDI to have one process preempt another's cores when
there aren't enough idle cores.  Send the REQ_SOFT flag in the higher
priority process.  This will also give any cores yielded back to the
most recent preemption victim.  It's really only made for interactions
between two MCPs.

kern/src/Makefrag
kern/src/kfs.c
kern/src/process.c
kern/src/resource.c
tests/msr_cycling_vcores.c [new file with mode: 0644]

index cd0fd72..fa36147 100644 (file)
@@ -63,7 +63,8 @@ KERN_APPFILES += \
                  $(TESTS_DIR)/msr_get_cores \
                  $(TESTS_DIR)/msr_dumb_while \
                  $(TESTS_DIR)/msr_nice_while \
-                 $(TESTS_DIR)/msr_single_while
+                 $(TESTS_DIR)/msr_single_while \
+                 $(TESTS_DIR)/msr_cycling_vcores
 endif
 
 KERN_LDFLAGS   := $(KERN_LDFLAGS) -L$(OBJDIR)/$(KERN_DIR) \
index d67b39e..ca6de29 100644 (file)
@@ -51,6 +51,7 @@ DECL_PROG(msr_get_cores);
 DECL_PROG(msr_dumb_while);
 DECL_PROG(msr_nice_while);
 DECL_PROG(msr_single_while);
+DECL_PROG(msr_cycling_vcores);
 DECL_FILE(kfs_test_txt);
 DECL_FILE(hello_txt);
 #endif
@@ -69,6 +70,7 @@ struct kfs_entry kfs[MAX_KFS_FILES] = {
        KFS_PENTRY(msr_dumb_while)
        KFS_PENTRY(msr_nice_while)
        KFS_PENTRY(msr_single_while)
+       KFS_PENTRY(msr_cycling_vcores)
        KFS_FENTRY(kfs_test_txt)
        KFS_FENTRY(hello_txt)
 #endif
index fe301d7..de20ddc 100644 (file)
@@ -26,6 +26,7 @@
 #include <sys/queue.h>
 #include <frontend.h>
 #include <monitor.h>
+#include <resource.h>
 
 /* Process Lists */
 struct proc_list proc_runnablelist = TAILQ_HEAD_INITIALIZER(proc_runnablelist);
@@ -831,6 +832,10 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
        uint32_t vcoreid = get_vcoreid(p, core_id());
        struct vcore *vc = &p->procinfo->vcoremap[vcoreid];
 
+#ifdef __CONFIG_OSDI__
+       bool new_idle_core = FALSE;
+#endif /* __CONFIG_OSDI__ */
+
        /* no reason to be nice, return */
        if (being_nice && !vc->preempt_pending)
                return;
@@ -879,6 +884,9 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
                        __seq_end_write(&p->procinfo->coremap_seqctr);
                        // add to idle list
                        put_idle_core(core_id());
+#ifdef __CONFIG_OSDI__
+                       new_idle_core = TRUE;
+#endif /* __CONFIG_OSDI__ */
                        // last vcore?  then we really want 1, and to yield the gang
                        // TODO: (RMS) will actually do this.
                        if (p->procinfo->num_vcores == 0) {
@@ -894,6 +902,21 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
        }
        spin_unlock(&p->proc_lock);
        proc_decref(p, 1); // need to eat the ref passed in.
+#ifdef __CONFIG_OSDI__
+       /* If there was a change to the idle cores, try and give our core to someone who was
+        * preempted.  core_request likely won't return.  if that happens, p's
+        * context ought to be cleaned up in the proc_startcore of the new guy. (if
+        * we actually yielded)
+        * TODO: (RMS) do this more intelligently e.g.: kick_scheduler(); */
+       extern struct proc *victim;
+       if (new_idle_core && victim) {
+               /* this ghetto victim pointer is not an edible reference, and core
+                * request will eat it when it doesn't return. */
+               proc_incref(victim, 1);
+               core_request(victim);
+               proc_decref(victim, 1);
+       }
+#endif /* __CONFIG_OSDI__ */
        /* Clean up the core and idle.  For mgmt cores, they will ultimately call
         * manager, which will call schedule() and will repick the yielding proc. */
        abandon_core();
index 2794c22..da703c3 100644 (file)
 #include <schedule.h>
 #include <hashtable.h>
 
+#ifdef __CONFIG_OSDI__
+/* Whoever was preempted from.  Ghetto hacks in yield will use this to give the
+ * core back.  Assumes only one proc is getting preempted, which is true for
+ * OSDI. */
+struct proc *victim = NULL;
+#endif
+
 /* This deals with a request for more cores.  The request is already stored in
  * the proc's amt_wanted (it is compared to amt_granted). 
  *
@@ -61,6 +68,12 @@ ssize_t core_request(struct proc *p)
                __proc_kmsg_pending(p, self_ipi_pending);
                return 0;
        }
+       /* Fail if we can never handle this amount (based on how many we told the
+        * process it can get). */
+       if (p->resources[RES_CORES].amt_wanted > p->procinfo->max_vcores) {
+               spin_unlock(&p->proc_lock);
+               return -EFAIL;
+       }
        /* otherwise, see how many new cores are wanted */
        amt_new = p->resources[RES_CORES].amt_wanted -
                  p->resources[RES_CORES].amt_granted;
@@ -88,9 +101,66 @@ ssize_t core_request(struct proc *p)
                }
                num_granted = amt_new;
        } else {
+#ifdef __CONFIG_OSDI__
+               /* take what we can from the idlecoremap, then if enough aren't
+                * available, take what we can now. */  
+               num_granted = num_idlecores;
+               for (int i = 0; i < num_granted; i++) {
+                       corelist[i] = idlecoremap[num_idlecores-1];
+                       num_idlecores--;
+               }
+#else
                num_granted = 0;
+#endif /* __CONFIG_OSDI__ */
        }
        spin_unlock(&idle_lock);
+#ifdef __CONFIG_OSDI__
+       /* Ghetto, using the SOFT flag to mean "take this from someone else" */
+       if (p->resources[RES_CORES].flags & REQ_SOFT) {
+               /* And take whatever else we can from whoever is using other cores */
+               size_t num_to_preempt = amt_new - num_granted;
+               size_t num_preempted = 0;
+               
+               printd("Attempted to preempt %d cores for proc %d (%p)\n",
+                      num_to_preempt, p->pid, p);
+               /* Find and preempt some cores.  Note the skipping of core 0.  Also note
+                * this is a horrible way to do it.  A reasonably smart scheduler can
+                * check its pcoremap. */
+               for (int i = 1; i < num_cpus; i++) {
+                       victim = per_cpu_info[i].cur_proc;
+                       /* victim is a core with a current proc that isn't us */
+                       if (victim && victim != p) {
+                               printd("Preempting pcore %d from proc %d (%p)\n", i, 
+                                      victim->pid, victim);
+                               /* preempt_core technically needs an edible reference, though
+                                * currently we always return since the victim isn't current */
+                               proc_incref(victim, 1);
+                               /* no waiting or anything, just take it.  telling them 1 sec */
+                               proc_preempt_core(victim, i, 1000000);
+                               proc_decref(victim, 1);
+                               num_preempted++;
+                       }
+                       if (num_preempted == num_to_preempt)
+                               break;
+               }
+               assert(num_preempted == num_to_preempt);
+               printd("Trying to get the idlecores recently preempted.\n");
+               /* Then take the idlecores for ourself.  Cannot handle a concurrent
+                * core_request.  If this fails, things will be fucked. */
+               spin_on(num_idlecores < num_to_preempt);
+               spin_lock(&idle_lock);
+               for (int i = num_granted; i < amt_new; i++) {
+                       // grab the last one on the list
+                       corelist[i] = idlecoremap[num_idlecores-1];
+                       num_idlecores--;
+               }
+               assert(num_idlecores >= 0);
+               spin_unlock(&idle_lock);
+               num_granted += num_preempted;
+               assert(num_granted == amt_new);
+       }
+#endif /* __CONFIG_OSDI__ */
+
        // Now, actually give them out
        if (num_granted) {
                switch (p->state) {
@@ -115,6 +185,7 @@ ssize_t core_request(struct proc *p)
                                __seq_start_write(&vcpd->preempt_tf_valid);
                                /* If we remove this, vcore0 will start where the _S left off */
                                vcpd->notif_pending = TRUE;
+                               assert(vcpd->notif_enabled);
                                /* in the async case, we'll need to remotely stop and bundle
                                 * vcore0's TF.  this is already done for the sync case (local
                                 * syscall). */
@@ -143,6 +214,7 @@ ssize_t core_request(struct proc *p)
                /* give them the cores.  this will start up the extras if RUNNING_M. */
                self_ipi_pending = __proc_give_cores(p, corelist, num_granted);
                spin_unlock(&p->proc_lock);
+               // TODO: (RMS) think about this, esp when its called from a scheduler
                __proc_kmsg_pending(p, self_ipi_pending);
                /* if there's a race on state (like DEATH), it'll get handled by
                 * proc_run or proc_destroy */
@@ -189,10 +261,6 @@ error_t resource_req(struct proc *p, int type, size_t amt_wanted,
        p->resources[type].flags = flags;
        spin_unlock(&p->proc_lock);
 
-       // no change in the amt_wanted
-       if (old_amount == amt_wanted)
-               return 0;
-
        switch (type) {
                case RES_CORES:
                        retval = core_request(p);
diff --git a/tests/msr_cycling_vcores.c b/tests/msr_cycling_vcores.c
new file mode 100644 (file)
index 0000000..6805c1a
--- /dev/null
@@ -0,0 +1,62 @@
+/* tests/msr_cycling_vcores.c
+ *
+ * This requests the max_vcores in the system, waits a bit, then gives them
+ * back, looping forever.  We can't give up all vcores, based on the current
+ * kernel, so we hold on to vcore0 to do the thinking. */
+
+#include <ros/resource.h>
+#include <parlib.h>
+#include <rstdio.h>
+#include <vcore.h>
+#include <timing.h>
+#include <mcs.h>
+
+mcs_barrier_t b;
+uint64_t begin = 0, end = 0;
+
+int main(int argc, char** argv)
+{
+
+       /* don't forget to enable notifs on vcore0.  if you don't, the kernel will
+        * restart your _S with notifs disabled, which is a path to confusion. */
+       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[0];
+       vcpd->notif_enabled = TRUE;
+
+       mcs_barrier_init(&b, max_vcores());
+
+       vcore_request(max_vcores());
+       printf("not enough vcores, going to try it manually\n");
+       sys_resource_req(RES_CORES, max_vcores(), 1, REQ_SOFT);
+       printf("We're screwed!\n");
+
+       /* should never make it here */
+       return -1;
+}
+
+void vcore_entry(void)
+{
+       uint32_t vcoreid = vcore_id();
+
+       if (vcoreid) {
+               mcs_barrier_wait(&b, vcoreid);
+               udelay(5000000);
+               if (vcoreid == 1)
+                       printf("Proc %d's vcores are yielding\n", getpid());
+               sys_yield(0);
+       } else {
+               /* trip the barrier here, all future times are in the loop */
+               mcs_barrier_wait(&b, vcoreid);
+               while (1) {
+                       udelay(15000000);
+                       printf("Proc %d requesting its cores again\n", getpid());
+                       begin = read_tsc();
+                       sys_resource_req(RES_CORES, max_vcores(), 1, REQ_SOFT);
+                       mcs_barrier_wait(&b, vcoreid);
+                       end = read_tsc();
+                       printf("Took %llu usec (%llu nsec) to get my yielded cores back.\n",
+                              udiff(begin, end), ndiff(begin, end));
+               }
+       }
+       printf("We're screwed!\n");
+       exit(-1);
+}