CPU state tracking
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 29 Sep 2014 00:31:34 +0000 (17:31 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 29 Sep 2014 01:42:54 +0000 (18:42 -0700)
Whenever the CPU transitions between states, we account for the amount
of time spent in the previous state in pcpui.  This will be useful for
accounting.  The downside is that we have a few more rdtsc calls, each
of which costs about 30 TSC ticks/cycles (Core i7-950, I think).

For example, given a process in userspace (state == USER), we take an
interrupt (change to IRQ), then after hard IRQ context, we go into the
kernel (change to KERN), and then eventually pop back to userspace
(change to USER).  Three rdtsc calls.  For a syscall, the transitions
are just USER->KERN, then KERN->USER.

In the future, we can add helpers to disable the rdtsc part, if it turns
into a pain.  I'll look into it more when I speed up syscalls.

kern/arch/x86/trap.c
kern/include/smp.h
kern/src/process.c
kern/src/smp.c

index 8446b71..fa2d045 100644 (file)
@@ -446,11 +446,13 @@ void trap(struct hw_trapframe *hw_tf)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        /* Copy out the TF for now */
-       if (!in_kernel(hw_tf))
+       if (!in_kernel(hw_tf)) {
                set_current_ctx_hw(pcpui, hw_tf);
-       else
+               /* ignoring state for nested kernel traps.  should be rare. */
+               __set_cpu_state(pcpui, CPU_STATE_KERNEL);
+       } else {
                inc_ktrap_depth(pcpui);
-
+       }
        printd("Incoming TRAP %d on core %d, TF at %p\n", hw_tf->tf_trapno,
               core_id(), hw_tf);
        if ((hw_tf->tf_cs & ~3) != GD_UT && (hw_tf->tf_cs & ~3) != GD_KT) {
@@ -491,6 +493,8 @@ void handle_irq(struct hw_trapframe *hw_tf)
        /* Copy out the TF for now */
        if (!in_kernel(hw_tf))
                set_current_ctx_hw(pcpui, hw_tf);
+       if (!in_irq_ctx(pcpui))
+               __set_cpu_state(pcpui, CPU_STATE_IRQ);
        inc_irq_depth(pcpui);
        /* Coupled with cpu_halt() and smp_idle() */
        abort_halt(hw_tf);
@@ -531,6 +535,8 @@ void handle_irq(struct hw_trapframe *hw_tf)
        /* Fall-through */
 out_no_eoi:
        dec_irq_depth(pcpui);
+       if (!in_irq_ctx(pcpui))
+               __set_cpu_state(pcpui, CPU_STATE_KERNEL);
        /* Return to the current process, which should be runnable.  If we're the
         * kernel, we should just return naturally.  Note that current and tf need
         * to still be okay (might not be after blocking) */
@@ -625,6 +631,7 @@ void sysenter_callwrapper(struct syscall *sysc, unsigned long count,
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        set_current_ctx_sw(pcpui, sw_tf);
+       __set_cpu_state(pcpui, CPU_STATE_KERNEL);
        /* Once we've set_current_ctx, we can enable interrupts.  This used to be
         * mandatory (we had immediate KMSGs that would muck with cur_ctx).  Now it
         * should only help for sanity/debugging. */
index c5422be..b7e0797 100644 (file)
 typedef sharC_env_t;
 #endif
 
+#define CPU_STATE_IRQ                  0
+#define CPU_STATE_KERNEL               1
+#define CPU_STATE_USER                 2
+#define CPU_STATE_IDLE                 3
+#define NR_CPU_STATES                  4
+
+static char *cpu_state_names[NR_CPU_STATES] =
+{
+       "irq",
+       "kern",
+       "user",
+       "idle",
+};
+
 struct per_cpu_info {
 #ifdef CONFIG_X86_64
        uintptr_t stacktop;                     /* must be first */
@@ -55,7 +69,9 @@ struct per_cpu_info {
        struct timer_chain tchain;      /* for the per-core alarm */
        unsigned int lock_depth;
        struct trace_ring traces;
-
+       int cpu_state;
+       uint64_t last_tick_cnt;
+       uint64_t state_ticks[NR_CPU_STATES];
 #ifdef __SHARC__
        // held spin-locks. this will have to go elsewhere if multiple kernel
        // threads can share a CPU.
@@ -96,6 +112,9 @@ void smp_idle(void) __attribute__((noreturn));
 void smp_percpu_init(void); // this must be called by each core individually
 void __arch_pcpu_init(uint32_t coreid);        /* each arch has one of these */
 
+void __set_cpu_state(struct per_cpu_info *pcpui, int state);
+void reset_cpu_state_ticks(int coreid);
+
 /* SMP utility functions */
 int smp_call_function_self(isr_t handler, void *data,
                            handler_wrapper_t **wait_wrapper);
index 01f57cd..ee04506 100644 (file)
@@ -749,6 +749,7 @@ void __proc_startcore(struct proc *p, struct user_context *ctx)
        __set_proc_current(p);
        /* Clear the current_ctx, since it is no longer used */
        current_ctx = 0;        /* TODO: might not need this... */
+       __set_cpu_state(pcpui, CPU_STATE_USER);
        proc_pop_ctx(ctx);
 }
 
index afd7e9a..5c48a67 100644 (file)
@@ -69,6 +69,7 @@ static void __attribute__((noinline, noreturn)) __smp_idle(void)
                 * Important to do this, since we could have a RKM come in via an
                 * interrupt right while PRKM is returning, and we wouldn't catch
                 * it. */
+               __set_cpu_state(pcpui, CPU_STATE_IDLE);
                cpu_halt();
                /* interrupts are back on now (given our current semantics) */
        }
@@ -117,10 +118,51 @@ void smp_percpu_init(void)
        assert(trace_buf);
        trace_ring_init(&pcpui->traces, trace_buf, PGSIZE,
                        sizeof(struct pcpu_trace_event));
+       for (int i = 0; i < NR_CPU_STATES; i++)
+               pcpui->state_ticks[i] = 0;
+       pcpui->last_tick_cnt = read_tsc();
+       /* Core 0 is in the KERNEL state, called from smp_boot.  The other cores are
+        * too, at least on x86, where we were called from asm (woken by POKE). */
+       pcpui->cpu_state = CPU_STATE_KERNEL;
        /* Enable full lock debugging, after all pcpui work is done */
        pcpui->__lock_checking_enabled = 1;
 }
 
+/* it's actually okay to set the state to the existing state.  originally, it
+ * was a bug in the state tracking, but it is possible, at least on x86, to have
+ * a halted core (state IDLE) get woken up by an IRQ that does not trigger the
+ * IRQ handling state.  for example, there is the I_POKE_CORE ipi.  smp_idle
+ * will just sleep again, and reset the state from IDLE to IDLE. */
+void __set_cpu_state(struct per_cpu_info *pcpui, int state)
+{
+       uint64_t now_ticks;
+       assert(!irq_is_enabled());
+       /* TODO: could put in an option to enable/disable state tracking. */
+       now_ticks = read_tsc();
+       pcpui->state_ticks[pcpui->cpu_state] += now_ticks - pcpui->last_tick_cnt;
+       /* TODO: if the state was user, we could account for the vcore's time,
+        * similar to the total_ticks in struct vcore.  the difference is that the
+        * total_ticks tracks the vcore's virtual time, while this tracks user time.
+        * something like vcore->user_ticks. */
+       pcpui->cpu_state = state;
+       pcpui->last_tick_cnt = now_ticks;
+}
+
+void reset_cpu_state_ticks(int coreid)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[coreid];
+       uint64_t now_ticks;
+       if (coreid >= num_cpus)
+               return;
+       /* need to update last_tick_cnt, so the current value doesn't get added in
+        * next time we update */
+       now_ticks = read_tsc();
+       for (int i = 0; i < NR_CPU_STATES; i++) {
+               pcpui->state_ticks[i] = 0;
+               pcpui->last_tick_cnt = now_ticks;
+       }
+}
+
 /* PCPUI Trace Rings: */
 
 static void pcpui_trace_kmsg_handler(void *event, void *data)