Killing of parallel processes
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 19 Aug 2009 02:35:14 +0000 (19:35 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 31 Aug 2009 21:07:02 +0000 (14:07 -0700)
proc_destroy() should be able to kill anything now, notably RUNNING_M
processes, via the DEATH IPI.

Also reworked smp_idle so that any core can call it.  Management cores
(core 0) will enter the manager(), while others will check their
workqueue.  Eventually want to merge these paths.

This is not heavily tested, so there are probably some issues.  Also
note the dirty hack with decref.

kern/arch/i386/apic.c
kern/arch/i386/process.c
kern/arch/i386/trapentry.S
kern/include/manager.h
kern/src/manager.c
kern/src/process.c
kern/src/smp.c
user/apps/roslib/mhello.c

index c5a465a..3e73726 100644 (file)
@@ -150,7 +150,7 @@ static int getpit()
     return ((high << 8) | low);
 }
 
     return ((high << 8) | low);
 }
 
-// forces cpu to relax for usec miliseconds
+// forces cpu to relax for usec miliseconds.  declared in kern/include/timing.h
 void udelay(uint64_t usec)
 {
        #if !defined(__BOCHS__)
 void udelay(uint64_t usec)
 {
        #if !defined(__BOCHS__)
index ff25784..73ad437 100644 (file)
@@ -1,11 +1,14 @@
 #include <arch/arch.h>
 #include <arch/trap.h>
 #include <process.h>
 #include <arch/arch.h>
 #include <arch/trap.h>
 #include <process.h>
+#include <pmap.h>
 #include <smp.h>
 #include <smp.h>
-#include <stdio.h>
+
 #include <string.h>
 #include <assert.h>
 #include <string.h>
 #include <assert.h>
+#include <stdio.h>
 
 
+/* Interrupt handler to start a process's context on this core. */
 void __startcore(trapframe_t *tf)
 {
        uint32_t coreid = core_id();
 void __startcore(trapframe_t *tf)
 {
        uint32_t coreid = core_id();
@@ -32,4 +35,21 @@ void __startcore(trapframe_t *tf)
        proc_startcore(p_to_run, tf_to_pop);
 }
 
        proc_startcore(p_to_run, tf_to_pop);
 }
 
-
+/* Interrupt handler to stop running whatever context is on this core and to
+ * idle.  Note this leaves no trace of what was running. */
+void __death(trapframe_t *tf)
+{
+       /* If we are currently running an address space on our core, we need a known
+        * good pgdir before releasing the old one.  This is currently the major
+        * practical implication of the kernel caring about a processes existence
+        * (the inc and decref).  This decref corresponds to the incref in
+        * proc_startcore (though it's not the only one). */
+       if (current) {
+               lcr3(boot_cr3);
+               proc_decref(current);
+               current = NULL;
+       } else {
+               warn("Sent a DEATH IPI to a core with no current process!");
+       }
+       smp_idle();             
+}
index 2d898fd..e020726 100644 (file)
@@ -141,8 +141,8 @@ IRQ_HANDLER(IRQ13, 45)
 IRQ_HANDLER(IRQ14, 46)
 IRQ_HANDLER(IRQ15, 47)
 /* 25 general purpose vectors, for use by the LAPIC.  Can expand later. */
 IRQ_HANDLER(IRQ14, 46)
 IRQ_HANDLER(IRQ15, 47)
 /* 25 general purpose vectors, for use by the LAPIC.  Can expand later. */
-IRQ_HANDLER_SPEC(IRQ198, I_DEATH, irq_handler)
-IRQ_HANDLER_SPEC(IRQ199, I_STARTCORE, __startcore)
+IRQ_HANDLER_SPEC(IRQ198, I_DEATH, __death) 
+IRQ_HANDLER_SPEC(IRQ199, I_STARTCORE, __startcore) 
 IRQ_HANDLER(IRQ200, 232)
 IRQ_HANDLER(IRQ201, 233)
 IRQ_HANDLER(IRQ202, 234)
 IRQ_HANDLER(IRQ200, 232)
 IRQ_HANDLER(IRQ201, 233)
 IRQ_HANDLER(IRQ202, 234)
index b333893..f8804e7 100644 (file)
  * The manager is the "asymmetric control unit", that runs on core 0 for now
  * and controls the actions of the whole system.
  */
  * The manager is the "asymmetric control unit", that runs on core 0 for now
  * and controls the actions of the whole system.
  */
-
 void manager(void);
 
 void manager(void);
 
+/* Returns if the calling core is a management core or not. */
+static inline bool management_core(void);
+
+static inline bool management_core(void)
+{
+       // currently returns true for only core 0
+       return !core_id();
+}
+
 #endif /* ROS_KERN_MANAGER_H */
 #endif /* ROS_KERN_MANAGER_H */
index f9c0e96..5617b49 100644 (file)
@@ -135,4 +135,3 @@ panic("This is okay");
        */
        return;
 }
        */
        return;
 }
