kvm: Add support for thread-specific instruction events
authorAndreas Sandberg <andreas@sandberg.pp.se>
Mon, 30 Sep 2013 07:53:52 +0000 (09:53 +0200)
committerAndreas Sandberg <andreas@sandberg.pp.se>
Mon, 30 Sep 2013 07:53:52 +0000 (09:53 +0200)
Instruction events are currently ignored when executing in KVM. This
changeset adds support for triggering KVM exits based on instruction
counts using hardware performance counters. Depending on the
underlying performance counter implementation, there might be some
inaccuracies due to instructions being counted in the host kernel when
entering/exiting KVM.

Due to limitations/bugs in Linux's performance counter interface, we
can't reliably change the period of an overflow counter. We work
around this issue by detaching and reattaching the counter if we need
to reconfigure it.

src/cpu/kvm/base.cc
src/cpu/kvm/base.hh

index 594c5b7aea21c0bd2ad238a2421137151aab6aad..5e3ec279ac7e91bbbaad006c02ca77cf06ebc8cf 100644 (file)
 
 volatile bool timerOverflowed = false;
 
-static void
-onTimerOverflow(int signo, siginfo_t *si, void *data)
-{
-    timerOverflowed = true;
-}
-
 BaseKvmCPU::BaseKvmCPU(BaseKvmCPUParams *params)
     : BaseCPU(params),
       vm(*params->kvmVM),
@@ -83,6 +77,7 @@ BaseKvmCPU::BaseKvmCPU(BaseKvmCPUParams *params)
       _kvmRun(NULL), mmioRing(NULL),
       pageSize(sysconf(_SC_PAGE_SIZE)),
       tickEvent(*this),
+      activeInstPeriod(0),
       perfControlledByTimer(params->usePerfOverflow),
       hostFreq(params->hostFreq),
       hostFactor(params->hostFactor),
@@ -516,6 +511,10 @@ BaseKvmCPU::tick()
           // We might need to update the KVM state.
           syncKvmState();
 
+          // Setup any pending instruction count breakpoints using
+          // PerfEvent.
+          setupInstStop();
+
           DPRINTF(KvmRun, "Entering KVM...\n");
           if (drainManager) {
               // Force an immediate exit from KVM after completing
@@ -637,6 +636,7 @@ BaseKvmCPU::kvmRun(Tick ticks)
         // sure we don't deliver it immediately next time we try to
         // enter into KVM.
         discardPendingSignal(KVM_TIMER_SIGNAL);
+        discardPendingSignal(KVM_INST_SIGNAL);
 
         const uint64_t hostCyclesExecuted(getHostCycles() - baseCycles);
         const uint64_t simCyclesExecuted(hostCyclesExecuted * hostFactor);
@@ -1035,6 +1035,26 @@ BaseKvmCPU::flushCoalescedMMIO()
     return ticks;
 }
 
+/**
+ * Cycle timer overflow when running in KVM. Forces the KVM syscall to
+ * exit with EINTR and allows us to run the event queue.
+ */
+static void
+onTimerOverflow(int signo, siginfo_t *si, void *data)
+{
+    timerOverflowed = true;
+}
+
+/**
+ * Instruction counter overflow when running in KVM. Forces the KVM
+ * syscall to exit with EINTR and allows us to handle instruction
+ * count events.
+ */
+static void
+onInstEvent(int signo, siginfo_t *si, void *data)
+{
+}
+
 void
 BaseKvmCPU::setupSignalHandler()
 {
@@ -1044,7 +1064,13 @@ BaseKvmCPU::setupSignalHandler()
     sa.sa_sigaction = onTimerOverflow;
     sa.sa_flags = SA_SIGINFO | SA_RESTART;
     if (sigaction(KVM_TIMER_SIGNAL, &sa, NULL) == -1)
-        panic("KVM: Failed to setup vCPU signal handler\n");
+        panic("KVM: Failed to setup vCPU timer signal handler\n");
+
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_sigaction = onInstEvent;
+    sa.sa_flags = SA_SIGINFO | SA_RESTART;
+    if (sigaction(KVM_INST_SIGNAL, &sa, NULL) == -1)
+        panic("KVM: Failed to setup vCPU instruction signal handler\n");
 
     sigset_t sigset;
     if (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1)
@@ -1056,11 +1082,12 @@ BaseKvmCPU::setupSignalHandler()
     // requests. See kvmRun().
     setSignalMask(&sigset);
 
-    // Mask the KVM_TIMER_SIGNAL so it isn't delivered unless we're
+    // Mask our control signals so they aren't delivered unless we're
     // actually executing inside KVM.
     sigaddset(&sigset, KVM_TIMER_SIGNAL);
+    sigaddset(&sigset, KVM_INST_SIGNAL);
     if (sigprocmask(SIG_SETMASK, &sigset, NULL) == -1)
-        panic("KVM: Failed mask the KVM timer signal\n");
+        panic("KVM: Failed mask the KVM control signals\n");
 }
 
 bool
