Minimizes proc_locking in core_request
[akaros.git] / kern / src / schedule.c
index a64e9ba..56df62a 100644 (file)
 #include <stdio.h>
 #include <assert.h>
 #include <atomic.h>
-#include <resource.h>
 #include <smp.h>
+#include <manager.h>
 #include <sys/queue.h>
 
 /* Process Lists */
-struct proc_list proc_runnablelist = TAILQ_HEAD_INITIALIZER(proc_runnablelist);
-spinlock_t runnablelist_lock = SPINLOCK_INITIALIZER;
+struct proc_list runnable_scps = TAILQ_HEAD_INITIALIZER(runnable_scps);
+struct proc_list all_mcps = TAILQ_HEAD_INITIALIZER(all_mcps);
+spinlock_t sched_lock = SPINLOCK_INITIALIZER;
 
 // This could be useful for making scheduling decisions.  
 /* Physical coremap: each index is a physical core id, with a proc ptr for
@@ -35,9 +36,13 @@ uint32_t idlecoremap[MAX_NUM_CPUS];
 uint32_t num_idlecores = 0;
 uint32_t num_mgmtcores = 1;
 
+/* Helper, defined below */
+static void __core_request(struct proc *p);
+
 void schedule_init(void)
 {
-       TAILQ_INIT(&proc_runnablelist);
+       TAILQ_INIT(&runnable_scps);
+       TAILQ_INIT(&all_mcps);
 
        /* Ghetto old idle core init */
        /* Init idle cores. Core 0 is the management core. */
@@ -84,78 +89,75 @@ void schedule_init(void)
        return;
 }
 
