_S -> _M and schedule()/core_request() work
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 22 Feb 2012 02:24:45 +0000 (18:24 -0800)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 27 Feb 2012 21:27:58 +0000 (13:27 -0800)
A lot is in transition.  This step makes the transition code to _M mode
the province of process.c, instead of resource.c.  I kept around the old
_M->_S code, but don't seriously use it or plan on it ever being used.

kern/include/process.h
kern/include/resource.h
kern/include/schedule.h
kern/src/process.c
kern/src/resource.c
kern/src/schedule.c

index 9067de4..6cef6ac 100644 (file)
@@ -75,6 +75,8 @@ void proc_decref(struct proc *p);
 void proc_run(struct proc *SAFE p);
 void proc_restartcore(void);
 void proc_destroy(struct proc *SAFE p);
+void __proc_switch_to_m(struct proc *p);
+void __proc_switch_to_s(struct proc *p); /* don't call this */
 void __proc_yield_s(struct proc *p, struct trapframe *tf);
 void proc_yield(struct proc *SAFE p, bool being_nice);
 void proc_notify(struct proc *p, uint32_t vcoreid);
index b94b69c..becc4c1 100644 (file)
@@ -15,7 +15,7 @@
 #include <arch/trap.h>
 #include <process.h>
 
-ssize_t core_request(struct proc *p);
+bool core_request(struct proc *p);
 error_t resource_req(struct proc *p, int type, size_t amt_wanted,
                      size_t amt_wanted_min, uint32_t flags);
 
index 1e36cc0..8e226e6 100644 (file)
@@ -29,6 +29,9 @@ void deschedule_proc(struct proc *p);
 /* Pick and run a process.  Note that this can return. */
 void schedule(void);
 
+/* Take a look at proc's resource (temp interface) */
+void poke_ksched(struct proc *p, int res_type);
+
 /* Gets called when a pcore becomes idle (like in proc yield) */
 void put_idle_core(uint32_t coreid);
 
index b480e4a..30cd213 100644 (file)
@@ -607,8 +607,11 @@ void proc_destroy(struct proc *p)
                        __proc_take_allcores_dumb(p, FALSE);
                        // fallthrough
                case PROC_RUNNABLE_S:
-                       // Think about other lists, like WAITING, or better ways to do this
-                       deschedule_proc(p);
+                       /* might need to pull from lists, though i'm currently a fan of the
+                        * model where external refs notice DYING (if it matters to them)
+                        * and decref when they are done.  the ksched will notice the proc
+                        * is dying and handle it accordingly (which delay the reaping til
+                        * the next call to schedule()) */
                        break;
                case PROC_RUNNING_S:
                        #if 0
@@ -659,6 +662,88 @@ void proc_destroy(struct proc *p)
        return;
 }
 
