HAMMER (and kernel) - Fix cpu-bound kernel thread issue.
authorMatthew Dillon <dillon@apollo.backplane.com>
Sat, 20 Jun 2009 00:04:31 +0000 (17:04 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Sat, 20 Jun 2009 00:04:31 +0000 (17:04 -0700)
* HAMMER now calls a new kernel function, lwkt_user_yield(), in its
  ioctl-based loops (such as the reblocker).

* Add a new LWKT function called lwkt_user_yield().  This function
  causes a kernel thread to yield at user priority (instead of kernel
  priority).

  This function also deals with a nasty issue related to the MP lock.
  A cpu-bound kernel thread holding the MP lock can prevent other
  cpus from serving interrupt threads which also need the MP lock.
  Detect the condition and release the MP lock for 10uS to give the
  other cpus a chance to pick it up.  This is a bad hack but it
  actually does work.

* Move passive_release() from MD code to kern/lwkt_thread.c and add
  an inline for the passive release recovery function.  Adjust all
  platforms to use the new API instead of rolling the same code for
  each platform.

Reported-by: Many, but especially Hasso Tepper <hasso@estpak.ee>
sys/kern/lwkt_thread.c
sys/platform/pc32/i386/trap.c
sys/platform/pc64/amd64/trap.c
sys/platform/vkernel/i386/trap.c
sys/sys/thread.h
sys/sys/thread2.h
sys/vfs/hammer/hammer_signal.c

index 53cfcf4..ea02c27 100644 (file)
@@ -73,6 +73,9 @@
 
 static MALLOC_DEFINE(M_THREAD, "thread", "lwkt threads");
 
+#ifdef SMP
+static int mplock_countx = 0;
+#endif
 static int untimely_switch = 0;
 #ifdef INVARIANTS
 static int panic_on_cscount = 0;
@@ -704,6 +707,8 @@ again:
                    TAILQ_INSERT_TAIL(&gd->gd_tdrunq[nq], ntd, td_threadq);
                }
            } else {
+               if (ntd->td_mpcount)
+                       ++mplock_countx;
                ++gd->gd_cnt.v_swtch;
                TAILQ_REMOVE(&gd->gd_tdrunq[nq], ntd, td_threadq);
                TAILQ_INSERT_TAIL(&gd->gd_tdrunq[nq], ntd, td_threadq);
@@ -1004,6 +1009,83 @@ lwkt_yield(void)
 }
 
 /*
+ * This function is used along with the lwkt_passive_recover() inline
+ * by the trap code to negotiate a passive release of the current
+ * process/lwp designation with the user scheduler.
+ */
+void
+lwkt_passive_release(struct thread *td)
+{
+    struct lwp *lp = td->td_lwp;
+
+    td->td_release = NULL;
+    lwkt_setpri_self(TDPRI_KERN_USER);
+    lp->lwp_proc->p_usched->release_curproc(lp);
+}
+
+/*
+ * Make a kernel thread act as if it were in user mode with regards
+ * to scheduling, to avoid becoming cpu-bound in the kernel.  Kernel
+ * loops which may be potentially cpu-bound can call lwkt_user_yield().
+ *
+ * The lwkt_user_yield() function is designed to have very low overhead
+ * if no yield is determined to be needed.
+ */
+void
+lwkt_user_yield(void)
+{
+    thread_t td = curthread;
+    struct lwp *lp = td->td_lwp;
+
+#ifdef SMP
+    /*
+     * XXX SEVERE TEMPORARY HACK.  A cpu-bound operation running in the
+     * kernel can prevent other cpus from servicing interrupt threads
+     * which still require the MP lock (which is a lot of them).  This
+     * has a chaining effect since if the interrupt is blocked, so is
+     * the event, so normal scheduling will not pick up on the problem.
+     */
+    if (mplock_countx && td->td_mpcount) {
+       int savecnt = td->td_mpcount;
+
+       td->td_mpcount = 1;
+       rel_mplock();
+       DELAY(10);
+       get_mplock();
+       td->td_mpcount = savecnt;
+       mplock_countx = 0;
+    }
+#endif
+
+    /*
+     * Another kernel thread wants the cpu
+     */
+    if (lwkt_resched_wanted())
+       lwkt_switch();
+
+    /*
+     * If the user scheduler has asynchronously determined that the current
+     * process (when running in user mode) needs to lose the cpu then make
+     * sure we are released.
+     */
+    if (user_resched_wanted()) {
+       if (td->td_release)
+           td->td_release(td);
+    }
+
+    /*
+     * If we are released reduce our priority
+     */
+    if (td->td_release == NULL) {
+       if (lwkt_check_resched(td) > 0)
+               lwkt_switch();
+       lp->lwp_proc->p_usched->acquire_curproc(lp);
+       td->td_release = lwkt_passive_release;
+       lwkt_setpri_self(TDPRI_USER_NORM);
+    }
+}
+
+/*
  * Return 0 if no runnable threads are pending at the same or higher
  * priority as the passed thread.
  *
@@ -1447,6 +1529,7 @@ lwkt_smp_stopped(void)
 void
 lwkt_mp_lock_contested(void)
 {
+    ++mplock_countx;
     loggiant(beg);
     lwkt_switch();
     loggiant(end);
index c31c13e..2e66c5e 100644 (file)
@@ -197,22 +197,6 @@ MALLOC_DEFINE(M_SYSMSG, "sysmsg", "sysmsg structure");
 extern int max_sysmsg;
 
 /*
- * Passive USER->KERNEL transition.  This only occurs if we block in the
- * kernel while still holding our userland priority.  We have to fixup our
- * priority in order to avoid potential deadlocks before we allow the system
- * to switch us to another thread.
- */
-static void
-passive_release(struct thread *td)
-{
-       struct lwp *lp = td->td_lwp;
-
-       td->td_release = NULL;
-       lwkt_setpri_self(TDPRI_KERN_USER);
-       lp->lwp_proc->p_usched->release_curproc(lp);
-}
-
-/*
  * userenter() passively intercepts the thread switch function to increase
  * the thread priority from a user priority to a kernel priority, reducing
  * syscall and trap overhead for the case where no switch occurs.
@@ -221,7 +205,7 @@ passive_release(struct thread *td)
 static __inline void
 userenter(struct thread *curtd)
 {
-       curtd->td_release = passive_release;
+       curtd->td_release = lwkt_passive_release;
 }
 
 /*
@@ -340,9 +324,7 @@ userexit(struct lwp *lp)
         * our passive release function was still in place, our priority was
         * never raised and does not need to be reduced.
         */
