Date: Thu, 13 Apr 2017 21:46:26 +0800 From: Yubin Ruan <ablacktshirt@gmail.com> To: Chris Torek <torek@elf.torek.net>, imp@bsdimp.com Cc: ed@nuxi.nl, freebsd-hackers@freebsd.org, kostikbel@gmail.com, rysto32@gmail.com Subject: Re: Understanding the FreeBSD locking mechanism Message-ID: <a51d29c2-4cab-0dfc-6fdc-81d7b2188d61@gmail.com> In-Reply-To: <201704131218.v3DCIBJg093207@elf.torek.net> References: <201704131218.v3DCIBJg093207@elf.torek.net>
next in thread | previous in thread | raw e-mail | index | archive | help
On 2017年04月13日 20:18, Chris Torek wrote: >> I discover that in the current implementation in FreeBSD, spinlock >> does not disable interrupt entirely: > [extra-snipped here] >> 610 /* Give interrupts a chance while we spin. */ >> 611 spinlock_exit(); >> 612 while (m->mtx_lock != MTX_UNOWNED) { > [more snip] > >> This is `_mtx_lock_spin_cookie(...)` in kern/kern_mutex.c, which >> implements the core logic of spinning. However, as you can see, while >> spinning, it would enable interrupt "occasionally" and disable it >> again... What is the rationale for that? > > This code snippet is slightly misleading. The full code path runs > from mtx_lock_spin() through __mtx_lock_spin(), which first > invokes spinlock_enter() and then, in the *contested* case (only), > calls _mtx_lock_spin_cookie(). > > spinlock_enter() is: > > td = curthread; > if (td->td_md.md_spinlock_count == 0) { > flags = intr_disable(); > td->td_md.md_spinlock_count = 1; > td->td_md.md_saved_flags = flags; > } else > td->td_md.md_spinlock_count++; > critical_enter(); > > so it actualy disables interrupts *only* on the transition from > td->td_md.md_spinlock_count = 0 to td->td_md.md_spinlock_count = 1, > i.e., the first time we take a spin lock in this thread, whether > this is a borrowed thread or not. It's possible that interrupts > are actually disabled at this point. If so, td->td_md.md_saved_flags > has interrupts disabled as well. This is all just an optimization > to use a thread-local variable so as to avoid touching hardware. > The details vary widely, but typically, touching the actual hardware > controls requires flushing the CPU's instruction pipeline. > > If the compare-and-swap fails, we enter _mtx_lock_spin_cookie() > and loop waiting to see if we can obtain the spin lock in time. > In that case, we don't actually *hold* this particular spin lock > itself yet, so we can call spinlock_exit() to undo the effect > of the outermost spinlock_enter() (in __mtx_lock_spin). That > decrements the counter. *If* it goes to zero, that also calls > intr_restore(td->td_md.md_saved_flags). > > Hence, if we have failed to obtain our first spin lock, we restore > the interrupt setting to whatever we saved. If interrupts were > already locked out (as in a filter type interrupt handler) this is > a potentially-somewhat-expensive no-op. If interrupts were > enabled previously, this is a somewhat expensive re-enable of > interrupts -- but that's OK, and maybe good, because we have no > spin locks of our own yet. That means we can take hardware > interrupts now, and let them borrow our current thread if they are > that kind of interrupt, or schedule another thread to run if > appropriate. That might even preempt us, since we do not yet hold > any spin locks. (But it won't preempt us if we have done a > critical_enter() before this point.) Good explanation. I just missed that "local" interrupt point. > (In fact, the spinlock exit/enter calls that you see inside > _mtx_lock_spin_cookie() wrap a loop that does not use compare-and- > swap operations at all, but rather ordinary memory reads. These > are cheaper than CAS operations on a lot of CPUs, but they may > produce wrong answers when two CPUs are racing to write the same why would that produce wrong result? I think what the inner loop wants to do is to perform some no-op for a while before it tries again to acquire the spinlock. So there is no race here. > location; only a CAS produces a guaranteed answer, which might > still be "you lost the race". The inner loop you are looking at > occurs after losing a CAS race. Once we think we might *win* a > future CAS race, _mtx_lock_spin_cookie() calls spinlock_enter() > again and tries the actual CAS operation, _mtx_obtain_lock_fetch(), > with interrupts disabled. Note also the calls to cpu_spinwait() > -- the Linux equivalent macro is cpu_relax() -- which translates > to a "pause" instruction on amd64.) Regards, Yubin Ruan
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?a51d29c2-4cab-0dfc-6fdc-81d7b2188d61>