Skip site navigation (1)Skip section navigation (2)
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>