-void schedule_proc(struct proc *p)
+/* _S procs are scheduled like in traditional systems */
+void schedule_scp(struct proc *p)
 {
        /* up the refcnt since we are storing the reference */
        proc_incref(p, 1);
-       spin_lock_irqsave(&runnablelist_lock);
+       spin_lock(&sched_lock);
        printd("Scheduling PID: %d\n", p->pid);
-       TAILQ_INSERT_TAIL(&proc_runnablelist, p, proc_link);
-       spin_unlock_irqsave(&runnablelist_lock);
-       return;
+       TAILQ_INSERT_TAIL(&runnable_scps, p, proc_link);
+       spin_unlock(&sched_lock);
+}
+
+/* important to only call this on RUNNING_S, for now */
+void register_mcp(struct proc *p)
+{
+       proc_incref(p, 1);
+       spin_lock(&sched_lock);
+       TAILQ_INSERT_TAIL(&all_mcps, p, proc_link);
+       spin_unlock(&sched_lock);
+       //poke_ksched(p, RES_CORES);
 }
 
 /* Something has changed, and for whatever reason the scheduler should
- * reevaluate things.  Currently, this assumes the calling core is free (since
- * an _S can be proc_run()'d), and it only attempts to run one process at a time
- * (which will suck, longer term, since the manager will just spin this func).
+ * reevaluate things. 
  *
- * Using irqsave spinlocks for now, since this could be called from a timer
- * interrupt handler (though ought to be in a bottom half or something).
- *     - TODO: the functions we call aren't irqsafe (proc_run, for instance), so
- *     we'll have to not call this directly from interrupt context.
- */
+ * Don't call this from interrupt context (grabs proclocks). */
 void schedule(void)
 {
-       struct proc *p;
-       
-       spin_lock_irqsave(&runnablelist_lock);
-       /* TODO at the very least, we should do a TAILQ_FOREACH_SAFE, trying to sort
-        * out everyone, but with the highest priority one first (want a priority
-        * queue or something), so people don't have to loop calling schedule().
-        *
-        * Also, we don't want a 'runnable list'.  that's so _S. */
-       p = TAILQ_FIRST(&proc_runnablelist);
-       if (p) {
-               TAILQ_REMOVE(&proc_runnablelist, p, proc_link);
-               spin_unlock_irqsave(&runnablelist_lock);
-               /* TODO: consider the process's state, or push that into
-                * give_cores/core_request */
-               /* lockless reference check, safe since we hold a ref and it's dying.
-                * Note that a process can still be killed right after we do this
-                * check, but give_cores and proc_run can handle that race. */
+       struct proc *p, *temp;
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       spin_lock(&sched_lock);
+       /* trivially try to handle the needs of all our MCPS.  smarter schedulers
+        * would do something other than FCFS */
+       TAILQ_FOREACH_SAFE(p, &all_mcps, proc_link, temp) {
+               printd("Ksched has MCP %08p (%d)\n", p, p->pid);
+               /* If they are dying, abort.  There's a bit of a race here.  If they
+                * start dying right after the check, core_request/give_cores would
+                * start dealing with a DYING proc.  The code can handle it, but this
+                * will probably change. */
                if (p->state == PROC_DYING) {
+                       TAILQ_REMOVE(&all_mcps, p, proc_link);
                        proc_decref(p);
-                       return;
+                       continue;
                }
-               /* TODO: ^^^^^ this is shit i'd like to hide from pluggable kscheds */
-               printd("PID of proc i'm running: %d\n", p->pid);
-               /* We can safely read is_mcp without locking (i think). */
-               if (__proc_is_mcp(p)) {
-                       /* _Ms need to get some cores, which will call proc_run() internally
-                        * (for now) */
-                       /* TODO: this interface sucks, change it */
-                       if (!core_request(p))
-                               schedule_proc(p);       /* can't run, put it back on the queue */
-                       else
-                               /* if there's a race on state (like DEATH), it'll get handled by
-                                * proc_run or proc_destroy.  TODO: Theoretical race here, since
-                                * someone else could make p an _S (in theory), and then we
-                                * would be calling this with an inedible ref (which is
-                                * currently a concern). */
-                               spin_lock(&p->proc_lock);
-                               __proc_run_m(p); /* trying to run a RUNNABLE_M here */
-                               spin_unlock(&p->proc_lock);
+               if (!num_idlecores)
+                       break;
+               /* TODO: might use amt_wanted as a proxy.  right now, they have
+                * amt_wanted == 1, even though they are waiting.
+                * TODO: this is RACY too - just like with DYING. */
+               if (p->state == PROC_WAITING)
+                       continue;
+               __core_request(p);
+       }
+       /* prune any dying SCPs at the head of the queue and maybe sched our core */
+       while ((p = TAILQ_FIRST(&runnable_scps))) {
+               if (p->state == PROC_DYING) {
+                       TAILQ_REMOVE(&runnable_scps, p, proc_link);
+                       proc_decref(p);
                } else {
-                       /* _S proc, just run it */
-                       proc_run_s(p);
+                       /* check our core to see if we can give it out to an SCP */
+                       if (management_core() && (!pcpui->owning_proc)) {
+                               TAILQ_REMOVE(&runnable_scps, p, proc_link);
+                               printd("PID of the SCP i'm running: %d\n", p->pid);
+                               proc_run_s(p);  /* gives it core we're running on */
+                               proc_decref(p);
+                       }
+                       break;
                }
-               /* decref the ref from the TAILQ */
-               proc_decref(p);
-       } else {
-               spin_unlock_irqsave(&runnablelist_lock);
        }
-       return;
+       spin_unlock(&sched_lock);
 }
 
 /* A process is asking the ksched to look at its resource desires.  The
@@ -164,16 +166,19 @@ void schedule(void)
 void poke_ksched(struct proc *p, int res_type)
 {
        /* TODO: probably want something to trigger all res_types */
-       /* Consider races with core_req called from other pokes or schedule */
+       spin_lock(&sched_lock);
        switch (res_type) {
                case RES_CORES:
-                       /* TODO: issues with whether or not they are RUNNING.  Need to
-                        * change core_request / give_cores. */
-                       core_request(p);
+                       /* ignore core requests from non-mcps (note we have races if we ever
+                        * allow procs to switch back). */
+                       if (!__proc_is_mcp(p))
+                               break;
+                       __core_request(p);
                        break;
                default:
                        break;
        }
+       spin_unlock(&sched_lock);
 }
 
 /* Helper function to return a core to the idlemap.  It causes some more lock
@@ -196,9 +201,10 @@ uint32_t max_vcores(struct proc *p)
 #endif /* __CONFIG_DISABLE_SMT__ */
 }
 
