Date: Tue, 17 May 2011 08:16:32 -0400 From: John Baldwin <jhb@FreeBSD.org> To: Max Laier <max@love2party.net> Cc: neel@freebsd.org, Andriy Gapon <avg@freebsd.org>, Attilio Rao <attilio@freebsd.org>, FreeBSD current <freebsd-current@freebsd.org>, Stephan Uphoff <ups@FreeBSD.org>, Peter Grehan <grehan@freebsd.org> Subject: Re: proposed smp_rendezvous change Message-ID: <4DD26720.3000001@FreeBSD.org> In-Reply-To: <201105161805.51188.max@love2party.net> References: <4DCD357D.6000109@FreeBSD.org> <201105161738.53366.max@love2party.net> <BANLkTik8CqtiP9OgvBpL08dqK6Aj%2BLQ3OA@mail.gmail.com> <201105161805.51188.max@love2party.net>
next in thread | previous in thread | raw e-mail | index | archive | help
On 5/16/11 6:05 PM, Max Laier wrote: > On Monday 16 May 2011 17:54:54 Attilio Rao wrote: >> 2011/5/16 Max Laier<max@love2party.net>: >>> On Monday 16 May 2011 16:46:03 John Baldwin wrote: >>>> On Monday, May 16, 2011 4:30:44 pm Max Laier wrote: >>>>> On Monday 16 May 2011 14:21:27 John Baldwin wrote: >>>>>> Yes, we need to fix that. Humm, it doesn't preempt when you do a >>>>>> critical_exit() though? Or do you use a hand-rolled critical exit >>>>>> that doesn't do a deferred preemption? >>>>> >>>>> Right now I just did a manual td_critnest++/--, but I guess ... >>>> >>>> Ah, ok, so you would "lose" a preemption. That's not really ideal. >>>> >>>>>> Actually, I'm curious how the spin unlock inside the IPI could yield >>>>>> the CPU. Oh, is rmlock doing a wakeup inside the IPI handler? I >>>>>> guess that is ok as long as the critical_exit() just defers the >>>>>> preemption to the end of the IPI handler. >>>>> >>>>> ... the earliest point where it is safe to preempt is after doing the >>>>> >>>>> atomic_add_int(&smp_rv_waiters[2], 1); >>>>> >>>>> so that we can start other IPIs again. However, since we don't accept >>>>> new IPIs until we signal EOI in the MD code (on amd64), this might >>>>> still not be a good place to do the yield?!? >>>> >>>> Hmm, yeah, you would want to do the EOI before you yield. However, we >>>> could actually move the EOI up before calling the MI code so long as we >>>> leave interrupts disabled for the duration of the handler (which we do). >>>> >>>>> The spin unlock boils down to a critical_exit() and unless we did a >>>>> critical_enter() at some point during the redenvouz setup, we will >>>>> yield() if we owepreempt. I'm not quite sure how that can happen, but >>>>> it seems like there is a path that allows the scheduler to set it from >>>>> a foreign CPU. >>>> >>>> No, it is only set on curthread by curthread. This is actually my main >>>> question. I've no idea how this could happen unless the rmlock code is >>>> actually triggering a wakeup or sched_add() in its rendezvous handler. >>>> >>>> I don't see anything in rm_cleanIPI() that would do that however. >>>> >>>> I wonder if your original issue was really fixed just by the first >>>> patch you had which fixed the race in smp_rendezvous()? >>> >>> I found the stack that lead me to this patch in the first place: >>> >>> #0 sched_switch (td=0xffffff011a970000, newtd=0xffffff006e6784b0, >>> flags=4) at src/sys/kern/sched_ule.c:1939 >>> #1 0xffffffff80285c7f in mi_switch (flags=6, newtd=0x0) at >>> src/sys/kern/kern_synch.c:475 >>> #2 0xffffffff802a2cb3 in critical_exit () at >>> src/sys/kern/kern_switch.c:185 #3 0xffffffff80465807 in spinlock_exit >>> () at >>> src/sys/amd64/amd64/machdep.c:1458 >>> #4 0xffffffff8027adea in rm_cleanIPI (arg=<value optimized out>) at >>> src/sys/kern/kern_rmlock.c:180 >>> #5 0xffffffff802b9887 in smp_rendezvous_action () at >>> src/sys/kern/subr_smp.c:402 >>> #6 0xffffffff8045e2a4 in Xrendezvous () at >>> src/sys/amd64/amd64/apic_vector.S:235 >>> #7 0xffffffff802a2c6e in critical_exit () at >>> src/sys/kern/kern_switch.c:179 #8 0xffffffff804365ba in uma_zfree_arg >>> (zone=0xffffff009ff4b5a0, item=0xffffff000f34cd40, >>> udata=0xffffff000f34ce08) at >>> src/sys/vm/uma_core.c:2370 >>> . >>> . >>> . >>> >>> and now that I look at it again, it is clear that critical_exit() just >>> isn't interrupt safe. I'm not sure how to fix that, yet ... but this: >>> >>> >>> if (td->td_critnest == 1) { >>> td->td_critnest = 0; >>> if (td->td_owepreempt) { >>> td->td_critnest = 1; >>> >>> clearly doesn't work. >> >> I'm sorry if I didn't reply to the whole rendezvous thread, but as >> long as there is so many people taking care of it, I'll stay hidden in >> my corner. >> >> I just wanted to tell that I think you are misunderstanding what >> critical section is supposed to do. >> >> When an interrupt fires, it goes on the old "interrupt/kernel context" >> which means it has not a context of his own. That is the reason why we >> disable interrupts on spinlocks (or similar workaround for !x86 >> architectures) and this is why spinlocks are the only protection >> usable in code that runs in interrupt context. >> >> Preempting just means another thread will be scheduler in the middle >> of another thread execution path. >> >> This code is perfectly fine if you consider curthread won't be >> descheduled while it is executing. > > Well, no - it is not. With this you can end up with a curthread that has > td_critnest=0 and td_owepreempt=1 in interrupt context. If you use a spinlock > on such a thread, it will do the preemption at the point where you drop the > spinlock, this is bad in some circumstances. One example is the smp_rendevous > case we are discussing here. Yes, the 'owepreempt' flag can leak because we allow it to be set while td_critnest is 0. That is the problem, and I think we added this as an optimization in the last round of changes to this routine to avoid excessive context switches. However, I think we will just have to revert that optimization and prevent that state from occurring. Hmm, the log message for the change said it was to avoid races. http://svnweb.freebsd.org/base?view=revision&revision=144777 That is what introduced the bug of mixing those two states. Hmmm, the reason for moving the td_critnest == 0 up according to my old e-mails was to avoid a problem with losing preemptions. I think the best way to fix this is to clear owepreempt earlier while td_critnest is still 1 and cache it locally in critical_exit(). So something like this: Index: kern/sched_ule.c =================================================================== --- kern/sched_ule.c (revision 221536) +++ kern/sched_ule.c (working copy) @@ -1785,7 +1785,7 @@ td->td_oncpu = NOCPU; if (!(flags & SW_PREEMPT)) td->td_flags &= ~TDF_NEEDRESCHED; - td->td_owepreempt = 0; + MPASS(td->td_owepreempt == 0); tdq->tdq_switchcnt++; /* * The lock pointer in an idle thread should never change. Reset it Index: kern/kern_switch.c =================================================================== --- kern/kern_switch.c (revision 221536) +++ kern/kern_switch.c (working copy) @@ -192,15 +192,22 @@ critical_exit(void) { struct thread *td; - int flags; + int flags, owepreempt; td = curthread; KASSERT(td->td_critnest != 0, ("critical_exit: td_critnest == 0")); if (td->td_critnest == 1) { + owepreempt = td->td_owepreempt; + td->td_owepreempt = 0; + /* + * XXX: Should move compiler_memory_barrier() from + * rmlock to a header. + */ + __asm __volatile("":::"memory"); td->td_critnest = 0; - if (td->td_owepreempt) { + if (owepreempt) { td->td_critnest = 1; thread_lock(td); td->td_critnest--; Index: kern/kern_synch.c =================================================================== --- kern/kern_synch.c (revision 221536) +++ kern/kern_synch.c (working copy) @@ -400,9 +400,7 @@ if (!TD_ON_LOCK(td) && !TD_IS_RUNNING(td)) mtx_assert(&Giant, MA_NOTOWNED); #endif - KASSERT(td->td_critnest == 1 || (td->td_critnest == 2 && - (td->td_owepreempt) && (flags & SW_INVOL) != 0 && - newtd == NULL) || panicstr, + KASSERT(td->td_critnest == 1 || panicstr, ("mi_switch: switch in a critical section")); KASSERT((flags & (SW_INVOL | SW_VOL)) != 0, ("mi_switch: switch must be voluntary or involuntary")); Index: kern/sched_4bsd.c =================================================================== --- kern/sched_4bsd.c (revision 221536) +++ kern/sched_4bsd.c (working copy) @@ -947,7 +947,7 @@ td->td_lastcpu = td->td_oncpu; if (!(flags & SW_PREEMPT)) td->td_flags &= ~TDF_NEEDRESCHED; - td->td_owepreempt = 0; + MPASS(td->td_owepreempt == 0); td->td_oncpu = NOCPU; /* Hmm, I'm also not sure if I really need the compiler barrier since td_owepreempt and td_critnest are marked volatile? -- John Baldwin
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?4DD26720.3000001>