+/* Turns *p into an MCP.  Needs to be called from a local syscall of a RUNNING_S
+ * process.  Currently, this ignores whether or not you are an _M already.  You
+ * should hold the lock before calling. */
+void __proc_switch_to_m(struct proc *p)
+{
+       int8_t state = 0;
+       switch (p->state) {
+               case (PROC_RUNNING_S):
+                       /* issue with if we're async or not (need to preempt it)
+                        * either of these should trip it. TODO: (ACR) async core req
+                        * TODO: relies on vcore0 being the caller (VC#) */
+                       if ((current != p) || (get_pcoreid(p, 0) != core_id()))
+                               panic("We don't handle async RUNNING_S core requests yet.");
+                       /* save the tf so userspace can restart it.  Like in __notify,
+                        * this assumes a user tf is the same as a kernel tf.  We save
+                        * it in the preempt slot so that we can also save the silly
+                        * state. */
+                       struct preempt_data *vcpd = &p->procdata->vcore_preempt_data[0];
+                       disable_irqsave(&state);        /* protect cur_tf */
+                       /* Note this won't play well with concurrent proc kmsgs, but
+                        * since we're _S and locked, we shouldn't have any. */
+                       assert(current_tf);
+                       /* Copy uthread0's context to the notif slot */
+                       vcpd->notif_tf = *current_tf;
+                       clear_owning_proc(core_id());   /* so we don't restart */
+                       save_fp_state(&vcpd->preempt_anc);
+                       enable_irqsave(&state);
+                       /* Userspace needs to not fuck with notif_disabled before
+                        * transitioning to _M. */
+                       if (vcpd->notif_disabled) {
+                               printk("[kernel] user bug: notifs disabled for vcore 0\n");
+                               vcpd->notif_disabled = FALSE;
+                       }
+                       /* 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). */
+                       /* this process no longer runs on its old location (which is
+                        * this core, for now, since we don't handle async calls) */
+                       __seq_start_write(&p->procinfo->coremap_seqctr);
+                       // TODO: (VC#) might need to adjust num_vcores
+                       // TODO: (ACR) will need to unmap remotely (receive-side)
+                       __unmap_vcore(p, 0);    /* VC# keep in sync with proc_run _S */
+                       __seq_end_write(&p->procinfo->coremap_seqctr);
+                       /* change to runnable_m (it's TF is already saved) */
+                       __proc_set_state(p, PROC_RUNNABLE_M);
+                       p->procinfo->is_mcp = TRUE;
+                       break;
+               case (PROC_RUNNABLE_S):
+                       /* Issues: being on the runnable_list, proc_set_state not liking
+                        * it, and not clearly thinking through how this would happen.
+                        * Perhaps an async call that gets serviced after you're
+                        * descheduled? */
+                       panic("Not supporting RUNNABLE_S -> RUNNABLE_M yet.\n");
+                       break;
+               case (PROC_DYING):
+                       warn("Dying, core request coming from %d\n", core_id());
+               default:
+                       break;
+       }
+}
+
+/* Old code to turn a RUNNING_M to a RUNNING_S, with the calling context
+ * becoming the new 'thread0'.  Don't use this. */
+void __proc_switch_to_s(struct proc *p)
+{
+       int8_t state = 0;
+       printk("[kernel] trying to transition _M -> _S (deprecated)!\n");
+       assert(p->state == PROC_RUNNING_M); // TODO: (ACR) async core req
+       /* save the context, to be restarted in _S mode */
+       disable_irqsave(&state);        /* protect cur_tf */
+       assert(current_tf);
+       p->env_tf = *current_tf;
+       clear_owning_proc(core_id());   /* so we don't restart */
+       enable_irqsave(&state);
+       env_push_ancillary_state(p); // TODO: (HSS)
+       /* sending death, since it's not our job to save contexts or anything in
+        * this case.  also, if this returns true, we will not return down
+        * below, and need to eat the reference to p */
+       __proc_take_allcores_dumb(p, FALSE);
+       __proc_set_state(p, PROC_RUNNABLE_S);
+}
+
 /* Helper function.  Is the given pcore a mapped vcore?  No locking involved, be
  * careful. */
 static bool is_mapped_vcore(struct proc *p, uint32_t pcoreid)
