smp_rendezvous runs with interrupts and preemption enabled on unicore systems

Attilio Rao attilio at freebsd.org
Mon Oct 31 23:43:05 UTC 2011


2011/10/28  <mdf at freebsd.org>:
> On Fri, Oct 28, 2011 at 8:37 AM, Ryan Stone <rysto32 at gmail.com> wrote:
>> I'm seeing issues on a unicore systems running a derivative of FreeBSD
>> 8.2-RELEASE if something calls mem_range_attr_set.  It turns out that
>> the root cause is a bug in smp_rendezvous_cpus.  The first part of
>> smp_rendezvous_cpus attempts to short-circuit the non-SMP case(note
>> that smp_started is never set to 1 on a unicore system):
>>
>>        if (!smp_started) {
>>                if (setup_func != NULL)
>>                        setup_func(arg);
>>                if (action_func != NULL)
>>                        action_func(arg);
>>                if (teardown_func != NULL)
>>                        teardown_func(arg);
>>                return;
>>        }
>>
>> The problem is that this runs with interrupts enabled, outside of a
>> critical section.  My system runs with device_polling enabled with hz
>> set to 2500, so its quite easy to wedge the system by having a thread
>> run mem_range_attr_set.  That has to do a smp_rendezvous, and if a
>> timer interrupt happens to go off half-way through the action_func and
>> preempt this thread, the system ends up deadlocked(although once it's
>> wedged, typing at the serial console stands a good chance of unwedging
>> the system.  Go figure).

I'm not entirely sure why this exactly breaks though (do you see that
happening with a random rendezvous callback or it is always the
same?), because that just becames a simple function calling on cpu0,
even if I think that there is still a bug as smp_rendezvous callbacks
may expect to have interrupts and preemption disabled and the
short-circuit breaks that assumption.

>> I know that smp_rendezvous was reworked substantially on HEAD, but by
>> inspection it looks like the bug is still present, as the
>> short-circuit behaviour is still there.
>>
>> I am not entirely sure of the best way to fix this.  Is it as simple
>> as doing a spinlock_enter before setup_func and a spinlock_exit after
>> teardown_func?  It seems to boot fine, but I'm not at all confident
>> that I understand the nuances of smp_rendezvous to be sure that there
>> aren't side effects that I don't know about.
>
> Looks like Max didn't have time to upstream this fix:
>
>  struct mtx smp_ipi_mtx;
> +MTX_SYSINIT(smp_ipi_mtx, &smp_ipi_mtx, "smp rendezvous", MTX_SPIN);
>
> ...
>
>  static void
>  mp_start(void *dummy)
>  {
>
> -       mtx_init(&smp_ipi_mtx, "smp rendezvous", NULL, MTX_SPIN);
>
> ...
>
>        if (!smp_started) {
> +               mtx_lock_spin(&smp_ipi_mtx);
>                if (setup_func != NULL)
>                        setup_func(arg);
>                if (action_func != NULL)
>                        action_func(arg);
>                if (teardown_func != NULL)
>                        teardown_func(arg);
> +               mtx_unlock_spin(&smp_ipi_mtx);
>                return;
>        }

I don't think that we strictly need the lock here, my preferred
solution would be (only test-compiled):
Index: sys/kern/subr_smp.c
===================================================================
--- sys/kern/subr_smp.c (revision 226972)
+++ sys/kern/subr_smp.c (working copy)
@@ -415,13 +415,16 @@ smp_rendezvous_cpus(cpuset_t map,
 {
        int curcpumap, i, ncpus = 0;

+       /* Look comments in the !SMP case. */
        if (!smp_started) {
+               spinlock_enter();
                if (setup_func != NULL)
                        setup_func(arg);
                if (action_func != NULL)
                        action_func(arg);
                if (teardown_func != NULL)
                        teardown_func(arg);
+               spinlock_exit();
                return;
        }

@@ -666,12 +669,18 @@ smp_rendezvous_cpus(cpuset_t map,
        void (*teardown_func)(void *),
        void *arg)
 {
+       /*
+        * In the !SMP case we just need to ensure the same initial conditions
+        * as the SMP case.
+        */
+       spinlock_enter();
        if (setup_func != NULL)
                setup_func(arg);
        if (action_func != NULL)
                action_func(arg);
        if (teardown_func != NULL)
                teardown_func(arg);
+       spinlock_exit();
 }

 void
@@ -681,12 +690,15 @@ smp_rendezvous(void (*setup_func)(void *),
               void *arg)
 {

+       /* Look comments in the smp_rendezvous_cpus() case. */
+       spinlock_enter();
        if (setup_func != NULL)
                setup_func(arg);
        if (action_func != NULL)
                action_func(arg);
        if (teardown_func != NULL)
                teardown_func(arg);
+       spinlock_exit();
 }

 /*


Thanks,
Attilio


-- 
Peace can only be achieved by understanding - A. Einstein


More information about the freebsd-current mailing list