-       if (td->td_release == NULL)
-               lwkt_setpri_self(TDPRI_USER_NORM);
-       td->td_release = NULL;
+       lwkt_passive_recover(td);
 
        /*
         * Become the current user scheduled process if we aren't already,
index 0094e4c..08be0d5 100644 (file)
@@ -165,24 +165,6 @@ SYSCTL_INT(_kern, OID_AUTO, trap_mpsafe, CTLFLAG_RW,
 TUNABLE_INT("kern.trap_mpsafe", &trap_mpsafe);
 #endif
 
-
-
-/*
- * Passive USER->KERNEL transition.  This only occurs if we block in the
- * kernel while still holding our userland priority.  We have to fixup our
- * priority in order to avoid potential deadlocks before we allow the system
- * to switch us to another thread.
- */
-static void
-passive_release(struct thread *td)
-{
-       struct lwp *lp = td->td_lwp;
-
-       td->td_release = NULL;
-       lwkt_setpri_self(TDPRI_KERN_USER);
-       lp->lwp_proc->p_usched->release_curproc(lp);
-}
-
 /*
  * userenter() passively intercepts the thread switch function to increase
  * the thread priority from a user priority to a kernel priority, reducing
@@ -192,7 +174,7 @@ passive_release(struct thread *td)
 static __inline void
 userenter(struct thread *curtd)
 {
-       curtd->td_release = passive_release;
+       curtd->td_release = lwkt_passive_release;
 }
 
 /*
@@ -311,9 +293,7 @@ userexit(struct lwp *lp)
         * our passive release function was still in place, our priority was
         * never raised and does not need to be reduced.
         */