@@ -1153,9 +1238,7 @@ static void __proc_give_cores_running(struct proc *p, uint32_t *pc_arr,
  *
  * The reason I didn't bring the _S cases from core_request over here is so we
  * can keep this family of calls dealing with only *_Ms, to avoiding caring if
- * this is called from another core, and to avoid the need_to_idle business.
- * The other way would be to have this function have the side effect of changing
- * state, and finding another way to do the need_to_idle.
+ * this is called from another core, and to avoid the _S -> _M transition.
  *
  * WARNING: You must hold the proc_lock before calling this! */
 void __proc_give_cores(struct proc *p, uint32_t *pc_arr, uint32_t num)
@@ -1168,8 +1251,10 @@ void __proc_give_cores(struct proc *p, uint32_t *pc_arr, uint32_t num)
                        panic("Don't give cores to a process in a *_S state!\n");
                        break;
                case (PROC_DYING):
-                       panic("Attempted to give cores to a DYING process.\n");
-                       break;
+                       /* We're dying, give the cores back to the ksched and return */
+                       for (int i = 0; i < num; i++)
+                               put_idle_core(pc_arr[i]);
+                       return;
                case (PROC_RUNNABLE_M):
                        __proc_give_cores_runnable(p, pc_arr, num);
                        break;
index ef3f1c0..c6d33c6 100644 (file)
  * transition a process from _M to _S (amt_wanted == 0).
  *
  * This needs a consumable/edible reference of p, in case it doesn't return.
- *
- * TODO: this is a giant function.  need to split it up a bit, probably move the
- * guts to process.c and have functions to call for the brains.
  */
-ssize_t core_request(struct proc *p)
+bool core_request(struct proc *p)
 {
-       size_t num_granted;
-       ssize_t amt_new;
-       uint32_t corelist[MAX_NUM_CPUS];
-       bool need_to_idle = FALSE;
-       int8_t state = 0;
+       uint32_t num_granted, amt_new, amt_wanted, amt_granted;
+       uint32_t corelist[MAX_NUM_CPUS]; /* TODO UGH, this could be huge! */
 
+       /* Currently, this is all locked, and there's a variety of races involved,
+        * esp with moving amt_wanted to procdata (TODO).  Will probably want to
+        * copy-in amt_wanted too. */
        spin_lock(&p->proc_lock);
-       if (p->state == PROC_DYING) {
-               spin_unlock(&p->proc_lock);
-               return -EFAIL;
-       }
-       /* check to see if this is a full deallocation.  for cores, it's a
-        * transition from _M to _S.  Will be issues with handling this async. */
-       if (!p->resources[RES_CORES].amt_wanted) {
-               printk("[kernel] trying to transition _M -> _S (deprecated)!\n");
-               assert(p->state == PROC_RUNNING_M); // TODO: (ACR) async core req
-               /* save the context, to be restarted in _S mode */
-               disable_irqsave(&state);        /* protect cur_tf */
-               assert(current_tf);
-               p->env_tf = *current_tf;
-               clear_owning_proc(core_id());   /* so we don't restart */
-               enable_irqsave(&state);
-               env_push_ancillary_state(p); // TODO: (HSS)
-               /* sending death, since it's not our job to save contexts or anything in
-                * this case.  also, if this returns true, we will not return down
-                * below, and need to eat the reference to p */
-               __proc_take_allcores_dumb(p, FALSE);
-               __proc_set_state(p, PROC_RUNNABLE_S);
-               schedule_proc(p);
-               spin_unlock(&p->proc_lock);
-               return 0;
+       amt_wanted = p->resources[RES_CORES].amt_wanted;
+       amt_granted = p->resources[RES_CORES].amt_granted;      /* aka, num_vcores */
+
+       /* Help them out - if they ask for something impossible, give them 1 so they
+        * can make some progress. */
+       if (amt_wanted > p->procinfo->max_vcores) {
+               p->resources[RES_CORES].amt_wanted = 1;
        }
-       /* 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;
+       /* TODO: sort how this works with WAITING. */
+       if (!amt_wanted) {
+               p->resources[RES_CORES].amt_wanted = 1;
        }
-       /* otherwise, see how many new cores are wanted */
-       amt_new = p->resources[RES_CORES].amt_wanted -
-                 p->resources[RES_CORES].amt_granted;
-       if (amt_new < 0) {
-               p->resources[RES_CORES].amt_wanted = p->resources[RES_CORES].amt_granted;
-               spin_unlock(&p->proc_lock);
-               return -EINVAL;
-       } else if (amt_new == 0) {
-               spin_unlock(&p->proc_lock);
-               return 0;
+       /* 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) {
+               if (amt_granted) {
+                       spin_unlock(&p->proc_lock);
+                       return TRUE;
+               } else {
+                       spin_unlock(&p->proc_lock);
+                       return FALSE;
+               }
        }
-       // else, we try to handle the request
+       /* 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 = proc_wants_cores(p, corelist, amt_new);
 
-       // Now, actually give them out
+       /* Now, actually give them out */
        if (num_granted) {
-               switch (p->state) {
-                       case (PROC_RUNNING_S):
-                               // issue with if we're async or not (need to preempt it)
-                               // either of these should trip it. TODO: (ACR) async core req
-                               // TODO: relies on vcore0 being the caller (VC#)
-                               // TODO: do this in process.c and use this line:
-                               //if ((current != p) || (get_pcoreid(p, 0) != core_id()))
-                               if ((current != p) || (p->procinfo->vcoremap[0].pcoreid != core_id()))
-                                       panic("We don't handle async RUNNING_S core requests yet.");
-                               /* save the tf so userspace can restart it.  Like in __notify,
-                                * this assumes a user tf is the same as a kernel tf.  We save
-                                * it in the preempt slot so that we can also save the silly
-                                * state. */
-                               struct preempt_data *vcpd = &p->procdata->vcore_preempt_data[0];
-                               disable_irqsave(&state);        /* protect cur_tf */
-                               /* Note this won't play well with concurrent proc kmsgs, but
-                                * since we're _S and locked, we shouldn't have any. */
-                               assert(current_tf);
-                               /* Copy uthread0's context to the notif slot */
-                               vcpd->notif_tf = *current_tf;
-                               clear_owning_proc(core_id());   /* so we don't restart */
-                               save_fp_state(&vcpd->preempt_anc);
-                               enable_irqsave(&state);
-                               /* Userspace needs to not fuck with notif_disabled before
-                                * transitioning to _M. */
-                               if (vcpd->notif_disabled) {
-                                       printk("[kernel] user bug: notifs disabled for vcore 0\n");
-                                       vcpd->notif_disabled = FALSE;
-                               }
-                               /* 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). */
-                               /* this process no longer runs on its old location (which is
-                                * this core, for now, since we don't handle async calls) */
-                               __seq_start_write(&p->procinfo->coremap_seqctr);
-                               // TODO: (VC#) might need to adjust num_vcores
-                               // TODO: (ACR) will need to unmap remotely (receive-side)
-                               __unmap_vcore(p, 0);    /* VC# keep in sync with proc_run _S */
-                               __seq_end_write(&p->procinfo->coremap_seqctr);
-                               // will need to give up this core / idle later (sync)
-                               need_to_idle = TRUE;
-                               // change to runnable_m (it's TF is already saved)
-                               __proc_set_state(p, PROC_RUNNABLE_M);
-                               p->procinfo->is_mcp = TRUE;
-                               break;
-                       case (PROC_RUNNABLE_S):
-                               /* Issues: being on the runnable_list, proc_set_state not liking
-                                * it, and not clearly thinking through how this would happen.
-                                * Perhaps an async call that gets serviced after you're
-                                * descheduled? */
-                               panic("Not supporting RUNNABLE_S -> RUNNABLE_M yet.\n");
-                               break;
-                       case (PROC_DYING):
-                               warn("Dying, core request coming from %d\n", core_id());
-                       default:
-                               break;
-               }
                /* give them the cores.  this will start up the extras if RUNNING_M. */
                __proc_give_cores(p, corelist, num_granted);
                spin_unlock(&p->proc_lock);
-               /* 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). */
-               if (p->state == PROC_RUNNABLE_M)
-                       proc_run(p);    /* I dislike this - caller should run it */
-       } else { // nothing granted, just return
-               spin_unlock(&p->proc_lock);
+               return TRUE;    /* proc can run (if it isn't already) */
        }
-       return num_granted;
+       spin_unlock(&p->proc_lock);
+       return FALSE;           /* Not giving them anything more */
 }
 
 error_t resource_req(struct proc *p, int type, size_t amt_wanted,
@@ -183,14 +106,17 @@ error_t resource_req(struct proc *p, int type, size_t amt_wanted,
 
        switch (type) {
                case RES_CORES:
-                       retval = core_request(p);
-                       // i don't like this retval hackery
-                       if (retval < 0) {
-                               set_errno(-retval);
-                               return -1;
+                       spin_lock(&p->proc_lock);
+                       if (p->state == PROC_RUNNING_S) {
+                               __proc_switch_to_m(p);  /* will later be a separate syscall */
+                               schedule_proc(p);
+                               spin_unlock(&p->proc_lock);
+                       } else {
+                               /* _M */
+                               spin_unlock(&p->proc_lock);
+                               poke_ksched(p, RES_CORES); /* will be a separate syscall */
                        }
-                       else
-                               return 0;
+                       return 0;
                        break;
                case RES_MEMORY:
                        // not clear if we should be in RUNNABLE_M or not
index b86df8b..eac4b03 100644 (file)
@@ -1,10 +1,8 @@
-/*
- * Copyright (c) 2009 The Regents of the University of California
+/* Copyright (c) 2009, 2012 The Regents of the University of California
  * Barret Rhoden <brho@cs.berkeley.edu>
  * See LICENSE for details.
  *
- * Scheduling and dispatching.
- */
+ * Scheduling and dispatching. */
 
 #ifdef __SHARC__
 #pragma nosharc
@@ -93,23 +91,11 @@ void schedule_proc(struct proc *p)
        return;
 }
 
-/* TODO: race here.  it's possible that p was already removed from the
- * list (by schedule()), while proc_destroy is trying to remove it from the
- * list.  schedule()'s proc_run() won't actually run it (since it's DYING), but
- * this code will probably fuck up.  Having TAILQ_REMOVE not hurt will help. */
-void deschedule_proc(struct proc *p)
-{
-       spin_lock_irqsave(&runnablelist_lock);
-       printd("Descheduling PID: %d\n", p->pid);
-       TAILQ_REMOVE(&proc_runnablelist, p, proc_link);
-       spin_unlock_irqsave(&runnablelist_lock);
-       /* down the refcnt, since its no longer stored */
-       proc_decref(p);
-       return;
-}
-
-/*
- * FIFO - just pop the head from the list and run it.
+/* 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).
+ *
  * 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).
  */
@@ -118,17 +104,40 @@ 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. */
+               if (p->state == PROC_DYING) {
+                       proc_decref(p);
+                       return;
+               }
+               /* 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) */
-                       if (core_request(p) <= 0)
-                               schedule_proc(p);       /* got none, put it back on the queue */
+                       /* 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). */
+                               proc_run(p); /* trying to run a RUNNABLE_M here */
                } else {
                        /* _S proc, just run it */
                        proc_run(p);
@@ -141,6 +150,24 @@ void schedule(void)
        return;
 }
 
+/* A process is asking the ksched to look at its resource desires.  The
+ * scheduler is free to ignore this, for its own reasons, so long as it
+ * eventually gets around to looking at resource desires. */
+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 */
+       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);
+                       break;
+               default:
+                       break;
+       }
+}
+
 /* Helper function to return a core to the idlemap.  It causes some more lock
  * acquisitions (like in a for loop), but it's a little easier.  Plus, one day
  * we might be able to do this without locks (for the putting). */