Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1680343 > unrolled thread
| Started by | Alan Stern <stern@rowland.harvard.edu> |
|---|---|
| First post | 2017-07-03 16:50 +0200 |
| Last post | 2017-07-03 23:00 +0200 |
| Articles | 7 — 3 participants |
Back to article view | Back to linux.kernel
This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by
below is the oldest one visible, not the original post.
Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair Alan Stern <stern@rowland.harvard.edu> - 2017-07-03 16:50 +0200
Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2017-07-03 19:20 +0200
Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair Manfred Spraul <manfred@colorfullife.com> - 2017-07-03 21:10 +0200
Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair Alan Stern <stern@rowland.harvard.edu> - 2017-07-03 22:00 +0200
Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair Manfred Spraul <manfred@colorfullife.com> - 2017-07-06 20:50 +0200
Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair Alan Stern <stern@rowland.harvard.edu> - 2017-07-03 22:10 +0200
Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> - 2017-07-03 23:00 +0200
| From | Alan Stern <stern@rowland.harvard.edu> |
|---|---|
| Date | 2017-07-03 16:50 +0200 |
| Subject | Re: [PATCH RFC 01/26] netfilter: Replace spin_unlock_wait() with lock/unlock pair |
| Message-ID | <tZaPP-2Pz-77@gated-at.bofh.it> |
On Sat, 1 Jul 2017, Manfred Spraul wrote:
> As we want to remove spin_unlock_wait() and replace it with explicit
> spin_lock()/spin_unlock() calls, we can use this to simplify the
> locking.
>
> In addition:
> - Reading nf_conntrack_locks_all needs ACQUIRE memory ordering.
> - The new code avoids the backwards loop.
>
> Only slightly tested, I did not manage to trigger calls to
> nf_conntrack_all_lock().
>
> Fixes: b16c29191dc8
> Signed-off-by: Manfred Spraul <manfred@colorfullife.com>
> Cc: <stable@vger.kernel.org>
> Cc: Sasha Levin <sasha.levin@oracle.com>
> Cc: Pablo Neira Ayuso <pablo@netfilter.org>
> Cc: netfilter-devel@vger.kernel.org
> ---
> net/netfilter/nf_conntrack_core.c | 44 +++++++++++++++++++++------------------
> 1 file changed, 24 insertions(+), 20 deletions(-)
>
> diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
> index e847dba..1193565 100644
> --- a/net/netfilter/nf_conntrack_core.c
> +++ b/net/netfilter/nf_conntrack_core.c
> @@ -96,19 +96,24 @@ static struct conntrack_gc_work conntrack_gc_work;
>
> void nf_conntrack_lock(spinlock_t *lock) __acquires(lock)
> {
> + /* 1) Acquire the lock */
> spin_lock(lock);
> - while (unlikely(nf_conntrack_locks_all)) {
> - spin_unlock(lock);
>
> - /*
> - * Order the 'nf_conntrack_locks_all' load vs. the
> - * spin_unlock_wait() loads below, to ensure
> - * that 'nf_conntrack_locks_all_lock' is indeed held:
> - */
> - smp_rmb(); /* spin_lock(&nf_conntrack_locks_all_lock) */
> - spin_unlock_wait(&nf_conntrack_locks_all_lock);
> - spin_lock(lock);
> - }
> + /* 2) read nf_conntrack_locks_all, with ACQUIRE semantics */
> + if (likely(smp_load_acquire(&nf_conntrack_locks_all) == false))
> + return;
As far as I can tell, this read does not need to have ACQUIRE
semantics.
You need to guarantee that two things can never happen:
(1) We read nf_conntrack_locks_all == false, and this routine's
critical section for nf_conntrack_locks[i] runs after the
(empty) critical section for that lock in
nf_conntrack_all_lock().
(2) We read nf_conntrack_locks_all == true, and this routine's
critical section for nf_conntrack_locks_all_lock runs before
the critical section in nf_conntrack_all_lock().
In fact, neither one can happen even if smp_load_acquire() is replaced
with READ_ONCE(). The reason is simple enough, using this property of
spinlocks:
If critical section CS1 runs before critical section CS2 (for
the same lock) then: (a) every write coming before CS1's
spin_unlock() will be visible to any read coming after CS2's
spin_lock(), and (b) no write coming after CS2's spin_lock()
will be visible to any read coming before CS1's spin_unlock().
Thus for (1), assuming the critical sections run in the order mentioned
above, since nf_conntrack_all_lock() writes to nf_conntrack_locks_all
before releasing nf_conntrack_locks[i], and since nf_conntrack_lock()
acquires nf_conntrack_locks[i] before reading nf_conntrack_locks_all,
by (a) the read will always see the write.
Similarly for (2), since nf_conntrack_all_lock() acquires
nf_conntrack_locks_all_lock before writing to nf_conntrack_locks_all,
and since nf_conntrack_lock() reads nf_conntrack_locks_all before
releasing nf_conntrack_locks_all_lock, by (b) the read cannot see the
write.
Alan Stern
> +
> + /* fast path failed, unlock */
> + spin_unlock(lock);
> +
> + /* Slow path 1) get global lock */
> + spin_lock(&nf_conntrack_locks_all_lock);
> +
> + /* Slow path 2) get the lock we want */
> + spin_lock(lock);
> +
> + /* Slow path 3) release the global lock */
> + spin_unlock(&nf_conntrack_locks_all_lock);
> }
> EXPORT_SYMBOL_GPL(nf_conntrack_lock);
>
> @@ -149,18 +154,17 @@ static void nf_conntrack_all_lock(void)
> int i;
>
> spin_lock(&nf_conntrack_locks_all_lock);
> - nf_conntrack_locks_all = true;
>
> - /*
> - * Order the above store of 'nf_conntrack_locks_all' against
> - * the spin_unlock_wait() loads below, such that if
> - * nf_conntrack_lock() observes 'nf_conntrack_locks_all'
> - * we must observe nf_conntrack_locks[] held:
> - */
> - smp_mb(); /* spin_lock(&nf_conntrack_locks_all_lock) */
> + nf_conntrack_locks_all = true;
>
> for (i = 0; i < CONNTRACK_LOCKS; i++) {
> - spin_unlock_wait(&nf_conntrack_locks[i]);
> + spin_lock(&nf_conntrack_locks[i]);
> +
> + /* This spin_unlock provides the "release" to ensure that
> + * nf_conntrack_locks_all==true is visible to everyone that
> + * acquired spin_lock(&nf_conntrack_locks[]).
> + */
> + spin_unlock(&nf_conntrack_locks[i]);
> }
> }
[toc] | [next] | [standalone]
| From | "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> |
|---|---|
| Date | 2017-07-03 19:20 +0200 |
| Message-ID | <tZdaW-4tv-13@gated-at.bofh.it> |
| In reply to | #1680343 |
On Mon, Jul 03, 2017 at 10:39:49AM -0400, Alan Stern wrote:
> On Sat, 1 Jul 2017, Manfred Spraul wrote:
>
> > As we want to remove spin_unlock_wait() and replace it with explicit
> > spin_lock()/spin_unlock() calls, we can use this to simplify the
> > locking.
> >
> > In addition:
> > - Reading nf_conntrack_locks_all needs ACQUIRE memory ordering.
> > - The new code avoids the backwards loop.
> >
> > Only slightly tested, I did not manage to trigger calls to
> > nf_conntrack_all_lock().
> >
> > Fixes: b16c29191dc8
> > Signed-off-by: Manfred Spraul <manfred@colorfullife.com>
> > Cc: <stable@vger.kernel.org>
> > Cc: Sasha Levin <sasha.levin@oracle.com>
> > Cc: Pablo Neira Ayuso <pablo@netfilter.org>
> > Cc: netfilter-devel@vger.kernel.org
> > ---
> > net/netfilter/nf_conntrack_core.c | 44 +++++++++++++++++++++------------------
> > 1 file changed, 24 insertions(+), 20 deletions(-)
> >
> > diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
> > index e847dba..1193565 100644
> > --- a/net/netfilter/nf_conntrack_core.c
> > +++ b/net/netfilter/nf_conntrack_core.c
> > @@ -96,19 +96,24 @@ static struct conntrack_gc_work conntrack_gc_work;
> >
> > void nf_conntrack_lock(spinlock_t *lock) __acquires(lock)
> > {
> > + /* 1) Acquire the lock */
> > spin_lock(lock);
> > - while (unlikely(nf_conntrack_locks_all)) {
> > - spin_unlock(lock);
> >
> > - /*
> > - * Order the 'nf_conntrack_locks_all' load vs. the
> > - * spin_unlock_wait() loads below, to ensure
> > - * that 'nf_conntrack_locks_all_lock' is indeed held:
> > - */
> > - smp_rmb(); /* spin_lock(&nf_conntrack_locks_all_lock) */
> > - spin_unlock_wait(&nf_conntrack_locks_all_lock);
> > - spin_lock(lock);
> > - }
> > + /* 2) read nf_conntrack_locks_all, with ACQUIRE semantics */
> > + if (likely(smp_load_acquire(&nf_conntrack_locks_all) == false))
> > + return;
>
> As far as I can tell, this read does not need to have ACQUIRE
> semantics.
>
> You need to guarantee that two things can never happen:
>
> (1) We read nf_conntrack_locks_all == false, and this routine's
> critical section for nf_conntrack_locks[i] runs after the
> (empty) critical section for that lock in
> nf_conntrack_all_lock().
>
> (2) We read nf_conntrack_locks_all == true, and this routine's
> critical section for nf_conntrack_locks_all_lock runs before
> the critical section in nf_conntrack_all_lock().
>
> In fact, neither one can happen even if smp_load_acquire() is replaced
> with READ_ONCE(). The reason is simple enough, using this property of
> spinlocks:
>
> If critical section CS1 runs before critical section CS2 (for
> the same lock) then: (a) every write coming before CS1's
> spin_unlock() will be visible to any read coming after CS2's
> spin_lock(), and (b) no write coming after CS2's spin_lock()
> will be visible to any read coming before CS1's spin_unlock().
>
> Thus for (1), assuming the critical sections run in the order mentioned
> above, since nf_conntrack_all_lock() writes to nf_conntrack_locks_all
> before releasing nf_conntrack_locks[i], and since nf_conntrack_lock()
> acquires nf_conntrack_locks[i] before reading nf_conntrack_locks_all,
> by (a) the read will always see the write.
>
> Similarly for (2), since nf_conntrack_all_lock() acquires
> nf_conntrack_locks_all_lock before writing to nf_conntrack_locks_all,
> and since nf_conntrack_lock() reads nf_conntrack_locks_all before
> releasing nf_conntrack_locks_all_lock, by (b) the read cannot see the
> write.
And the Linux kernel memory model (https://lwn.net/Articles/718628/
and https://lwn.net/Articles/720550/) agrees with Alan. Here is
a litmus test, which emulates spin_lock() with xchg_acquire() and
spin_unlock() with smp_store_release():
------------------------------------------------------------------------
C C-ManfredSpraul-L1G1xchgnr.litmus
(* Expected result: Never. *)
{
}
P0(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
{
/* Acquire local lock. */
r10 = xchg_acquire(lcl, 1);
r1 = READ_ONCE(*nfcla);
if (r1) {
smp_store_release(lcl, 0);
r11 = xchg_acquire(gbl, 1);
r12 = xchg_acquire(lcl, 1);
smp_store_release(gbl, 0);
}
r2 = READ_ONCE(*gbl_held);
WRITE_ONCE(*lcl_held, 1);
WRITE_ONCE(*lcl_held, 0);
smp_store_release(lcl, 0);
}
P1(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
{
/* Acquire global lock. */
r10 = xchg_acquire(gbl, 1);
WRITE_ONCE(*nfcla, 1);
r11 = xchg_acquire(lcl, 1);
smp_store_release(lcl, 0);
r2 = READ_ONCE(*lcl_held);
WRITE_ONCE(*gbl_held, 1);
WRITE_ONCE(*gbl_held, 0);
smp_store_release(gbl, 0);
}
exists
((0:r2=1 \/ 1:r2=1) /\ 0:r10=0 /\ 0:r11=0 /\ 0:r12=0 /\ 1:r10=0 /\ 1:r11=0)
------------------------------------------------------------------------
The memory model says that the forbidden state does not happen:
------------------------------------------------------------------------
States 25
0:r10=0; 0:r11=0; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=0; 0:r11=0; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=1; 1:r2=0;
0:r10=0; 0:r11=0; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=1; 1:r2=1;
0:r10=0; 0:r11=0; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=1; 1:r2=0;
0:r10=0; 0:r11=0; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=1; 1:r2=1;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=1;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=1; 1:r2=0;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=1; 1:r2=1;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=1;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=1; 1:r2=0;
0:r10=0; 0:r11=1; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=1; 1:r2=1;
0:r10=0; 0:r11=1; 0:r12=1; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=0; 0:r11=1; 0:r12=1; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=1;
0:r10=0; 0:r11=1; 0:r12=1; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=0; 0:r11=1; 0:r12=1; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=1;
0:r10=1; 0:r11=0; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=1; 0:r11=0; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=1;
0:r10=1; 0:r11=0; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=1; 0:r11=0; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=1;
0:r10=1; 0:r11=1; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=1; 0:r11=1; 0:r12=0; 0:r2=0; 1:r10=0; 1:r11=0; 1:r2=1;
0:r10=1; 0:r11=1; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=0;
0:r10=1; 0:r11=1; 0:r12=0; 0:r2=1; 1:r10=0; 1:r11=0; 1:r2=1;
No
Witnesses
Positive: 0 Negative: 260
Condition exists ((0:r2=1 \/ 1:r2=1) /\ 0:r10=0 /\ 0:r11=0 /\ 0:r12=0 /\ 1:r10=0 /\ 1:r11=0)
Observation C-ManfredSpraul-L1G1xchgnr Never 0 260
------------------------------------------------------------------------
(Note the line "Positive: 0 Negative: 260", in other words, there
were no scenarios that matched the "exists" clause and 260 that did
not match.)
Of course, testing is also required. ;-)
Manfred, any objections to my changing your patch as Alan suggests?
Thanx, Paul
[toc] | [prev] | [next] | [standalone]
| From | Manfred Spraul <manfred@colorfullife.com> |
|---|---|
| Date | 2017-07-03 21:10 +0200 |
| Message-ID | <tZeTo-5CM-21@gated-at.bofh.it> |
| In reply to | #1680509 |
On 07/03/2017 07:14 PM, Paul E. McKenney wrote:
> On Mon, Jul 03, 2017 at 10:39:49AM -0400, Alan Stern wrote:
>> On Sat, 1 Jul 2017, Manfred Spraul wrote:
>>
>>> As we want to remove spin_unlock_wait() and replace it with explicit
>>> spin_lock()/spin_unlock() calls, we can use this to simplify the
>>> locking.
>>>
>>> In addition:
>>> - Reading nf_conntrack_locks_all needs ACQUIRE memory ordering.
>>> - The new code avoids the backwards loop.
>>>
>>> Only slightly tested, I did not manage to trigger calls to
>>> nf_conntrack_all_lock().
>>>
>>> Fixes: b16c29191dc8
>>> Signed-off-by: Manfred Spraul <manfred@colorfullife.com>
>>> Cc: <stable@vger.kernel.org>
>>> Cc: Sasha Levin <sasha.levin@oracle.com>
>>> Cc: Pablo Neira Ayuso <pablo@netfilter.org>
>>> Cc: netfilter-devel@vger.kernel.org
>>> ---
>>> net/netfilter/nf_conntrack_core.c | 44 +++++++++++++++++++++------------------
>>> 1 file changed, 24 insertions(+), 20 deletions(-)
>>>
>>> diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
>>> index e847dba..1193565 100644
>>> --- a/net/netfilter/nf_conntrack_core.c
>>> +++ b/net/netfilter/nf_conntrack_core.c
>>> @@ -96,19 +96,24 @@ static struct conntrack_gc_work conntrack_gc_work;
>>>
>>> void nf_conntrack_lock(spinlock_t *lock) __acquires(lock)
>>> {
>>> + /* 1) Acquire the lock */
>>> spin_lock(lock);
>>> - while (unlikely(nf_conntrack_locks_all)) {
>>> - spin_unlock(lock);
>>>
>>> - /*
>>> - * Order the 'nf_conntrack_locks_all' load vs. the
>>> - * spin_unlock_wait() loads below, to ensure
>>> - * that 'nf_conntrack_locks_all_lock' is indeed held:
>>> - */
>>> - smp_rmb(); /* spin_lock(&nf_conntrack_locks_all_lock) */
>>> - spin_unlock_wait(&nf_conntrack_locks_all_lock);
>>> - spin_lock(lock);
>>> - }
>>> + /* 2) read nf_conntrack_locks_all, with ACQUIRE semantics */
>>> + if (likely(smp_load_acquire(&nf_conntrack_locks_all) == false))
>>> + return;
>> As far as I can tell, this read does not need to have ACQUIRE
>> semantics.
>>
>> You need to guarantee that two things can never happen:
>>
>> (1) We read nf_conntrack_locks_all == false, and this routine's
>> critical section for nf_conntrack_locks[i] runs after the
>> (empty) critical section for that lock in
>> nf_conntrack_all_lock().
>>
>> (2) We read nf_conntrack_locks_all == true, and this routine's
>> critical section for nf_conntrack_locks_all_lock runs before
>> the critical section in nf_conntrack_all_lock().
I was looking at nf_conntrack_all_unlock:
There is a smp_store_release() - which memory barrier does this pair with?
nf_conntrack_all_unlock()
<arbitrary writes>
smp_store_release(a, false)
spin_unlock(b);
nf_conntrack_lock()
spin_lock(c);
xx=read_once(a)
if (xx==false)
return
<arbitrary read>
>> In fact, neither one can happen even if smp_load_acquire() is replaced
>> with READ_ONCE(). The reason is simple enough, using this property of
>> spinlocks:
>>
>> If critical section CS1 runs before critical section CS2 (for
>> the same lock) then: (a) every write coming before CS1's
>> spin_unlock() will be visible to any read coming after CS2's
>> spin_lock(), and (b) no write coming after CS2's spin_lock()
>> will be visible to any read coming before CS1's spin_unlock().
Does this apply? The locks are different.
>> Thus for (1), assuming the critical sections run in the order mentioned
>> above, since nf_conntrack_all_lock() writes to nf_conntrack_locks_all
>> before releasing nf_conntrack_locks[i], and since nf_conntrack_lock()
>> acquires nf_conntrack_locks[i] before reading nf_conntrack_locks_all,
>> by (a) the read will always see the write.
>>
>> Similarly for (2), since nf_conntrack_all_lock() acquires
>> nf_conntrack_locks_all_lock before writing to nf_conntrack_locks_all,
>> and since nf_conntrack_lock() reads nf_conntrack_locks_all before
>> releasing nf_conntrack_locks_all_lock, by (b) the read cannot see the
>> write.
> And the Linux kernel memory model (https://lwn.net/Articles/718628/
> and https://lwn.net/Articles/720550/) agrees with Alan. Here is
> a litmus test, which emulates spin_lock() with xchg_acquire() and
> spin_unlock() with smp_store_release():
>
> ------------------------------------------------------------------------
>
> C C-ManfredSpraul-L1G1xchgnr.litmus
>
> (* Expected result: Never. *)
>
> {
> }
>
> P0(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
> {
> /* Acquire local lock. */
> r10 = xchg_acquire(lcl, 1);
> r1 = READ_ONCE(*nfcla);
> if (r1) {
> smp_store_release(lcl, 0);
> r11 = xchg_acquire(gbl, 1);
> r12 = xchg_acquire(lcl, 1);
> smp_store_release(gbl, 0);
> }
> r2 = READ_ONCE(*gbl_held);
> WRITE_ONCE(*lcl_held, 1);
> WRITE_ONCE(*lcl_held, 0);
> smp_store_release(lcl, 0);
> }
>
> P1(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
> {
> /* Acquire global lock. */
> r10 = xchg_acquire(gbl, 1);
> WRITE_ONCE(*nfcla, 1);
> r11 = xchg_acquire(lcl, 1);
> smp_store_release(lcl, 0);
> r2 = READ_ONCE(*lcl_held);
> WRITE_ONCE(*gbl_held, 1);
> WRITE_ONCE(*gbl_held, 0);
Where is the write that resets nfcla=0?
> smp_store_release(gbl, 0);
> }
>
> exists
> ((0:r2=1 \/ 1:r2=1) /\ 0:r10=0 /\ 0:r11=0 /\ 0:r12=0 /\ 1:r10=0 /\ 1:r11=0)
>
> ------------------------------------------------------------------------
>
> The memory model says that the forbidden state does not happen:
[...]
> Manfred, any objections to my changing your patch as Alan suggests?
I tried to pair the memory barriers:
nf_conntrack_all_unlock() contains a smp_store_release().
What does that pair with?
--
Manfred
[toc] | [prev] | [next] | [standalone]
| From | Alan Stern <stern@rowland.harvard.edu> |
|---|---|
| Date | 2017-07-03 22:00 +0200 |
| Message-ID | <tZfFL-6aZ-7@gated-at.bofh.it> |
| In reply to | #1680541 |
On Mon, 3 Jul 2017, Manfred Spraul wrote: > >>> + /* 2) read nf_conntrack_locks_all, with ACQUIRE semantics */ > >>> + if (likely(smp_load_acquire(&nf_conntrack_locks_all) == false)) > >>> + return; > >> As far as I can tell, this read does not need to have ACQUIRE > >> semantics. > >> > >> You need to guarantee that two things can never happen: > >> > >> (1) We read nf_conntrack_locks_all == false, and this routine's > >> critical section for nf_conntrack_locks[i] runs after the > >> (empty) critical section for that lock in > >> nf_conntrack_all_lock(). > >> > >> (2) We read nf_conntrack_locks_all == true, and this routine's > >> critical section for nf_conntrack_locks_all_lock runs before > >> the critical section in nf_conntrack_all_lock(). > I was looking at nf_conntrack_all_unlock: > There is a smp_store_release() - which memory barrier does this pair with? > > nf_conntrack_all_unlock() > <arbitrary writes> > smp_store_release(a, false) > spin_unlock(b); > > nf_conntrack_lock() > spin_lock(c); > xx=read_once(a) > if (xx==false) > return > <arbitrary read> Ah, I see your point. Yes, I did wonder about what would happen when nf_conntrack_locks_all was set back to false. But I didn't think about it any further, because the relevant code wasn't in your patch. > I tried to pair the memory barriers: > nf_conntrack_all_unlock() contains a smp_store_release(). > What does that pair with? You are right, this does need to be smp_load_acquire() after all. Perhaps the preceding comment should mention that it pairs with the smp_store_release() from an earlier invocation of nf_conntrack_all_unlock(). (Alternatively, you could make nf_conntrack_all_unlock() do a lock+unlock on all the locks in the array, just like nf_conntrack_all_lock(). But of course, that would be a lot less efficient.) Alan Stern
[toc] | [prev] | [next] | [standalone]
| From | Manfred Spraul <manfred@colorfullife.com> |
|---|---|
| Date | 2017-07-06 20:50 +0200 |
| Message-ID | <u0k0G-Om-39@gated-at.bofh.it> |
| In reply to | #1680553 |
[Multipart message — attachments visible in raw view] — view raw
Hi Alan,
On 07/03/2017 09:57 PM, Alan Stern wrote:
>
> (Alternatively, you could make nf_conntrack_all_unlock() do a
> lock+unlock on all the locks in the array, just like
> nf_conntrack_all_lock(). But of course, that would be a lot less
> efficient.)
Hmmmm.
Someone with a weakly ordered system who can test this?
semop() has a very short hotpath.
Either with aim9.shared_memory.ops_per_sec or
#sem-scalebench -t 10 -m 0
https://github.com/manfred-colorfu/ipcscale/blob/master/sem-scalebench.cpp
--
Manfred
[toc] | [prev] | [next] | [standalone]
| From | Alan Stern <stern@rowland.harvard.edu> |
|---|---|
| Date | 2017-07-03 22:10 +0200 |
| Message-ID | <tZfPs-6uN-27@gated-at.bofh.it> |
| In reply to | #1680509 |
On Mon, 3 Jul 2017, Paul E. McKenney wrote:
> On Mon, Jul 03, 2017 at 10:39:49AM -0400, Alan Stern wrote:
> > On Sat, 1 Jul 2017, Manfred Spraul wrote:
> >
> > > As we want to remove spin_unlock_wait() and replace it with explicit
> > > spin_lock()/spin_unlock() calls, we can use this to simplify the
> > > locking.
> > >
> > > In addition:
> > > - Reading nf_conntrack_locks_all needs ACQUIRE memory ordering.
> > > - The new code avoids the backwards loop.
> > >
> > > Only slightly tested, I did not manage to trigger calls to
> > > nf_conntrack_all_lock().
> > >
> > > Fixes: b16c29191dc8
> > > Signed-off-by: Manfred Spraul <manfred@colorfullife.com>
> > > Cc: <stable@vger.kernel.org>
> > > Cc: Sasha Levin <sasha.levin@oracle.com>
> > > Cc: Pablo Neira Ayuso <pablo@netfilter.org>
> > > Cc: netfilter-devel@vger.kernel.org
> > > ---
> > > net/netfilter/nf_conntrack_core.c | 44 +++++++++++++++++++++------------------
> > > 1 file changed, 24 insertions(+), 20 deletions(-)
> > >
> > > diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
> > > index e847dba..1193565 100644
> > > --- a/net/netfilter/nf_conntrack_core.c
> > > +++ b/net/netfilter/nf_conntrack_core.c
> > > @@ -96,19 +96,24 @@ static struct conntrack_gc_work conntrack_gc_work;
> > >
> > > void nf_conntrack_lock(spinlock_t *lock) __acquires(lock)
> > > {
> > > + /* 1) Acquire the lock */
> > > spin_lock(lock);
> > > - while (unlikely(nf_conntrack_locks_all)) {
> > > - spin_unlock(lock);
> > >
> > > - /*
> > > - * Order the 'nf_conntrack_locks_all' load vs. the
> > > - * spin_unlock_wait() loads below, to ensure
> > > - * that 'nf_conntrack_locks_all_lock' is indeed held:
> > > - */
> > > - smp_rmb(); /* spin_lock(&nf_conntrack_locks_all_lock) */
> > > - spin_unlock_wait(&nf_conntrack_locks_all_lock);
> > > - spin_lock(lock);
> > > - }
> > > + /* 2) read nf_conntrack_locks_all, with ACQUIRE semantics */
> > > + if (likely(smp_load_acquire(&nf_conntrack_locks_all) == false))
> > > + return;
> >
> > As far as I can tell, this read does not need to have ACQUIRE
> > semantics.
> >
> > You need to guarantee that two things can never happen:
> >
> > (1) We read nf_conntrack_locks_all == false, and this routine's
> > critical section for nf_conntrack_locks[i] runs after the
> > (empty) critical section for that lock in
> > nf_conntrack_all_lock().
> >
> > (2) We read nf_conntrack_locks_all == true, and this routine's
> > critical section for nf_conntrack_locks_all_lock runs before
> > the critical section in nf_conntrack_all_lock().
> >
> > In fact, neither one can happen even if smp_load_acquire() is replaced
> > with READ_ONCE(). The reason is simple enough, using this property of
> > spinlocks:
> >
> > If critical section CS1 runs before critical section CS2 (for
> > the same lock) then: (a) every write coming before CS1's
> > spin_unlock() will be visible to any read coming after CS2's
> > spin_lock(), and (b) no write coming after CS2's spin_lock()
> > will be visible to any read coming before CS1's spin_unlock().
> >
> > Thus for (1), assuming the critical sections run in the order mentioned
> > above, since nf_conntrack_all_lock() writes to nf_conntrack_locks_all
> > before releasing nf_conntrack_locks[i], and since nf_conntrack_lock()
> > acquires nf_conntrack_locks[i] before reading nf_conntrack_locks_all,
> > by (a) the read will always see the write.
> >
> > Similarly for (2), since nf_conntrack_all_lock() acquires
> > nf_conntrack_locks_all_lock before writing to nf_conntrack_locks_all,
> > and since nf_conntrack_lock() reads nf_conntrack_locks_all before
> > releasing nf_conntrack_locks_all_lock, by (b) the read cannot see the
> > write.
>
> And the Linux kernel memory model (https://lwn.net/Articles/718628/
> and https://lwn.net/Articles/720550/) agrees with Alan. Here is
> a litmus test, which emulates spin_lock() with xchg_acquire() and
> spin_unlock() with smp_store_release():
>
> ------------------------------------------------------------------------
>
> C C-ManfredSpraul-L1G1xchgnr.litmus
>
> (* Expected result: Never. *)
>
> {
> }
>
> P0(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
> {
> /* Acquire local lock. */
> r10 = xchg_acquire(lcl, 1);
> r1 = READ_ONCE(*nfcla);
> if (r1) {
> smp_store_release(lcl, 0);
> r11 = xchg_acquire(gbl, 1);
> r12 = xchg_acquire(lcl, 1);
> smp_store_release(gbl, 0);
> }
> r2 = READ_ONCE(*gbl_held);
> WRITE_ONCE(*lcl_held, 1);
> WRITE_ONCE(*lcl_held, 0);
> smp_store_release(lcl, 0);
> }
>
> P1(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
> {
> /* Acquire global lock. */
> r10 = xchg_acquire(gbl, 1);
> WRITE_ONCE(*nfcla, 1);
> r11 = xchg_acquire(lcl, 1);
> smp_store_release(lcl, 0);
> r2 = READ_ONCE(*lcl_held);
> WRITE_ONCE(*gbl_held, 1);
This litmus test is incomplete, because it omits the assignment setting
nf_conntrack_locks_all back to false when the global lock is released.
You should insert
smp_store_release(*nfcla, 0);
right here.
> WRITE_ONCE(*gbl_held, 0);
> smp_store_release(gbl, 0);
> }
>
> exists
> ((0:r2=1 \/ 1:r2=1) /\ 0:r10=0 /\ 0:r11=0 /\ 0:r12=0 /\ 1:r10=0 /\ 1:r11=0)
With that addition, the litmus test fails unless the read of nfcla in
P0 is an smp_load_acquire. So Manfred's patch should not be changed.
Alan Stern
[toc] | [prev] | [next] | [standalone]
| From | "Paul E. McKenney" <paulmck@linux.vnet.ibm.com> |
|---|---|
| Date | 2017-07-03 23:00 +0200 |
| Message-ID | <tZgBR-6MZ-33@gated-at.bofh.it> |
| In reply to | #1680557 |
On Mon, Jul 03, 2017 at 04:04:14PM -0400, Alan Stern wrote:
> On Mon, 3 Jul 2017, Paul E. McKenney wrote:
>
> > On Mon, Jul 03, 2017 at 10:39:49AM -0400, Alan Stern wrote:
> > > On Sat, 1 Jul 2017, Manfred Spraul wrote:
> > >
> > > > As we want to remove spin_unlock_wait() and replace it with explicit
> > > > spin_lock()/spin_unlock() calls, we can use this to simplify the
> > > > locking.
> > > >
> > > > In addition:
> > > > - Reading nf_conntrack_locks_all needs ACQUIRE memory ordering.
> > > > - The new code avoids the backwards loop.
> > > >
> > > > Only slightly tested, I did not manage to trigger calls to
> > > > nf_conntrack_all_lock().
> > > >
> > > > Fixes: b16c29191dc8
> > > > Signed-off-by: Manfred Spraul <manfred@colorfullife.com>
> > > > Cc: <stable@vger.kernel.org>
> > > > Cc: Sasha Levin <sasha.levin@oracle.com>
> > > > Cc: Pablo Neira Ayuso <pablo@netfilter.org>
> > > > Cc: netfilter-devel@vger.kernel.org
> > > > ---
> > > > net/netfilter/nf_conntrack_core.c | 44 +++++++++++++++++++++------------------
> > > > 1 file changed, 24 insertions(+), 20 deletions(-)
> > > >
> > > > diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
> > > > index e847dba..1193565 100644
> > > > --- a/net/netfilter/nf_conntrack_core.c
> > > > +++ b/net/netfilter/nf_conntrack_core.c
> > > > @@ -96,19 +96,24 @@ static struct conntrack_gc_work conntrack_gc_work;
> > > >
> > > > void nf_conntrack_lock(spinlock_t *lock) __acquires(lock)
> > > > {
> > > > + /* 1) Acquire the lock */
> > > > spin_lock(lock);
> > > > - while (unlikely(nf_conntrack_locks_all)) {
> > > > - spin_unlock(lock);
> > > >
> > > > - /*
> > > > - * Order the 'nf_conntrack_locks_all' load vs. the
> > > > - * spin_unlock_wait() loads below, to ensure
> > > > - * that 'nf_conntrack_locks_all_lock' is indeed held:
> > > > - */
> > > > - smp_rmb(); /* spin_lock(&nf_conntrack_locks_all_lock) */
> > > > - spin_unlock_wait(&nf_conntrack_locks_all_lock);
> > > > - spin_lock(lock);
> > > > - }
> > > > + /* 2) read nf_conntrack_locks_all, with ACQUIRE semantics */
> > > > + if (likely(smp_load_acquire(&nf_conntrack_locks_all) == false))
> > > > + return;
> > >
> > > As far as I can tell, this read does not need to have ACQUIRE
> > > semantics.
> > >
> > > You need to guarantee that two things can never happen:
> > >
> > > (1) We read nf_conntrack_locks_all == false, and this routine's
> > > critical section for nf_conntrack_locks[i] runs after the
> > > (empty) critical section for that lock in
> > > nf_conntrack_all_lock().
> > >
> > > (2) We read nf_conntrack_locks_all == true, and this routine's
> > > critical section for nf_conntrack_locks_all_lock runs before
> > > the critical section in nf_conntrack_all_lock().
> > >
> > > In fact, neither one can happen even if smp_load_acquire() is replaced
> > > with READ_ONCE(). The reason is simple enough, using this property of
> > > spinlocks:
> > >
> > > If critical section CS1 runs before critical section CS2 (for
> > > the same lock) then: (a) every write coming before CS1's
> > > spin_unlock() will be visible to any read coming after CS2's
> > > spin_lock(), and (b) no write coming after CS2's spin_lock()
> > > will be visible to any read coming before CS1's spin_unlock().
> > >
> > > Thus for (1), assuming the critical sections run in the order mentioned
> > > above, since nf_conntrack_all_lock() writes to nf_conntrack_locks_all
> > > before releasing nf_conntrack_locks[i], and since nf_conntrack_lock()
> > > acquires nf_conntrack_locks[i] before reading nf_conntrack_locks_all,
> > > by (a) the read will always see the write.
> > >
> > > Similarly for (2), since nf_conntrack_all_lock() acquires
> > > nf_conntrack_locks_all_lock before writing to nf_conntrack_locks_all,
> > > and since nf_conntrack_lock() reads nf_conntrack_locks_all before
> > > releasing nf_conntrack_locks_all_lock, by (b) the read cannot see the
> > > write.
> >
> > And the Linux kernel memory model (https://lwn.net/Articles/718628/
> > and https://lwn.net/Articles/720550/) agrees with Alan. Here is
> > a litmus test, which emulates spin_lock() with xchg_acquire() and
> > spin_unlock() with smp_store_release():
> >
> > ------------------------------------------------------------------------
> >
> > C C-ManfredSpraul-L1G1xchgnr.litmus
> >
> > (* Expected result: Never. *)
> >
> > {
> > }
> >
> > P0(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
> > {
> > /* Acquire local lock. */
> > r10 = xchg_acquire(lcl, 1);
> > r1 = READ_ONCE(*nfcla);
> > if (r1) {
> > smp_store_release(lcl, 0);
> > r11 = xchg_acquire(gbl, 1);
> > r12 = xchg_acquire(lcl, 1);
> > smp_store_release(gbl, 0);
> > }
> > r2 = READ_ONCE(*gbl_held);
> > WRITE_ONCE(*lcl_held, 1);
> > WRITE_ONCE(*lcl_held, 0);
> > smp_store_release(lcl, 0);
> > }
> >
> > P1(int *nfcla, spinlock_t *gbl, int *gbl_held, spinlock_t *lcl, int *lcl_held)
> > {
> > /* Acquire global lock. */
> > r10 = xchg_acquire(gbl, 1);
> > WRITE_ONCE(*nfcla, 1);
> > r11 = xchg_acquire(lcl, 1);
> > smp_store_release(lcl, 0);
> > r2 = READ_ONCE(*lcl_held);
> > WRITE_ONCE(*gbl_held, 1);
>
> This litmus test is incomplete, because it omits the assignment setting
> nf_conntrack_locks_all back to false when the global lock is released.
> You should insert
>
> smp_store_release(*nfcla, 0);
>
> right here.
>
> > WRITE_ONCE(*gbl_held, 0);
> > smp_store_release(gbl, 0);
> > }
> >
> > exists
> > ((0:r2=1 \/ 1:r2=1) /\ 0:r10=0 /\ 0:r11=0 /\ 0:r12=0 /\ 1:r10=0 /\ 1:r11=0)
>
> With that addition, the litmus test fails unless the read of nfcla in
> P0 is an smp_load_acquire. So Manfred's patch should not be changed.
Very good! Aside from updating litmus tests, my work is done! ;-)
Thanx, Paul
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web