-/* Ghetto old interface, hacked out of resource.c.  It doesn't even care about
- * the proc yet, but in general the whole core_request bit needs reworked. */
-uint32_t proc_wants_cores(struct proc *p, uint32_t *pc_arr, uint32_t amt_new)
+/* Ghetto helper, just hands out the next amt_new cores, or 0 if we can't do all
+ * of them. */
+static uint32_t get_idle_cores(struct proc *p, uint32_t *pc_arr,
+                               uint32_t amt_new)
 {
        uint32_t num_granted;
        /* You should do something smarter than just giving the stuff out.  Like
@@ -219,13 +225,59 @@ uint32_t proc_wants_cores(struct proc *p, uint32_t *pc_arr, uint32_t amt_new)
        return num_granted;
 }
 
+/* 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). */
+static void __core_request(struct proc *p)
+{
+       uint32_t num_granted, amt_new, amt_wanted, amt_granted;
+       uint32_t corelist[MAX_NUM_CPUS]; /* TODO UGH, this could be huge! */
+
+       /* TODO: consider copy-in for amt_wanted too. */
+       amt_wanted = p->procdata->res_req[RES_CORES].amt_wanted;
+       amt_granted = p->procinfo->res_grant[RES_CORES];
+
+       /* Help them out - if they ask for something impossible, give them 1 so they
+        * can make some progress. (this is racy). */
+       if (amt_wanted > p->procinfo->max_vcores) {
+               p->procdata->res_req[RES_CORES].amt_wanted = 1;
+       }
+       /* if they are satisfied, we're done.  There's a slight chance they have
+        * cores, but they aren't running (sched gave them cores while they were
+        * yielding, and now we see them on the run queue). */
+       if (amt_wanted <= amt_granted)
+               return;
+       /* otherwise, see what they want.  Current models are simple - it's just a
+        * raw number of cores, and we just give out what we can. */
+       amt_new = amt_wanted - amt_granted;
+       /* TODO: Could also consider amt_min */
+
+       /* TODO: change this.  this function is really "find me amt_new cores", the
+        * nature of this info depends on how we express desires, and a lot of that
+        * info could be lost through this interface. */
+       num_granted = get_idle_cores(p, corelist, amt_new);
+
+       /* Now, actually give them out */
+       if (num_granted) {
+               /* give them the cores.  this will start up the extras if RUNNING_M. */
+               spin_lock(&p->proc_lock);
+               __proc_give_cores(p, corelist, num_granted);
+               /* at some point after giving cores, call proc_run_m() (harmless on
+                * RUNNING_Ms).  You can give small groups of cores, then run them
+                * (which is more efficient than interleaving runs with the gives for
+                * bulk preempted processes). */
+               __proc_run_m(p); /* harmless to call this on RUNNING_Ms */
+               spin_unlock(&p->proc_lock);
+       }
+}
+
 /************** Debugging **************/
 void sched_diag(void)
 {
        struct proc *p;
-       /* just print the runnables for now */
-       TAILQ_FOREACH(p, &proc_runnablelist, proc_link)
-               printk("PID: %d\n", p->pid);
+       TAILQ_FOREACH(p, &runnable_scps, proc_link)
+               printk("_S PID: %d\n", p->pid);
+       TAILQ_FOREACH(p, &all_mcps, proc_link)
+               printk("MCP PID: %d\n", p->pid);
        return;
 }
 
@@ -237,3 +289,25 @@ void print_idlecoremap(void)
                printk("idlecoremap[%d] = %d\n", i, idlecoremap[i]);
        spin_unlock(&idle_lock);
 }
+
+void print_resources(struct proc *p)
+{
+       printk("--------------------\n");
+       printk("PID: %d\n", p->pid);
+       printk("--------------------\n");
+       for (int i = 0; i < MAX_NUM_RESOURCES; i++)
+               printk("Res type: %02d, amt wanted: %08d, amt granted: %08d\n", i,
+                      p->procdata->res_req[i].amt_wanted, p->procinfo->res_grant[i]);
+}
+
+void print_all_resources(void)
+{
+       /* Hash helper */
+       void __print_resources(void *item)
+       {
+               print_resources((struct proc*)item);
+       }
+       spin_lock(&pid_hash_lock);
+       hash_for_each(pid_hash, __print_resources);
+       spin_unlock(&pid_hash_lock);
+}