-
index ba28b05..69ad08b 100644 (file)
@@ -119,8 +119,7 @@ void proc_run(struct proc *p)
                        // There should be no core cleanup to do (like decref).
                        assert(current != p);
                        // if we're a worker core, smp_idle, o/w return
                        // There should be no core cleanup to do (like decref).
                        assert(current != p);
                        // if we're a worker core, smp_idle, o/w return
-                       // TODO considering encapsulating this block (core_id too)
-                       if (core_id())
+                       if (!management_core())
                                smp_idle(); // this never returns
                        return;
                case (PROC_RUNNABLE_S):
                                smp_idle(); // this never returns
                        return;
                case (PROC_RUNNABLE_S):
@@ -149,16 +148,15 @@ void proc_run(struct proc *p)
                         * after the death IPI.  Otherwise, it would look like a new
                         * process.  So we hold the lock to make sure our IPI went out
                         * before a possible death IPI.
                         * after the death IPI.  Otherwise, it would look like a new
                         * process.  So we hold the lock to make sure our IPI went out
                         * before a possible death IPI.
-                        * Likewise, we disable interrupts, in case one of the IPIs was for
-                        * us, and reenable them after letting go of the lock.
+                        * Likewise, we need interrupts to be disabled, in case one of the
+                        * IPIs was for us, and reenable them after letting go of the lock.
+                        * This is done by spin_lock_irqsave, so be careful if you change
+                        * this.
                         * Note there is no guarantee this core's interrupts were on, so it
                         * may not get the IPI for a while... */
                         * Note there is no guarantee this core's interrupts were on, so it
                         * may not get the IPI for a while... */
-                       int8_t state = 0;
-                       disable_irqsave(&state);
                        for (int i = 0; i < p->num_vcores; i++)
                                send_ipi(p->vcoremap[i], 0, I_STARTCORE);
                        spin_unlock_irqsave(&p->proc_lock);
                        for (int i = 0; i < p->num_vcores; i++)
                                send_ipi(p->vcoremap[i], 0, I_STARTCORE);
                        spin_unlock_irqsave(&p->proc_lock);
-                       enable_irqsave(&state);
                        break;
                default:
                        spin_unlock_irqsave(&p->proc_lock);
                        break;
                default:
                        spin_unlock_irqsave(&p->proc_lock);