-       if (td->td_release == NULL)
-               lwkt_setpri_self(TDPRI_USER_NORM);
-       td->td_release = NULL;
+       lwkt_passive_recover(td);
 
        /*
         * Become the current user scheduled process if we aren't already,
index 2938ef1..1c92e97 100644 (file)
@@ -183,22 +183,6 @@ MALLOC_DEFINE(M_SYSMSG, "sysmsg", "sysmsg structure");
 extern int max_sysmsg;
 
 /*
- * Passive USER->KERNEL transition.  This only occurs if we block in the
- * kernel while still holding our userland priority.  We have to fixup our
- * priority in order to avoid potential deadlocks before we allow the system
- * to switch us to another thread.
- */
-static void
-passive_release(struct thread *td)
-{
-       struct lwp *lp = td->td_lwp;
-
-       td->td_release = NULL;
-       lwkt_setpri_self(TDPRI_KERN_USER);
-       lp->lwp_proc->p_usched->release_curproc(lp);
-}
-
-/*
  * userenter() passively intercepts the thread switch function to increase
  * the thread priority from a user priority to a kernel priority, reducing
  * syscall and trap overhead for the case where no switch occurs.
@@ -207,7 +191,7 @@ passive_release(struct thread *td)
 static __inline void
 userenter(struct thread *curtd)
 {
-       curtd->td_release = passive_release;
+       curtd->td_release = lwkt_passive_release;
 }
 
 /*
@@ -324,9 +308,7 @@ userexit(struct lwp *lp)
         * our passive release function was still in place, our priority was
         * never raised and does not need to be reduced.
         */
-       if (td->td_release == NULL)
-               lwkt_setpri_self(TDPRI_USER_NORM);
-       td->td_release = NULL;
+       lwkt_passive_recover(td);
 
        /*
         * Become the current user scheduled process if we aren't already,
index 5543e4f..6be7d18 100644 (file)
@@ -356,9 +356,11 @@ extern void lwkt_deschedule(thread_t);
 extern void lwkt_deschedule_self(thread_t);
 extern void lwkt_yield(void);
 extern void lwkt_yield_quick(void);
+extern void lwkt_user_yield(void);
 extern void lwkt_token_wait(void);
 extern void lwkt_hold(thread_t);
 extern void lwkt_rele(thread_t);
+extern void lwkt_passive_release(thread_t);
 
 extern void lwkt_gettoken(lwkt_tokref_t, lwkt_token_t);
 extern int lwkt_trytoken(lwkt_tokref_t, lwkt_token_t);
index ebac5f5..1957d40 100644 (file)
@@ -229,6 +229,21 @@ lwkt_getpri_self(void)
     return(lwkt_getpri(curthread));
 }
 
+/*
+ * Reduce our priority in preparation for a return to userland.  If
+ * our passive release function was still in place, our priority was
+ * never raised and does not need to be reduced.
+ *
+ * See also lwkt_passive_release() and platform/blah/trap.c
+ */
+static __inline void
+lwkt_passive_recover(thread_t td)
+{
+    if (td->td_release == NULL)
+       lwkt_setpri_self(TDPRI_USER_NORM);
+    td->td_release = NULL;
+}
+
 #ifdef SMP
 
 /*
index c0ad1bf..fd48879 100644 (file)
@@ -44,6 +44,7 @@ hammer_signal_check(hammer_mount_t hmp)
 {
        int sig;
 
+       lwkt_user_yield();
        if (++hmp->check_interrupt < 100)
                return(0);
        hmp->check_interrupt = 0;