@@ -1113,12 +1140,7 @@ BaseKvmCPU::setupCounters()
     hwCycles.attach(cfgCycles,
                     0); // TID (0 => currentThread)
 
-    DPRINTF(Kvm, "Attaching instruction counter...\n");
-    PerfKvmCounterConfig cfgInstructions(PERF_TYPE_HARDWARE,
-                                      PERF_COUNT_HW_INSTRUCTIONS);
-    hwInstructions.attach(cfgInstructions,
-                          0, // TID (0 => currentThread)
-                          hwCycles);
+    setupInstCounter();
 }
 
 bool
@@ -1153,3 +1175,49 @@ BaseKvmCPU::ioctlRun()
                   errno);
     }
 }
+
+void
+BaseKvmCPU::setupInstStop()
+{
+
+    if (comInstEventQueue[0]->empty()) {
+        setupInstCounter(0);
+    } else {
+        const uint64_t next(comInstEventQueue[0]->nextTick());
+
+        assert(next > ctrInsts);
+        setupInstCounter(next - ctrInsts);
+    }
+}
+
+void
+BaseKvmCPU::setupInstCounter(uint64_t period)
+{
+    // No need to do anything if we aren't attaching for the first
+    // time or the period isn't changing.
+    if (period == activeInstPeriod && hwInstructions.attached())
+        return;
+
+    PerfKvmCounterConfig cfgInstructions(PERF_TYPE_HARDWARE,
+                                         PERF_COUNT_HW_INSTRUCTIONS);
+
+    if (period) {
+        // Setup a sampling counter if that has been requested.
+        cfgInstructions.wakeupEvents(1)
+            .samplePeriod(period);
+    }
+
+    // We need to detach and re-attach the counter to reliably change
+    // sampling settings. See PerfKvmCounter::period() for details.
+    if (hwInstructions.attached())
+        hwInstructions.detach();
+    assert(hwCycles.attached());
+    hwInstructions.attach(cfgInstructions,
+                          0, // TID (0 => currentThread)
+                          hwCycles);
+
+    if (period)
+        hwInstructions.enableSignals(KVM_INST_SIGNAL);
+
+    activeInstPeriod = period;
+}
index 3c299ce4e15b1df76f0fa26ccbfae81514d1ee28..103808365246c6a3f39e8b97081b12b5130b159c 100644 (file)
@@ -53,6 +53,9 @@
 /** Signal to use to trigger time-based exits from KVM */
 #define KVM_TIMER_SIGNAL SIGRTMIN
 
+/** Signal to use to trigger instruction-based exits from KVM */
+#define KVM_INST_SIGNAL (SIGRTMIN+1)
+
 // forward declarations
 class ThreadContext;
 struct BaseKvmCPUParams;
@@ -615,9 +618,6 @@ class BaseKvmCPU : public BaseCPU
      */
     bool discardPendingSignal(int signum) const;
 
-    /** Setup hardware performance counters */
-    void setupCounters();
-
     /** Try to drain the CPU if a drain is pending */
     bool tryDrain();
 
@@ -647,11 +647,59 @@ class BaseKvmCPU : public BaseCPU
 
     TickEvent tickEvent;
 
+    /**
+     * Setup an instruction break if there is one pending.
+     *
+     * Check if there are pending instruction breaks in the CPU's
+     * instruction event queue and schedule an instruction break using
+     * PerfEvent.
+     *
+     * @note This method doesn't currently handle the main system
+     * instruction event queue.
+     */
+    void setupInstStop();
+
     /** @{ */
-    /** Guest performance counters */
+    /** Setup hardware performance counters */
+    void setupCounters();
+
+    /**
+     * Setup the guest instruction counter.
+     *
+     * Setup the guest instruction counter and optionally request a
+     * signal every N instructions executed by the guest. This method
+     * will re-attach the counter if the counter has already been
+     * attached and its sampling settings have changed.
+     *
+     * @param period Signal period, set to 0 to disable signaling.
+     */
+    void setupInstCounter(uint64_t period = 0);
+
+    /** Currently active instruction count breakpoint */
+    uint64_t activeInstPeriod;
+
+    /**
+     * Guest cycle counter.
+     *
+     * This is the group leader of all performance counters measuring
+     * the guest system. It can be used in conjunction with the
+     * PerfKvmTimer (see perfControlledByTimer) to trigger exits from
+     * KVM.
+     */
     PerfKvmCounter hwCycles;
+
+    /**
+     * Guest instruction counter.
+     *
+     * This counter is typically only used to measure the number of
+     * instructions executed by the guest. However, it can also be
+     * used to trigger exits from KVM if the configuration script
+     * requests an exit after a certain number of instructions.
+     *
+     * @see setupInstBreak
+     * @see scheduleInstStop
+     */
     PerfKvmCounter hwInstructions;
-    /** @} */
 
     /**
      * Does the runTimer control the performance counters?
@@ -661,6 +709,7 @@ class BaseKvmCPU : public BaseCPU
      * exits.
      */
     bool perfControlledByTimer;
+    /** @} */
 
     /**
      * Timer used to force execution into the monitor after a