@@ -257,6 +255,8 @@ void proc_startcore(struct proc *p, trapframe_t *tf) {
  */
 void proc_destroy(struct proc *p)
 {
  */
 void proc_destroy(struct proc *p)
 {
+       // Note this code relies on this lock disabling interrupts, similar to
+       // proc_run.
        spin_lock_irqsave(&p->proc_lock);
        // Could save the state and do this outside the lock
        switch (p->state) {
        spin_lock_irqsave(&p->proc_lock);
        // Could save the state and do this outside the lock
        switch (p->state) {
@@ -270,47 +270,35 @@ void proc_destroy(struct proc *p)
                        // Think about other lists, or better ways to do this
        }
        proc_set_state(p, PROC_DYING);
                        // Think about other lists, or better ways to do this
        }
        proc_set_state(p, PROC_DYING);
-       // BIG TODO: check the coremap to find out who needs to die
-       // send the death IPI to everyone else involved
-       spin_unlock_irqsave(&p->proc_lock);
-
-       proc_decref(p); // this decref is for the process in general
-       atomic_dec(&num_envs);
-
-       /*
-        * If we are currently running this address space on our core, we need a
-        * known good pgdir before releasing the old one.  This is currently the
-        * major practical implication of the kernel caring about a processes
-        * existence (the inc and decref).  This decref corresponds to the incref in
-        * proc_startcore (though it's not the only one).
-        */
-       // TODO - probably make this a function, which the death IPI calls
-       if (current == p) {
-               lcr3(boot_cr3);
-               proc_decref(p); // this decref is for the cr3
-               current = NULL;
-       } else {
-               return;
+       /* Send the DEATH IPI to every core running this process */
+       for (int i = 0; i < p->num_vcores; i++) {
+               send_ipi(p->vcoremap[i], 0, I_DEATH);
        }
        }
+       /* TODO: will need to update the pcoremap that's currently in schedule */
 
 
-       // for old envs that die on user cores.  since env run never returns, cores
-       // never get back to their old hlt/relaxed/spin state, so we need to force
-       // them back to an idle function.
+       atomic_dec(&num_envs);
+       /* TODO: (REF) dirty hack.  decref currently uses a lock, but needs to use
+        * CAS instead (another lock would be slightly less ghetto).  but we need to
+        * decref before releasing the lock, since that could enable interrupts,
+        * which would have us receive the DEATH IPI if this was called locally by
+        * the target process. */ 
+       //proc_decref(p); // this decref is for the process in general
+       p->env_refcnt--;
+       size_t refcnt = p->env_refcnt; // need to copy this in so it's not reloaded
+       
+       /* After unlocking, we can receive a DEATH IPI and clean up */
+       spin_unlock_irqsave(&p->proc_lock);
 
 
-       if (core_id()) {
-               smp_idle();
-               panic("should never see me");
-       }
-       // else we're core 0 and can do the usual
+       // coupled with the refcnt-- above, from decref.  if this happened,
+       // proc_destroy was called "remotely", and with no one else refcnting
+       if (!refcnt)
+               env_free(p);
 
 
-       /* Instead of picking a new environment to run, or defaulting to the monitor
-        * like before, for now we'll hop into the manager() function, which
-        * dispatches jobs.  Note that for now we start the manager from the top,
-        * and not from where we left off the last time we called manager.  That
-        * would require us to save some context (and a stack to work on) here.
-        */
-       manager();
-       assert(0); // never get here
+       /* If we were running the process, we should have received an IPI by now.
+        * If not, odds are interrupts are disabled, which shouldn't happen while
+        * servicing syscalls. */
+       assert(current != p);
+       return;
 }
 
 /*
 }
 
 /*
@@ -337,6 +325,8 @@ void proc_destroy(struct proc *p)
  *
  * Also, no one should ever update the refcnt outside of these functions.
  * Eventually, we'll have Ivy support for this. (TODO)
  *
  * Also, no one should ever update the refcnt outside of these functions.
  * Eventually, we'll have Ivy support for this. (TODO)
+ *
+ * TODO: (REF) change to use CAS.
  */
 error_t proc_incref(struct proc *p)
 {
  */
 error_t proc_incref(struct proc *p)
 {
@@ -356,13 +346,17 @@ error_t proc_incref(struct proc *p)
  * "Last one out" actually finalizes the death of the process.  This is tightly
  * coupled with the previous function (incref)
  * Be sure to load a different cr3 before calling this!
  * "Last one out" actually finalizes the death of the process.  This is tightly
  * coupled with the previous function (incref)
  * Be sure to load a different cr3 before calling this!
+ *
+ * TODO: (REF) change to use CAS.  Note that when we do so, we may be holding
+ * the process lock when calling env_free().
  */
 void proc_decref(struct proc *p)
 {
        spin_lock_irqsave(&p->proc_lock);
        p->env_refcnt--;
  */
 void proc_decref(struct proc *p)
 {
        spin_lock_irqsave(&p->proc_lock);
        p->env_refcnt--;
+       size_t refcnt = p->env_refcnt; // need to copy this in so it's not reloaded
        spin_unlock_irqsave(&p->proc_lock);
        // if we hit 0, no one else will increment and we can check outside the lock
        spin_unlock_irqsave(&p->proc_lock);
        // if we hit 0, no one else will increment and we can check outside the lock
-       if (p->env_refcnt == 0)
+       if (!refcnt)
                env_free(p);
 }
                env_free(p);
 }
index aa50ac5..dd3fca1 100644 (file)
@@ -17,6 +17,7 @@
 #include <assert.h>
 #include <pmap.h>
 #include <process.h>
 #include <assert.h>
 #include <pmap.h>
 #include <process.h>
+#include <manager.h>
 #include <trap.h>
 
 struct per_cpu_info per_cpu_info[MAX_NUM_CPUS];
 #include <trap.h>
 
 struct per_cpu_info per_cpu_info[MAX_NUM_CPUS];
@@ -24,17 +25,30 @@ struct per_cpu_info per_cpu_info[MAX_NUM_CPUS];
 // tracks number of global waits on smp_calls, must be <= NUM_HANDLER_WRAPPERS
 atomic_t outstanding_calls = 0;
 
 // tracks number of global waits on smp_calls, must be <= NUM_HANDLER_WRAPPERS
 atomic_t outstanding_calls = 0;
 
-/* All non-zero cores call this at the end of their boot process.  They halt,
- * and wake up when interrupted, do any work on their work queue, then halt
- * when there is nothing to do.  
- * TODO: think about resetting the stack pointer at the beginning.
+/* All cores end up calling this whenever there is nothing left to do.  Non-zero
+ * cores call it when they are done booting.  Other cases include after getting
+ * a DEATH IPI.
+ * - Management cores (core 0 for now) call manager, which should never return.
+ * - Worker cores halt and wake up when interrupted, do any work on their work
+ *   queue, then halt again.
+ *
+ * TODO: think about resetting the stack pointer at the beginning for worker
+ * cores.
+ * TODO: think about unifying the manager into a workqueue function, so we don't
+ * need to check mgmt_core in here.  it gets a little ugly, since there are
+ * other places where we check for mgmt and might not smp_idle / call manager.
  */
 void smp_idle(void)
 {
  */
 void smp_idle(void)
 {
-       enable_irq();
-       while (1) {
-               process_workqueue();
-               // consider races with work added after we started leaving the last func
-               cpu_halt();
+       if (!management_core()) {
+               enable_irq();
+               while (1) {
+                       process_workqueue();
+                       // consider races with work added after we started leaving the last func
+                       cpu_halt();
+               }
+       } else {
+               manager();
        }
        }
+       assert(0);
 }
 }
index 9aa3936..3fec025 100644 (file)
@@ -1,9 +1,27 @@
 #include <lib.h>
 #include <stdio.h>
 
 #include <lib.h>
 #include <stdio.h>
 
+// ghetto udelay, put in a lib somewhere and export the tsc freq
+#include <arch/arch.h>
+void udelay(uint64_t usec, uint64_t tsc_freq)
+{
+       uint64_t start, end, now;
+
+       start = read_tsc();
+    end = start + (tsc_freq * usec) / 1000000;
+       if (end == 0) cprintf("This is terribly wrong \n");
+       do {
+        cpu_relax();
+        now = read_tsc();
+       } while (now < end || (now > start && end < start));
+       return;
+}
+
 int main(int argc, char** argv)
 {
        cprintf("Multi-Goodbye, world, from PID: %d!\n", sys_getpid());
 int main(int argc, char** argv)
 {
        cprintf("Multi-Goodbye, world, from PID: %d!\n", sys_getpid());
-       while(1);
+       //while(1);
+       udelay(5000000, 1995014570); // KVM's freq.  Whatever.
+
        return 0;
 }
        return 0;
 }