kernel/locking: qspinlock improvements
v3 cover kernel/locking: qspinlock improvements
Hi all,
This is version three of the qspinlock patches I posted previously:
v1: https://lkml.org/lkml/2018/4/5/496
v2: https://lkml.org/lkml/2018/4/11/618
Changes since v2 include:
* Fixed bisection issues
* Fixed x86 PV build
* Added patch proposing me as a co-maintainer
* Rebased onto -rc2
All feedback welcome,
Will
--->8
Jason Low (1):
locking/mcs: Use smp_cond_load_acquire() in mcs spin loop
Waiman Long (1):
locking/qspinlock: Add stat tracking for pending vs slowpath
Will Deacon (12):
barriers: Introduce smp_cond_load_relaxed and atomic_cond_read_relaxed locking/qspinlock: Merge struct __qspinlock into struct qspinlock locking/qspinlock: Bound spinning on pending->locked transition in
slowpath
locking/qspinlock/x86: Increase _Q_PENDING_LOOPS upper bound locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath locking/qspinlock: Kill cmpxchg loop when claiming lock from head of
queue
locking/qspinlock: Use atomic_cond_read_acquire locking/qspinlock: Use smp_cond_load_relaxed to wait for next node locking/qspinlock: Make queued_spin_unlock use smp_store_release locking/qspinlock: Elide back-to-back RELEASE operations with
smp_wmb()
locking/qspinlock: Use try_cmpxchg instead of cmpxchg when locking
MAINTAINERS: Add myself as a co-maintainer for LOCKING PRIMITIVES
MAINTAINERS | 1 + arch/x86/include/asm/qspinlock.h | 21 ++- arch/x86/include/asm/qspinlock_paravirt.h | 3 +- include/asm-generic/atomic-long.h | 2 + include/asm-generic/barrier.h | 27 +++- include/asm-generic/qspinlock.h | 2 +- include/asm-generic/qspinlock_types.h | 32 +++- include/linux/atomic.h | 2 + kernel/locking/mcs_spinlock.h | 10 +- kernel/locking/qspinlock.c | 247 ++++++++++++++---------------- kernel/locking/qspinlock_paravirt.h | 44 ++---- kernel/locking/qspinlock_stat.h | 9 +- 12 files changed, 209 insertions(+), 191 deletions(-)
Signature · 1 line
2.1.4
PATCH v3 1/14 barriers: Introduce smp_cond_load_relaxed and atomic_cond_read_relaxed
Whilst we currently provide smp_cond_load_acquire and atomic_cond_read_acquire, there are cases where the ACQUIRE semantics are not required because of a subsequent fence or release operation once the conditional loop has exited.
This patch adds relaxed versions of the conditional spinning primitives to avoid unnecessary barrier overhead on architectures such as arm64.
--- include/asm-generic/atomic-long.h | 2 ++ include/asm-generic/barrier.h | 27 +++++++++++++++++++++------ include/linux/atomic.h | 2 ++ 3 files changed, 25 insertions(+), 6 deletions(-)
include/asm-generic/atomic-long.h +2 -0
@@ -244,6 +244,8 @@ static inline long atomic_long_add_unless(atomic_long_t *l, long a, long u)#define atomic_long_inc_not_zero (l) \ ATOMIC_LONG_PFX(_inc_not_zero)((ATOMIC_LONG_PFX(_t) *)(l)) +#define atomic_long_cond_read_relaxed (v, c) \ + ATOMIC_LONG_PFX(_cond_read_relaxed)((ATOMIC_LONG_PFX(_t) *)(v), (c))#define atomic_long_cond_read_acquire (v, c) \ ATOMIC_LONG_PFX(_cond_read_acquire)((ATOMIC_LONG_PFX(_t) *)(v), (c))
include/asm-generic/barrier.h +21 -6
@@ -221,18 +221,17 @@ do { \#endif /** -* smp_cond_load_acquire() - (Spin) wait for cond with ACQUIRE ordering +* smp_cond_load_relaxed() - (Spin) wait for cond with no ordering guarantees * @ptr: pointer to the variable to wait on * @cond: boolean expression to wait for * -* Equivalent to using smp_load_acquire() on the condition variable but employs -* the control dependency of the wait to reduce the barrier on many platforms. +* Equivalent to using READ_ONCE() on the condition variable. * * Due to C lacking lambda expressions we load the value of *ptr into a * pre-named variable @VAL to be used in @cond. */ -#ifndef smp_cond_load_acquire -#define smp_cond_load_acquire (ptr, cond_expr) ({ \ +#ifndef smp_cond_load_relaxed +#define smp_cond_load_relaxed (ptr, cond_expr) ({ \ typeof(ptr) __PTR = (ptr); \ typeof(*ptr) VAL; \ for (;;) { \
@@ -241,10 +240,26 @@ do { \break ; \cpu_relax (); \ } \ -smp_acquire__after_ctrl_dep (); \VAL ; \ })#endif +/** +* smp_cond_load_acquire() - (Spin) wait for cond with ACQUIRE ordering +* @ptr: pointer to the variable to wait on +* @cond: boolean expression to wait for +* +* Equivalent to using smp_load_acquire() on the condition variable but employs +* the control dependency of the wait to reduce the barrier on many platforms. +*/ +#ifndef smp_cond_load_acquire +#define smp_cond_load_acquire (ptr, cond_expr) ({ \ + typeof(*ptr) _val; \ + _val = smp_cond_load_relaxed(ptr, cond_expr); \ + smp_acquire__after_ctrl_dep(); \ + _val; \ +}) +#endif +#endif /* !__ASSEMBLY__ */ #endif /* __ASM_GENERIC_BARRIER_H */
include/linux/atomic.h +2 -0
@@ -654,6 +654,7 @@ static inline int atomic_dec_if_positive(atomic_t *v) }#endif +#define atomic_cond_read_relaxed (v, c) smp_cond_load_relaxed(&(v)->counter, (c))#define atomic_cond_read_acquire (v, c) smp_cond_load_acquire(&(v)->counter, (c))#ifdef CONFIG_GENERIC_ATOMIC64
@@ -1075,6 +1076,7 @@ static inline long long atomic64_fetch_andnot_release(long long i, atomic64_t *v }#endif +#define atomic64_cond_read_relaxed (v, c) smp_cond_load_relaxed(&(v)->counter, (c))#define atomic64_cond_read_acquire (v, c) smp_cond_load_acquire(&(v)->counter, (c))#include <asm-generic/atomic-long.h>
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/barriers: Introduce smp_cond_load_relaxed() and atomic_cond_read_relaxed()
locking/barriers: Introduce smp_cond_load_relaxed() and atomic_cond_read_relaxed()
Commit-ID: fcfdfe30e324725007e9dc5814b62a4c430ea909 Gitweb: https://git.kernel.org/tip/fcfdfe30e324725007e9dc5814b62a4c430ea909 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:15 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:44 +0200
locking/barriers: Introduce smp_cond_load_relaxed() and atomic_cond_read_relaxed()
Whilst we currently provide smp_cond_load_acquire() and atomic_cond_read_acquire(), there are cases where the ACQUIRE semantics are not required because of a subsequent fence or release operation once the conditional loop has exited.
This patch adds relaxed versions of the conditional spinning primitives to avoid unnecessary barrier overhead on architectures such as arm64.
--- include/asm-generic/atomic-long.h | 2 ++ include/asm-generic/barrier.h | 27 +++++++++++++++++++++------ include/linux/atomic.h | 2 ++ 3 files changed, 25 insertions(+), 6 deletions(-)
include/asm-generic/atomic-long.h +2 -0
@@ -244,6 +244,8 @@ static inline long atomic_long_add_unless(atomic_long_t *l, long a, long u)#define atomic_long_inc_not_zero (l) \ ATOMIC_LONG_PFX(_inc_not_zero)((ATOMIC_LONG_PFX(_t) *)(l)) +#define atomic_long_cond_read_relaxed (v, c) \ + ATOMIC_LONG_PFX(_cond_read_relaxed)((ATOMIC_LONG_PFX(_t) *)(v), (c))#define atomic_long_cond_read_acquire (v, c) \ ATOMIC_LONG_PFX(_cond_read_acquire)((ATOMIC_LONG_PFX(_t) *)(v), (c))
include/asm-generic/barrier.h +21 -6
@@ -221,18 +221,17 @@ do { \#endif /** -* smp_cond_load_acquire() - (Spin) wait for cond with ACQUIRE ordering +* smp_cond_load_relaxed() - (Spin) wait for cond with no ordering guarantees * @ptr: pointer to the variable to wait on * @cond: boolean expression to wait for * -* Equivalent to using smp_load_acquire() on the condition variable but employs -* the control dependency of the wait to reduce the barrier on many platforms. +* Equivalent to using READ_ONCE() on the condition variable. * * Due to C lacking lambda expressions we load the value of *ptr into a * pre-named variable @VAL to be used in @cond. */ -#ifndef smp_cond_load_acquire -#define smp_cond_load_acquire (ptr, cond_expr) ({ \ +#ifndef smp_cond_load_relaxed +#define smp_cond_load_relaxed (ptr, cond_expr) ({ \ typeof(ptr) __PTR = (ptr); \ typeof(*ptr) VAL; \ for (;;) { \
@@ -241,10 +240,26 @@ do { \break ; \cpu_relax (); \ } \ -smp_acquire__after_ctrl_dep (); \VAL ; \ })#endif +/** +* smp_cond_load_acquire() - (Spin) wait for cond with ACQUIRE ordering +* @ptr: pointer to the variable to wait on +* @cond: boolean expression to wait for +* +* Equivalent to using smp_load_acquire() on the condition variable but employs +* the control dependency of the wait to reduce the barrier on many platforms. +*/ +#ifndef smp_cond_load_acquire +#define smp_cond_load_acquire (ptr, cond_expr) ({ \ + typeof(*ptr) _val; \ + _val = smp_cond_load_relaxed(ptr, cond_expr); \ + smp_acquire__after_ctrl_dep(); \ + _val; \ +}) +#endif +#endif /* !__ASSEMBLY__ */ #endif /* __ASM_GENERIC_BARRIER_H */
include/linux/atomic.h +2 -0
@@ -654,6 +654,7 @@ static inline int atomic_dec_if_positive(atomic_t *v) }#endif +#define atomic_cond_read_relaxed (v, c) smp_cond_load_relaxed(&(v)->counter, (c))#define atomic_cond_read_acquire (v, c) smp_cond_load_acquire(&(v)->counter, (c))#ifdef CONFIG_GENERIC_ATOMIC64
@@ -1075,6 +1076,7 @@ static inline long long atomic64_fetch_andnot_release(long long i, atomic64_t *v }#endif +#define atomic64_cond_read_relaxed (v, c) smp_cond_load_relaxed(&(v)->counter, (c))#define atomic64_cond_read_acquire (v, c) smp_cond_load_acquire(&(v)->counter, (c))#include <asm-generic/atomic-long.h>
PATCH v3 2/14 locking/qspinlock: Merge struct __qspinlock into struct qspinlock
In reply to Will Deaconstruct __qspinlock provides a handy union of fields so that subcomponents of the lockword can be accessed by name, without having to manage shifts and masks explicitly and take endianness into account.
This is useful in qspinlock.h and also potentially in arch headers, so move the struct __qspinlock into struct qspinlock and kill the extra definition.
--- arch/x86/include/asm/qspinlock.h | 2 +- arch/x86/include/asm/qspinlock_paravirt.h | 3 +- include/asm-generic/qspinlock_types.h | 32 +++++++++++++++++++-- kernel/locking/qspinlock.c | 46 ++----------------------------- kernel/locking/qspinlock_paravirt.h | 34 ++++++++--------------- 5 files changed, 46 insertions(+), 71 deletions(-)
arch/x86/include/asm/qspinlock.h +1 -1
@@ -16,7 +16,7 @@*/ static inline void native_queued_spin_unlock (struct qspinlock *lock) { -smp_store_release ((u8 *)lock,0 ); +smp_store_release (&lock->locked,0 ); }#ifdef CONFIG_PARAVIRT_SPINLOCKS
arch/x86/include/asm/qspinlock_paravirt.h +1 -2
@@ -22,8 +22,7 @@ PV_CALLEE_SAVE_REGS_THUNK(__pv_queued_spin_unlock_slowpath);* * void __pv_queued_spin_unlock(struct qspinlock *lock) * { -* struct __qspinlock *l = (void *)lock; -* u8 lockval = cmpxchg(&l->locked, _Q_LOCKED_VAL, 0); +* u8 lockval = cmpxchg(&lock->locked, _Q_LOCKED_VAL, 0); * * if (likely(lockval == _Q_LOCKED_VAL)) * return;
include/asm-generic/qspinlock_types.h +30 -2
@@ -29,13 +29,41 @@#endif typedef struct qspinlock { -atomic_t val; +union { +atomic_t val; + +/* +* By using the whole 2nd least significant byte for the +* pending bit, we can allow better optimization of the lock +* acquisition for the pending bit holder. +*/ +#ifdef __LITTLE_ENDIAN +struct { +u8 locked; +u8 pending; + }; +struct { +u16 locked_pending; +u16 tail; + }; +#else +struct { +u16 tail; +u16 locked_pending; + }; +struct { +u8 reserved[2 ]; +u8 pending; +u8 locked; + }; +#endif + }; }arch_spinlock_t ;/* * Initializier */ -#define __ARCH_SPIN_LOCK_UNLOCKED { ATOMIC_INIT(0) } +#define __ARCH_SPIN_LOCK_UNLOCKED { .val = ATOMIC_INIT(0) }/* * Bitfields in the atomic value:
kernel/locking/qspinlock.c +3 -43
74 lines not on this page · raw message
kernel/locking/qspinlock_paravirt.h +11 -23
@@ -87,8 +87,6 @@ struct pv_node {#define queued_spin_trylock (l) pv_hybrid_queued_unfair_trylock(l)static inline bool pv_hybrid_queued_unfair_trylock (struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; -/* * Stay in unfair lock mode as long as queued mode waiters are * present in the MCS wait queue but the pending bit isn't set.
@@ -97,7 +95,7 @@ static inline bool pv_hybrid_queued_unfair_trylock(struct qspinlock *lock)int val =atomic_read (&lock->val);if (!(val & _Q_LOCKED_PENDING_MASK) && - (cmpxchg_acquire (&l->locked,0 , _Q_LOCKED_VAL) ==0 )) { + (cmpxchg_acquire (&lock->locked,0 , _Q_LOCKED_VAL) ==0 )) {qstat_inc (qstat_pv_lock_stealing, true);return true; }
@@ -117,16 +115,12 @@ static inline bool pv_hybrid_queued_unfair_trylock(struct qspinlock *lock)#if _Q_PENDING_BITS ==8 static __always_inline void set_pending(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; - -WRITE_ONCE (l->pending,1 ); +WRITE_ONCE (lock->pending,1 ); } static __always_inline void clear_pending(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; - -WRITE_ONCE (l->pending,0 ); +WRITE_ONCE (lock->pending,0 ); }/*
@@ -136,10 +130,8 @@ static __always_inline void clear_pending(struct qspinlock *lock)*/ static __always_inline int trylock_clear_pending(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; - -return !READ_ONCE (l->locked) && - (cmpxchg_acquire (&l->locked_pending, _Q_PENDING_VAL, +return !READ_ONCE (lock->locked) && + (cmpxchg_acquire (&lock->locked_pending, _Q_PENDING_VAL, _Q_LOCKED_VAL) == _Q_PENDING_VAL); }#else /* _Q_PENDING_BITS == 8 */
@@ -384,7 +376,6 @@ static void pv_wait_node(struct mcs_spinlock *node, struct mcs_spinlock *prev)static void pv_kick_node (struct qspinlock *lock,struct mcs_spinlock *node) {struct pv_node *pn = (struct pv_node *)node; -struct __qspinlock *l = (void *)lock;/* * If the vCPU is indeed halted, advance its state to match that of
@@ -413,7 +404,7 @@ static void pv_kick_node(struct qspinlock *lock, struct mcs_spinlock *node)* the hash table later on at unlock time, no atomic instruction is * needed. */ -WRITE_ONCE (l->locked, _Q_SLOW_VAL); +WRITE_ONCE (lock->locked, _Q_SLOW_VAL); (void )pv_hash (lock, pn); }
@@ -428,7 +419,6 @@ static u32 pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node) { struct pv_node *pn = (struct pv_node *)node; -struct __qspinlock *l = (void *)lock;struct qspinlock **lp =NULL ;int waitcnt =0 ;int loop;
@@ -479,13 +469,13 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node)* * Matches the smp_rmb() in __pv_queued_spin_unlock(). */ -if (xchg (&l->locked, _Q_SLOW_VAL) ==0 ) { +if (xchg (&lock->locked, _Q_SLOW_VAL) ==0 ) {/* * The lock was free and now we own the lock. * Change the lock value back to _Q_LOCKED_VAL * and unhash the table. */ -WRITE_ONCE (l->locked, _Q_LOCKED_VAL); +WRITE_ONCE (lock->locked, _Q_LOCKED_VAL);WRITE_ONCE (*lp,NULL ); goto gotlock; }
@@ -493,7 +483,7 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node)WRITE_ONCE (pn->state, vcpu_hashed);qstat_inc (qstat_pv_wait_head, true);qstat_inc (qstat_pv_wait_again, waitcnt); -pv_wait (&l->locked, _Q_SLOW_VAL); +pv_wait (&lock->locked, _Q_SLOW_VAL);/* * Because of lock stealing, the queue head vCPU may not be
@@ -518,7 +508,6 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node) __visible void__pv_queued_spin_unlock_slowpath (struct qspinlock *lock,u8 locked) { -struct __qspinlock *l = (void *)lock;struct pv_node *node;if (unlikely (locked != _Q_SLOW_VAL)) {
@@ -547,7 +536,7 @@ __pv_queued_spin_unlock_slowpath(struct qspinlock *lock, u8 locked)* Now that we have a reference to the (likely) blocked pv_node, * release the lock. */ -smp_store_release (&l->locked,0 ); +smp_store_release (&lock->locked,0 );/* * At this point the memory pointed at by lock can be freed/reused,
@@ -573,7 +562,6 @@ __pv_queued_spin_unlock_slowpath(struct qspinlock *lock, u8 locked)#ifndef __pv_queued_spin_unlock __visible void __pv_queued_spin_unlock(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock;u8 locked;/*
@@ -581,7 +569,7 @@ __visible void __pv_queued_spin_unlock(struct qspinlock *lock)* unhash. Otherwise it would be possible to have multiple @lock * entries, which would be BAD. */ - locked =cmpxchg_release (&l->locked, _Q_LOCKED_VAL,0 ); + locked =cmpxchg_release (&lock->locked, _Q_LOCKED_VAL,0 );if (likely (locked == _Q_LOCKED_VAL))return ;
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Merge 'struct __qspinlock' into 'struct qspinlock'
locking/qspinlock: Merge 'struct __qspinlock' into 'struct qspinlock'
Commit-ID: 625e88be1f41b53cec55827c984e4a89ea8ee9f9 Gitweb: https://git.kernel.org/tip/625e88be1f41b53cec55827c984e4a89ea8ee9f9 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:16 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:45 +0200
locking/qspinlock: Merge 'struct __qspinlock' into 'struct qspinlock'
'struct __qspinlock' provides a handy union of fields so that subcomponents of the lockword can be accessed by name, without having to manage shifts and masks explicitly and take endianness into account.
This is useful in qspinlock.h and also potentially in arch headers, so move the 'struct __qspinlock' into 'struct qspinlock' and kill the extra definition.
--- arch/x86/include/asm/qspinlock.h | 2 +- arch/x86/include/asm/qspinlock_paravirt.h | 3 +- include/asm-generic/qspinlock_types.h | 32 +++++++++++++++++++-- kernel/locking/qspinlock.c | 46 ++----------------------------- kernel/locking/qspinlock_paravirt.h | 34 ++++++++--------------- 5 files changed, 46 insertions(+), 71 deletions(-)
arch/x86/include/asm/qspinlock.h +1 -1
@@ -16,7 +16,7 @@*/ static inline void native_queued_spin_unlock (struct qspinlock *lock) { -smp_store_release ((u8 *)lock,0 ); +smp_store_release (&lock->locked,0 ); }#ifdef CONFIG_PARAVIRT_SPINLOCKS
arch/x86/include/asm/qspinlock_paravirt.h +1 -2
@@ -22,8 +22,7 @@ PV_CALLEE_SAVE_REGS_THUNK(__pv_queued_spin_unlock_slowpath);* * void __pv_queued_spin_unlock(struct qspinlock *lock) * { -* struct __qspinlock *l = (void *)lock; -* u8 lockval = cmpxchg(&l->locked, _Q_LOCKED_VAL, 0); +* u8 lockval = cmpxchg(&lock->locked, _Q_LOCKED_VAL, 0); * * if (likely(lockval == _Q_LOCKED_VAL)) * return;
include/asm-generic/qspinlock_types.h +30 -2
@@ -29,13 +29,41 @@#endif typedef struct qspinlock { -atomic_t val; +union { +atomic_t val; + +/* +* By using the whole 2nd least significant byte for the +* pending bit, we can allow better optimization of the lock +* acquisition for the pending bit holder. +*/ +#ifdef __LITTLE_ENDIAN +struct { +u8 locked; +u8 pending; + }; +struct { +u16 locked_pending; +u16 tail; + }; +#else +struct { +u16 tail; +u16 locked_pending; + }; +struct { +u8 reserved[2 ]; +u8 pending; +u8 locked; + }; +#endif + }; }arch_spinlock_t ;/* * Initializier */ -#define __ARCH_SPIN_LOCK_UNLOCKED { ATOMIC_INIT(0) } +#define __ARCH_SPIN_LOCK_UNLOCKED { .val = ATOMIC_INIT(0) }/* * Bitfields in the atomic value:
kernel/locking/qspinlock.c +3 -43
74 lines not on this page · raw message
kernel/locking/qspinlock_paravirt.h +11 -23
@@ -87,8 +87,6 @@ struct pv_node {#define queued_spin_trylock (l) pv_hybrid_queued_unfair_trylock(l)static inline bool pv_hybrid_queued_unfair_trylock (struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; -/* * Stay in unfair lock mode as long as queued mode waiters are * present in the MCS wait queue but the pending bit isn't set.
@@ -97,7 +95,7 @@ static inline bool pv_hybrid_queued_unfair_trylock(struct qspinlock *lock)int val =atomic_read (&lock->val);if (!(val & _Q_LOCKED_PENDING_MASK) && - (cmpxchg_acquire (&l->locked,0 , _Q_LOCKED_VAL) ==0 )) { + (cmpxchg_acquire (&lock->locked,0 , _Q_LOCKED_VAL) ==0 )) {qstat_inc (qstat_pv_lock_stealing, true);return true; }
@@ -117,16 +115,12 @@ static inline bool pv_hybrid_queued_unfair_trylock(struct qspinlock *lock)#if _Q_PENDING_BITS ==8 static __always_inline void set_pending(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; - -WRITE_ONCE (l->pending,1 ); +WRITE_ONCE (lock->pending,1 ); } static __always_inline void clear_pending(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; - -WRITE_ONCE (l->pending,0 ); +WRITE_ONCE (lock->pending,0 ); }/*
@@ -136,10 +130,8 @@ static __always_inline void clear_pending(struct qspinlock *lock)*/ static __always_inline int trylock_clear_pending(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock; - -return !READ_ONCE (l->locked) && - (cmpxchg_acquire (&l->locked_pending, _Q_PENDING_VAL, +return !READ_ONCE (lock->locked) && + (cmpxchg_acquire (&lock->locked_pending, _Q_PENDING_VAL, _Q_LOCKED_VAL) == _Q_PENDING_VAL); }#else /* _Q_PENDING_BITS == 8 */
@@ -384,7 +376,6 @@ static void pv_wait_node(struct mcs_spinlock *node, struct mcs_spinlock *prev)static void pv_kick_node (struct qspinlock *lock,struct mcs_spinlock *node) {struct pv_node *pn = (struct pv_node *)node; -struct __qspinlock *l = (void *)lock;/* * If the vCPU is indeed halted, advance its state to match that of
@@ -413,7 +404,7 @@ static void pv_kick_node(struct qspinlock *lock, struct mcs_spinlock *node)* the hash table later on at unlock time, no atomic instruction is * needed. */ -WRITE_ONCE (l->locked, _Q_SLOW_VAL); +WRITE_ONCE (lock->locked, _Q_SLOW_VAL); (void )pv_hash (lock, pn); }
@@ -428,7 +419,6 @@ static u32 pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node) { struct pv_node *pn = (struct pv_node *)node; -struct __qspinlock *l = (void *)lock;struct qspinlock **lp =NULL ;int waitcnt =0 ;int loop;
@@ -479,13 +469,13 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node)* * Matches the smp_rmb() in __pv_queued_spin_unlock(). */ -if (xchg (&l->locked, _Q_SLOW_VAL) ==0 ) { +if (xchg (&lock->locked, _Q_SLOW_VAL) ==0 ) {/* * The lock was free and now we own the lock. * Change the lock value back to _Q_LOCKED_VAL * and unhash the table. */ -WRITE_ONCE (l->locked, _Q_LOCKED_VAL); +WRITE_ONCE (lock->locked, _Q_LOCKED_VAL);WRITE_ONCE (*lp,NULL ); goto gotlock; }
@@ -493,7 +483,7 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node)WRITE_ONCE (pn->state, vcpu_hashed);qstat_inc (qstat_pv_wait_head, true);qstat_inc (qstat_pv_wait_again, waitcnt); -pv_wait (&l->locked, _Q_SLOW_VAL); +pv_wait (&lock->locked, _Q_SLOW_VAL);/* * Because of lock stealing, the queue head vCPU may not be
@@ -518,7 +508,6 @@ gotlock: __visible void__pv_queued_spin_unlock_slowpath (struct qspinlock *lock,u8 locked) { -struct __qspinlock *l = (void *)lock;struct pv_node *node;if (unlikely (locked != _Q_SLOW_VAL)) {
@@ -547,7 +536,7 @@ __pv_queued_spin_unlock_slowpath(struct qspinlock *lock, u8 locked)* Now that we have a reference to the (likely) blocked pv_node, * release the lock. */ -smp_store_release (&l->locked,0 ); +smp_store_release (&lock->locked,0 );/* * At this point the memory pointed at by lock can be freed/reused,
@@ -573,7 +562,6 @@ __pv_queued_spin_unlock_slowpath(struct qspinlock *lock, u8 locked)#ifndef __pv_queued_spin_unlock __visible void __pv_queued_spin_unlock(struct qspinlock *lock) { -struct __qspinlock *l = (void *)lock;u8 locked;/*
@@ -581,7 +569,7 @@ __visible void __pv_queued_spin_unlock(struct qspinlock *lock)* unhash. Otherwise it would be possible to have multiple @lock * entries, which would be BAD. */ - locked =cmpxchg_release (&l->locked, _Q_LOCKED_VAL,0 ); + locked =cmpxchg_release (&lock->locked, _Q_LOCKED_VAL,0 );if (likely (locked == _Q_LOCKED_VAL))return ;
PATCH v3 3/14 locking/qspinlock: Bound spinning on pending->locked transition in slowpath
In reply to Will DeaconIf a locker taking the qspinlock slowpath reads a lock value indicating that only the pending bit is set, then it will spin whilst the concurrent pending->locked transition takes effect.
Unfortunately, there is no guarantee that such a transition will ever be observed since concurrent lockers could continuously set pending and hand over the lock amongst themselves, leading to starvation. Whilst this would probably resolve in practice, it means that it is not possible to prove liveness properties about the lock and means that lock acquisition time is unbounded.
Rather than removing the pending->locked spinning from the slowpath altogether (which has been shown to heavily penalise a 2-threaded locking stress test on x86), this patch replaces the explicit spinning with a call to atomic_cond_read_relaxed and allows the architecture to provide a bound on the number of spins. For architectures that can respond to changes in cacheline state in their smp_cond_load implementation, it should be sufficient to use the default bound of 1.
--- kernel/locking/qspinlock.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-)
kernel/locking/qspinlock.c +17 -3
@@ -77,6 +77,18 @@#endif /* +* The pending bit spinning loop count. +* This heuristic is used to limit the number of lockword accesses +* made by atomic_cond_read_relaxed when waiting for the lock to +* transition out of the "== _Q_PENDING_VAL" state. We don't spin +* indefinitely because there's no guarantee that we'll make forward +* progress. +*/ +#ifndef _Q_PENDING_LOOPS +#define _Q_PENDING_LOOPS 1 +#endif + +/* * Per-CPU queue node structures; we can never have more than 4 nested * contexts: task, softirq, hardirq, nmi. *
@@ -266,13 +278,15 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)return ;/* -* wait for in-progress pending->locked hand-overs +* Wait for in-progress pending->locked hand-overs with a bounded +* number of spins so that we guarantee forward progress. * * 0,1,0 -> 0,0,1 */ if (val == _Q_PENDING_VAL) { -while ((val =atomic_read (&lock->val)) == _Q_PENDING_VAL) -cpu_relax (); +int cnt = _Q_PENDING_LOOPS; + val =atomic_cond_read_relaxed (&lock->val, + (VAL != _Q_PENDING_VAL) || !cnt--); }/*
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Bound spinning on pending->locked transition in slowpath
locking/qspinlock: Bound spinning on pending->locked transition in slowpath
Commit-ID: 6512276d97b160d90b53285bd06f7f201459a7e3 Gitweb: https://git.kernel.org/tip/6512276d97b160d90b53285bd06f7f201459a7e3 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:17 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:46 +0200
locking/qspinlock: Bound spinning on pending->locked transition in slowpath
If a locker taking the qspinlock slowpath reads a lock value indicating that only the pending bit is set, then it will spin whilst the concurrent pending->locked transition takes effect.
Unfortunately, there is no guarantee that such a transition will ever be observed since concurrent lockers could continuously set pending and hand over the lock amongst themselves, leading to starvation. Whilst this would probably resolve in practice, it means that it is not possible to prove liveness properties about the lock and means that lock acquisition time is unbounded.
Rather than removing the pending->locked spinning from the slowpath altogether (which has been shown to heavily penalise a 2-threaded locking stress test on x86), this patch replaces the explicit spinning with a call to atomic_cond_read_relaxed and allows the architecture to provide a bound on the number of spins. For architectures that can respond to changes in cacheline state in their smp_cond_load implementation, it should be sufficient to use the default bound of 1.
--- kernel/locking/qspinlock.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-)
kernel/locking/qspinlock.c +17 -3
@@ -76,6 +76,18 @@#define MAX_NODES 4#endif +/* +* The pending bit spinning loop count. +* This heuristic is used to limit the number of lockword accesses +* made by atomic_cond_read_relaxed when waiting for the lock to +* transition out of the "== _Q_PENDING_VAL" state. We don't spin +* indefinitely because there's no guarantee that we'll make forward +* progress. +*/ +#ifndef _Q_PENDING_LOOPS +#define _Q_PENDING_LOOPS 1 +#endif +/* * Per-CPU queue node structures; we can never have more than 4 nested * contexts: task, softirq, hardirq, nmi.
@@ -266,13 +278,15 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)return ;/* -* wait for in-progress pending->locked hand-overs +* Wait for in-progress pending->locked hand-overs with a bounded +* number of spins so that we guarantee forward progress. * * 0,1,0 -> 0,0,1 */ if (val == _Q_PENDING_VAL) { -while ((val =atomic_read (&lock->val)) == _Q_PENDING_VAL) -cpu_relax (); +int cnt = _Q_PENDING_LOOPS; + val =atomic_cond_read_relaxed (&lock->val, + (VAL != _Q_PENDING_VAL) || !cnt--); }/*
PATCH v3 4/14 locking/qspinlock/x86: Increase _Q_PENDING_LOOPS upper bound
In reply to Will DeaconOn x86, atomic_cond_read_relaxed will busy-wait with a cpu_relax() loop, so it is desirable to increase the number of times we spin on the qspinlock lockword when it is found to be transitioning from pending to locked.
According to Waiman Long:
| Ideally, the spinning times should be at least a few times the typical | cacheline load time from memory which I think can be down to 100ns or | so for each cacheline load with the newest systems or up to several | hundreds ns for older systems.
which in his benchmarking corresponded to 512 iterations.
--- arch/x86/include/asm/qspinlock.h | 2 ++ 1 file changed, 2 insertions(+)
arch/x86/include/asm/qspinlock.h +2 -0
@@ -7,6 +7,8 @@#include <asm-generic/qspinlock_types.h> #include <asm/paravirt.h> +#define _Q_PENDING_LOOPS (1 << 9) +#define queued_spin_unlock queued_spin_unlock/** * queued_spin_unlock - release a queued spinlock
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock/x86: Increase _Q_PENDING_LOOPS upper bound
locking/qspinlock/x86: Increase _Q_PENDING_LOOPS upper bound
Commit-ID: b247be3fe89b6aba928bf80f4453d1c4ba8d2063 Gitweb: https://git.kernel.org/tip/b247be3fe89b6aba928bf80f4453d1c4ba8d2063 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:18 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:47 +0200
locking/qspinlock/x86: Increase _Q_PENDING_LOOPS upper bound
On x86, atomic_cond_read_relaxed will busy-wait with a cpu_relax() loop, so it is desirable to increase the number of times we spin on the qspinlock lockword when it is found to be transitioning from pending to locked.
According to Waiman Long:
| Ideally, the spinning times should be at least a few times the typical | cacheline load time from memory which I think can be down to 100ns or | so for each cacheline load with the newest systems or up to several | hundreds ns for older systems.
which in his benchmarking corresponded to 512 iterations.
--- arch/x86/include/asm/qspinlock.h | 2 ++ 1 file changed, 2 insertions(+)
arch/x86/include/asm/qspinlock.h +2 -0
@@ -7,6 +7,8 @@#include <asm-generic/qspinlock_types.h> #include <asm/paravirt.h> +#define _Q_PENDING_LOOPS (1 << 9) +#define queued_spin_unlock queued_spin_unlock/** * queued_spin_unlock - release a queued spinlock
PATCH v3 5/14 locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
In reply to Will DeaconThe qspinlock locking slowpath utilises a "pending" bit as a simple form of an embedded test-and-set lock that can avoid the overhead of explicit queuing in cases where the lock is held but uncontended. This bit is managed using a cmpxchg loop which tries to transition the uncontended lock word from (0,0,0) -> (0,0,1) or (0,0,1) -> (0,1,1).
Unfortunately, the cmpxchg loop is unbounded and lockers can be starved indefinitely if the lock word is seen to oscillate between unlocked (0,0,0) and locked (0,0,1). This could happen if concurrent lockers are able to take the lock in the cmpxchg loop without queuing and pass it around amongst themselves.
This patch fixes the problem by unconditionally setting _Q_PENDING_VAL using atomic_fetch_or, and then inspecting the old value to see whether we need to spin on the current lock owner, or whether we now effectively hold the lock. The tricky scenario is when concurrent lockers end up queuing on the lock and the lock becomes available, causing us to see a lockword of (n,0,0). With pending now set, simply queuing could lead to deadlock as the head of the queue may not have observed the pending flag being cleared. Conversely, if the head of the queue did observe pending being cleared, then it could transition the lock from (n,0,0) -> (0,0,1) meaning that any attempt to "undo" our setting of the pending bit could race with a concurrent locker trying to set it.
We handle this race by preserving the pending bit when taking the lock after reaching the head of the queue and leaving the tail entry intact if we saw pending set, because we know that the tail is going to be updated shortly.
--- kernel/locking/qspinlock.c | 102 ++++++++++++++++++++---------------- kernel/locking/qspinlock_paravirt.h | 5 -- 2 files changed, 58 insertions(+), 49 deletions(-)
kernel/locking/qspinlock.c +58 -44
@@ -128,6 +128,17 @@ static inline __pure struct mcs_spinlock *decode_tail(u32 tail)#if _Q_PENDING_BITS ==8 /** +* clear_pending - clear the pending bit. +* @lock: Pointer to queued spinlock structure +* +* *,1,* -> *,0,* +*/ +static __always_inline void clear_pending(struct qspinlock *lock) +{ +WRITE_ONCE (lock->pending,0 ); +} + +/** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queued spinlock structure *
@@ -163,6 +174,17 @@ static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail)#else /* _Q_PENDING_BITS == 8 */ /** +* clear_pending - clear the pending bit. +* @lock: Pointer to queued spinlock structure +* +* *,1,* -> *,0,* +*/ +static __always_inline void clear_pending(struct qspinlock *lock) +{ +atomic_andnot (_Q_PENDING_VAL, &lock->val); +} + +/** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queued spinlock structure *
@@ -266,7 +288,7 @@ static __always_inline u32 __pv_wait_head_or_lock(struct qspinlock *lock,void queued_spin_lock_slowpath (struct qspinlock *lock,u32 val) {struct mcs_spinlock *prev, *next, *node; -u32 new, old, tail; +u32 old, tail;int idx;BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));
@@ -290,58 +312,50 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) }/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock * 0,0,1 -> 0,1,1 ; pending */ -for (;;) { + val =atomic_fetch_or_acquire (_Q_PENDING_VAL, &lock->val); +if (!(val & ~_Q_LOCKED_MASK)) {/* -* If we observe any contention; queue. +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 +* +* this wait loop must be a load-acquire such that we match the +* store-release that clears the locked bit and create lock +* sequentiality; this is because not all +* clear_pending_set_locked() implementations imply full +* barriers. */ -if (val & ~_Q_LOCKED_MASK) - goto queue; - - new = _Q_LOCKED_VAL; -if (val == new) - new |= _Q_PENDING_VAL; +if (val & _Q_LOCKED_MASK) { +smp_cond_load_acquire (&lock->val.counter, + !(VAL & _Q_LOCKED_MASK)); + }/* -* Acquire semantic is required here as the function may -* return immediately if the lock was free. +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 */ - old =atomic_cmpxchg_acquire (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } - -/* -* we won the trylock -*/ -if (new == _Q_LOCKED_VAL) +clear_pending_set_locked (lock);return ; + }/* -* we're pending, wait for the owner to go away. -* -* *,1,1 -> *,1,0 -* -* this wait loop must be a load-acquire such that we match the -* store-release that clears the locked bit and create lock -* sequentiality; this is because not all clear_pending_set_locked() -* implementations imply full barriers. -*/ -smp_cond_load_acquire (&lock->val.counter, !(VAL & _Q_LOCKED_MASK)); - -/* -* take ownership and clear the pending bit. -* -* *,1,0 -> *,0,1 +* If pending was clear but there are waiters in the queue, then +* we need to undo our setting of pending before we queue ourselves. */ -clear_pending_set_locked (lock); -return ; +if (!(val & _Q_PENDING_MASK)) +clear_pending (lock);/* * End of pending bit optimistic spinning and beginning of MCS
@@ -445,15 +459,15 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* claim the lock: * * n,0,0 -> 0,0,1 : lock, uncontended -* *,0,0 -> *,0,1 : lock, contended +* *,*,0 -> *,*,1 : lock, contended * -* If the queue head is the only one in the queue (lock value == tail), -* clear the tail code and grab the lock. Otherwise, we only need -* to grab the lock. +* If the queue head is the only one in the queue (lock value == tail) +* and nobody is pending, clear the tail code and grab the lock. +* Otherwise, we only need to grab the lock. */ for (;;) {/* In the PV case we might already have _Q_LOCKED_VAL set */ -if ((val & _Q_TAIL_MASK) != tail) { +if ((val & _Q_TAIL_MASK) != tail || (val & _Q_PENDING_MASK)) {set_locked (lock);break ; }
kernel/locking/qspinlock_paravirt.h +0 -5
@@ -118,11 +118,6 @@ static __always_inline void set_pending(struct qspinlock *lock)WRITE_ONCE (lock->pending,1 ); } -static __always_inline void clear_pending(struct qspinlock *lock) -{ -WRITE_ONCE (lock->pending,0 ); -} -/* * The pending bit check in pv_queued_spin_steal_lock() isn't a memory * barrier. Therefore, an atomic cmpxchg_acquire() is used to acquire the
Signature · 1 line
2.1.4
Peter Zijlstra
maintainerRe: [PATCH v3 05/14] locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
Quoted 11 lines · Will Deacon
kernel/locking/qspinlock.c +6
-0@@ -290,58 +312,50 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) }/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + +/* * trylock || pending
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 331
Quoted 10 lines
kernel/locking/qspinlock.c +5
-0* * 0,0,0 -> 0,0,1 ; trylock * 0,0,1 -> 0,1,1 ; pending */ + val =atomic_fetch_or_acquire (_Q_PENDING_VAL, &lock->val); +if (!(val & ~_Q_LOCKED_MASK)) {/* +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0
Tail must be 0 here, right?
Quoted 7 lines
kernel/locking/qspinlock.c +6
-0+* +* this wait loop must be a load-acquire such that we match the +* store-release that clears the locked bit and create lock +* sequentiality; this is because not all +* clear_pending_set_locked() implementations imply full +* barriers. */
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 348
Quoted 10 lines
kernel/locking/qspinlock.c +7
-0+if (val & _Q_LOCKED_MASK) { +smp_cond_load_acquire (&lock->val.counter, + !(VAL & _Q_LOCKED_MASK)); + }/* +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 */
Idem.
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 358
Quoted 10 lines
kernel/locking/qspinlock.c +6
-0+clear_pending_set_locked (lock);return ; + }/* +* If pending was clear but there are waiters in the queue, then +* we need to undo our setting of pending before we queue ourselves. */ +if (!(val & _Q_PENDING_MASK)) +clear_pending (lock);
This is the branch for when we have !0 tail.
Quoted 10 lines
kernel/locking/qspinlock.c +1
-0/* * End of pending bit optimistic spinning and beginning of MCS @@ -445,15 +459,15 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* claim the lock: * * n,0,0 -> 0,0,1 : lock, uncontended +* *,*,0 -> *,*,1 : lock, contended *
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 473
Quoted 10 lines
kernel/locking/qspinlock.c +4
-0+* If the queue head is the only one in the queue (lock value == tail) +* and nobody is pending, clear the tail code and grab the lock. +* Otherwise, we only need to grab the lock. */ for (;;) {/* In the PV case we might already have _Q_LOCKED_VAL set */ +if ((val & _Q_TAIL_MASK) != tail || (val & _Q_PENDING_MASK)) {set_locked (lock);break ; }
This one hunk is terrible on the brain. I'm fairly sure I get it, but I feel that comment can use help. Or at least, I need help reading it.
I'll try and cook up something when my brain starts working again.
Re: [PATCH v3 05/14] locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
Hi Peter,
Quoted 16 lines · Peter Zijlstra
Quoted 15 lines · Will Deacon
kernel/locking/qspinlock.c +6
-0@@ -290,58 +312,50 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) }/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock * 0,0,1 -> 0,1,1 ; pending */
Quoted 8 lines
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 331
Quoted 6 lines
kernel/locking/qspinlock.c +5
-0+ val =atomic_fetch_or_acquire (_Q_PENDING_VAL, &lock->val); +if (!(val & ~_Q_LOCKED_MASK)) {/* +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 Tail must be 0 here, right?
Not necessarily. If we're concurrently setting pending with another slowpath locker, they could queue in the tail behind us, so we can't mess with those upper bits.
Quoted 12 lines
Quoted 12 lines
kernel/locking/qspinlock.c +10
-0+* +* this wait loop must be a load-acquire such that we match the +* store-release that clears the locked bit and create lock +* sequentiality; this is because not all +* clear_pending_set_locked() implementations imply full +* barriers. */ +if (val & _Q_LOCKED_MASK) { +smp_cond_load_acquire (&lock->val.counter, + !(VAL & _Q_LOCKED_MASK)); + }
Quoted 7 lines
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 348
Quoted 5 lines
kernel/locking/qspinlock.c +3
-0/* +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 */ Idem.
Same here, hence why clear_pending_set_locked is either a 16-bit store or an RmW (we can't just clobber the tail with 0).
Quoted 4 lines
Quoted 4 lines
kernel/locking/qspinlock.c +2
-0+clear_pending_set_locked (lock);return ; + }
Quoted 8 lines
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 358
Quoted 6 lines
kernel/locking/qspinlock.c +4
-0/* +* If pending was clear but there are waiters in the queue, then +* we need to undo our setting of pending before we queue ourselves. */ +if (!(val & _Q_PENDING_MASK)) +clear_pending (lock);This is the branch for when we have !0 tail.
That's the case where "val" has a !0 tail, but I think the comments are trying to talk about the status of the lockword in memory, no?
Quoted 23 lines
Quoted 9 lines
kernel/locking/qspinlock.c +1
-0/* * End of pending bit optimistic spinning and beginning of MCS @@ -445,15 +459,15 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* claim the lock: * * n,0,0 -> 0,0,1 : lock, uncontended +* *,*,0 -> *,*,1 : lock, contended * kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 473
Quoted 10 lines
kernel/locking/qspinlock.c +4
-0+* If the queue head is the only one in the queue (lock value == tail) +* and nobody is pending, clear the tail code and grab the lock. +* Otherwise, we only need to grab the lock. */ for (;;) {/* In the PV case we might already have _Q_LOCKED_VAL set */ +if ((val & _Q_TAIL_MASK) != tail || (val & _Q_PENDING_MASK)) {set_locked (lock);break ; }This one hunk is terrible on the brain. I'm fairly sure I get it, but I feel that comment can use help. Or at least, I need help reading it.
Quoted 1 line
I'll try and cook up something when my brain starts working again.
Cheers. I think the code is a bit easier to read if you look at it after the whole series is applied, but the comments could probably still be improved.
Will
Peter Zijlstra
maintainerRe: [PATCH v3 05/14] locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
Quoted 28 lines · Will Deacon
Hi Peter,
Quoted 16 lines · Peter Zijlstra
Quoted 15 lines · Will Deacon
kernel/locking/qspinlock.c +6
-0@@ -290,58 +312,50 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) }/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock * 0,0,1 -> 0,1,1 ; pending */ Quoted 8 lines
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 331
Quoted 6 lines
kernel/locking/qspinlock.c +5
-0+ val =atomic_fetch_or_acquire (_Q_PENDING_VAL, &lock->val); +if (!(val & ~_Q_LOCKED_MASK)) {/* +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 Tail must be 0 here, right?
Quoted 3 lines
Not necessarily. If we're concurrently setting pending with another slowpath locker, they could queue in the tail behind us, so we can't mess with those upper bits.
Could be my brain just entirely stopped working; but I read that as:
!(val & ~0xFF) := !(val & 0xFFFFFF00)
which then pretty much mandates the top bits are empty, no?
Re: [PATCH v3 05/14] locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
Quoted 31 lines · Peter Zijlstra
Quoted 26 lines · Will Deacon
Quoted 16 lines · Peter Zijlstra
Quoted 15 lines · Will Deacon
kernel/locking/qspinlock.c +6
-0@@ -290,58 +312,50 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) }/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock * 0,0,1 -> 0,1,1 ; pending */ Quoted 8 lines
kernel/locking/qspinlock.c void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) · line 331
Quoted 6 lines
kernel/locking/qspinlock.c +5
-0+ val =atomic_fetch_or_acquire (_Q_PENDING_VAL, &lock->val); +if (!(val & ~_Q_LOCKED_MASK)) {/* +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 Tail must be 0 here, right?
Quoted 3 lines
Not necessarily. If we're concurrently setting pending with another slowpath locker, they could queue in the tail behind us, so we can't mess with those upper bits.
Quoted 5 lines
Could be my brain just entirely stopped working; but I read that as:
!(val & ~0xFF) := !(val & 0xFFFFFF00)which then pretty much mandates the top bits are empty, no?
Only if there isn't a concurrent locker. For example:
T0:
// fastpath fails to acquire the lock, returns val == _Q_LOCKED_VAL
if (val & ~_Q_LOCKED_MASK)
goto queue;
// Fallthrough
T1:
// fastpath fails to acquire the lock, returns val == _Q_LOCKED_VAL
if (val & ~_Q_LOCKED_MASK)
goto queue;
// Fallthrough
T0:
val = atomic_fetch_or_acquire(_Q_PENDING_VAL, &lock->val); // val == _Q_LOCKED_VAL
T1:
val = atomic_fetch_or_acquire(_Q_PENDING_VAL, &lock->val); // val == _Q_PENDING_VAL | _Q_LOCKED_VAL
// Queue into tail
T0:
// Spins for _Q_LOCKED_MASK to go to zero, but tail is *non-zero*
So it's really down to whether the state transitions in the comments refer to the lockword in memory, or the local "val" variable. I think the former is more instructive, because the whole thing is concurrent.
Will
Waiman Long
reviewerRe: [PATCH v3 05/14] locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
In reply to Will DeaconQuoted 202 lines · Will Deacon
The qspinlock locking slowpath utilises a "pending" bit as a simple form of an embedded test-and-set lock that can avoid the overhead of explicit queuing in cases where the lock is held but uncontended. This bit is managed using a cmpxchg loop which tries to transition the uncontended lock word from (0,0,0) -> (0,0,1) or (0,0,1) -> (0,1,1).
Unfortunately, the cmpxchg loop is unbounded and lockers can be starved indefinitely if the lock word is seen to oscillate between unlocked (0,0,0) and locked (0,0,1). This could happen if concurrent lockers are able to take the lock in the cmpxchg loop without queuing and pass it around amongst themselves.
This patch fixes the problem by unconditionally setting
_Q_PENDING_VALusingatomic_fetch_or, and then inspecting the old value to see whether we need to spin on the current lock owner, or whether we now effectively hold the lock. The tricky scenario is when concurrent lockers end up queuing on the lock and the lock becomes available, causing us to see a lockword of (n,0,0). With pending now set, simply queuing could lead to deadlock as the head of the queue may not have observed the pending flag being cleared. Conversely, if the head of the queue did observe pending being cleared, then it could transition the lock from (n,0,0) -> (0,0,1) meaning that any attempt to "undo" our setting of the pending bit could race with a concurrent locker trying to set it.We handle this race by preserving the pending bit when taking the lock after reaching the head of the queue and leaving the tail entry intact if we saw pending set, because we know that the tail is going to be updated shortly.
Cc Peter Zijlstra <[email protected]>Cc Ingo Molnar <[email protected]>Signed-off-by Will Deacon <[email protected]>--- kernel/locking/qspinlock.c | 102 ++++++++++++++++++++---------------- kernel/locking/qspinlock_paravirt.h | 5 -- 2 files changed, 58 insertions(+), 49 deletions(-)kernel/locking/qspinlock.c +58
-44@@ -128,6 +128,17 @@ static inline __pure struct mcs_spinlock *decode_tail(u32 tail)#if _Q_PENDING_BITS ==8 /** +* clear_pending - clear the pending bit. +* @lock: Pointer to queued spinlock structure +* +* *,1,* -> *,0,* +*/ +static __always_inline void clear_pending(struct qspinlock *lock) +{ +WRITE_ONCE (lock->pending,0 ); +} + +/** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queued spinlock structure * @@ -163,6 +174,17 @@ static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail)#else /* _Q_PENDING_BITS == 8 */ /** +* clear_pending - clear the pending bit. +* @lock: Pointer to queued spinlock structure +* +* *,1,* -> *,0,* +*/ +static __always_inline void clear_pending(struct qspinlock *lock) +{ +atomic_andnot (_Q_PENDING_VAL, &lock->val); +} + +/** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queued spinlock structure * @@ -266,7 +288,7 @@ static __always_inline u32 __pv_wait_head_or_lock(struct qspinlock *lock,void queued_spin_lock_slowpath (struct qspinlock *lock,u32 val) {struct mcs_spinlock *prev, *next, *node; -u32 new, old, tail; +u32 old, tail;int idx;BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));@@ -290,58 +312,50 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) }/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; + +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock * 0,0,1 -> 0,1,1 ; pending */ -for (;;) { + val =atomic_fetch_or_acquire (_Q_PENDING_VAL, &lock->val); +if (!(val & ~_Q_LOCKED_MASK)) {/* -* If we observe any contention; queue. +* we're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 +* +* this wait loop must be a load-acquire such that we match the +* store-release that clears the locked bit and create lock +* sequentiality; this is because not all +* clear_pending_set_locked() implementations imply full +* barriers. */ -if (val & ~_Q_LOCKED_MASK) - goto queue; - - new = _Q_LOCKED_VAL; -if (val == new) - new |= _Q_PENDING_VAL; +if (val & _Q_LOCKED_MASK) { +smp_cond_load_acquire (&lock->val.counter, + !(VAL & _Q_LOCKED_MASK)); + }/* -* Acquire semantic is required here as the function may -* return immediately if the lock was free. +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 */ - old =atomic_cmpxchg_acquire (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } - -/* -* we won the trylock -*/ -if (new == _Q_LOCKED_VAL) +clear_pending_set_locked (lock);return ; + }/* -* we're pending, wait for the owner to go away. -* -* *,1,1 -> *,1,0 -* -* this wait loop must be a load-acquire such that we match the -* store-release that clears the locked bit and create lock -* sequentiality; this is because not all clear_pending_set_locked() -* implementations imply full barriers. -*/ -smp_cond_load_acquire (&lock->val.counter, !(VAL & _Q_LOCKED_MASK)); - -/* -* take ownership and clear the pending bit. -* -* *,1,0 -> *,0,1 +* If pending was clear but there are waiters in the queue, then +* we need to undo our setting of pending before we queue ourselves. */ -clear_pending_set_locked (lock); -return ; +if (!(val & _Q_PENDING_MASK)) +clear_pending (lock);/* * End of pending bit optimistic spinning and beginning of MCS @@ -445,15 +459,15 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* claim the lock: * * n,0,0 -> 0,0,1 : lock, uncontended -* *,0,0 -> *,0,1 : lock, contended +* *,*,0 -> *,*,1 : lock, contended * -* If the queue head is the only one in the queue (lock value == tail), -* clear the tail code and grab the lock. Otherwise, we only need -* to grab the lock. +* If the queue head is the only one in the queue (lock value == tail) +* and nobody is pending, clear the tail code and grab the lock. +* Otherwise, we only need to grab the lock. */ for (;;) {/* In the PV case we might already have _Q_LOCKED_VAL set */ -if ((val & _Q_TAIL_MASK) != tail) { +if ((val & _Q_TAIL_MASK) != tail || (val & _Q_PENDING_MASK)) {set_locked (lock);break ; }kernel/locking/qspinlock_paravirt.h +0
-0@@ -118,11 +118,6 @@ static __always_inline void set_pending(struct qspinlock *lock)WRITE_ONCE (lock->pending,1 );
kernel/locking/qspinlock_paravirt.h static __always_inline void set_pending(struct qspinlock *lock) · line 123
Quoted 10 lines
kernel/locking/qspinlock_paravirt.h +0
-5} -static __always_inline void clear_pending(struct qspinlock *lock) -{ -WRITE_ONCE (lock->pending,0 ); -} -/* * The pending bit check in pv_queued_spin_steal_lock() isn't a memory * barrier. Therefore, an atomic cmpxchg_acquire() is used to acquire the
There is another clear_pending() function after the "#else /* _Q_PENDING_BITS == 8 */" line that need to be removed as well.
-Longman
Re: [PATCH v3 05/14] locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
Hi Waiman,
Quoted 9 lines · Waiman Long
Quoted 8 lines · Will Deacon
kernel/locking/qspinlock_paravirt.h +0
-0@@ -118,11 +118,6 @@ static __always_inline void set_pending(struct qspinlock *lock)WRITE_ONCE (lock->pending,1 ); }
Quoted 11 lines
kernel/locking/qspinlock_paravirt.h static __always_inline void set_pending(struct qspinlock *lock) · line 123
Quoted 8 lines
kernel/locking/qspinlock_paravirt.h +0
-5-static __always_inline void clear_pending(struct qspinlock *lock) -{ -WRITE_ONCE (lock->pending,0 ); -} -/* * The pending bit check in pv_queued_spin_steal_lock() isn't a memory * barrier. Therefore, an atomic cmpxchg_acquire() is used to acquire the There is another
clear_pending()function after the "#else /*_Q_PENDING_BITS== 8 */" line that need to be removed as well.
Bugger, sorry I missed that one. Is the >= 16K CPUs case supported elsewhere in Linux? The x86 Kconfig appears to clamp NR_CPUS to 8192 iiuc.
Anyway, additional patch below. Ingo -- please can you apply this on top?
Thanks,
Will
--->8
Quoted 1 line
From
ef6aa51e47047fe1a57dfdbe2f45caf63fa95be4Mon Sep 17 00:00:00 2001
From: Will Deacon <[email protected]>
Date: Fri, 27 Apr 2018 10:40:13 +0100
Subject: [PATCH] locking/qspinlock: Remove duplicate clear_pending function
from PV code
The native clear_pending function is identical to the PV version, so the latter can simply be removed. This fixes the build for systems with >= 16K CPUs using the PV lock implementation.
--- kernel/locking/qspinlock_paravirt.h | 5 ----- 1 file changed, 5 deletions(-)
kernel/locking/qspinlock_paravirt.h +0 -5
@@ -130,11 +130,6 @@ static __always_inline void set_pending(struct qspinlock *lock)atomic_or (_Q_PENDING_VAL, &lock->val); } -static __always_inline void clear_pending(struct qspinlock *lock) -{ -atomic_andnot (_Q_PENDING_VAL, &lock->val); -} - static __always_inline int trylock_clear_pending(struct qspinlock *lock) {int val =atomic_read (&lock->val);
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Remove duplicate clear_pending() function from PV code
locking/qspinlock: Remove duplicate clear_pending() function from PV code
Commit-ID: 3bea9adc96842b8a7345c7fb202c16ae9c8d5b25 Gitweb: https://git.kernel.org/tip/3bea9adc96842b8a7345c7fb202c16ae9c8d5b25 Author: Will Deacon <[email protected]> AuthorDate: Fri, 27 Apr 2018 10:40:13 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 12:55:22 +0200
locking/qspinlock: Remove duplicate clear_pending() function from PV code
The native clear_pending() function is identical to the PV version, so the latter can simply be removed.
This fixes the build for systems with >= 16K CPUs using the PV lock implementation.
--- kernel/locking/qspinlock_paravirt.h | 5 ----- 1 file changed, 5 deletions(-)
kernel/locking/qspinlock_paravirt.h +0 -5
@@ -130,11 +130,6 @@ static __always_inline void set_pending(struct qspinlock *lock)atomic_or (_Q_PENDING_VAL, &lock->val); } -static __always_inline void clear_pending(struct qspinlock *lock) -{ -atomic_andnot (_Q_PENDING_VAL, &lock->val); -} - static __always_inline int trylock_clear_pending(struct qspinlock *lock) {int val =atomic_read (&lock->val);
Waiman Long
reviewerRe: [PATCH v3 05/14] locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath
In reply to Will DeaconQuoted 22 lines · Will Deacon
Hi Waiman,
Quoted 9 lines · Waiman Long
Quoted 8 lines · Will Deacon
kernel/locking/qspinlock_paravirt.h +0
-0@@ -118,11 +118,6 @@ static __always_inline void set_pending(struct qspinlock *lock)WRITE_ONCE (lock->pending,1 ); }Quoted 10 lines
kernel/locking/qspinlock_paravirt.h static __always_inline void set_pending(struct qspinlock *lock)
Quoted 8 lines
kernel/locking/qspinlock_paravirt.h +0
-5-static __always_inline void clear_pending(struct qspinlock *lock) -{ -WRITE_ONCE (lock->pending,0 ); -} -/* * The pending bit check in pv_queued_spin_steal_lock() isn't a memory * barrier. Therefore, an atomic cmpxchg_acquire() is used to acquire the There is another
clear_pending()function after the "#else /*_Q_PENDING_BITS== 8 */" line that need to be removed as well.
Quoted 5 lines
Bugger, sorry I missed that one. Is the >= 16K CPUs case supported elsewhere in Linux? The x86
Kconfigappears to clampNR_CPUSto 8192 iiuc.Anyway, additional patch below. Ingo -- please can you apply this on top?
I don't think we support >= 16k in any of the distros. However, this will be a limit that we will reach eventually. That is why I said we can wait.
Cheers,
Longman
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Remove unbounded cmpxchg() loop from locking slowpath
In reply to Will Deaconlocking/qspinlock: Remove unbounded cmpxchg() loop from locking slowpath
Commit-ID: 59fb586b4a07b4e1a0ee577140ab4842ba451acd Gitweb: https://git.kernel.org/tip/59fb586b4a07b4e1a0ee577140ab4842ba451acd Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:19 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:47 +0200
locking/qspinlock: Remove unbounded cmpxchg() loop from locking slowpath
The qspinlock locking slowpath utilises a "pending" bit as a simple form of an embedded test-and-set lock that can avoid the overhead of explicit queuing in cases where the lock is held but uncontended. This bit is managed using a cmpxchg() loop which tries to transition the uncontended lock word from (0,0,0) -> (0,0,1) or (0,0,1) -> (0,1,1).
Unfortunately, the cmpxchg() loop is unbounded and lockers can be starved indefinitely if the lock word is seen to oscillate between unlocked (0,0,0) and locked (0,0,1). This could happen if concurrent lockers are able to take the lock in the cmpxchg() loop without queuing and pass it around amongst themselves.
This patch fixes the problem by unconditionally setting _Q_PENDING_VAL using atomic_fetch_or, and then inspecting the old value to see whether we need to spin on the current lock owner, or whether we now effectively hold the lock. The tricky scenario is when concurrent lockers end up queuing on the lock and the lock becomes available, causing us to see a lockword of (n,0,0). With pending now set, simply queuing could lead to deadlock as the head of the queue may not have observed the pending flag being cleared. Conversely, if the head of the queue did observe pending being cleared, then it could transition the lock from (n,0,0) -> (0,0,1) meaning that any attempt to "undo" our setting of the pending bit could race with a concurrent locker trying to set it.
We handle this race by preserving the pending bit when taking the lock after reaching the head of the queue and leaving the tail entry intact if we saw pending set, because we know that the tail is going to be updated shortly.
--- kernel/locking/qspinlock.c | 102 ++++++++++++++++++++---------------- kernel/locking/qspinlock_paravirt.h | 5 -- 2 files changed, 58 insertions(+), 49 deletions(-)
kernel/locking/qspinlock.c +58 -44
@@ -127,6 +127,17 @@ static inline __pure struct mcs_spinlock *decode_tail(u32 tail)#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK)#if _Q_PENDING_BITS ==8 +/** +* clear_pending - clear the pending bit. +* @lock: Pointer to queued spinlock structure +* +* *,1,* -> *,0,* +*/ +static __always_inline void clear_pending(struct qspinlock *lock) +{ +WRITE_ONCE (lock->pending,0 ); +} +/** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queued spinlock structure
@@ -162,6 +173,17 @@ static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail)#else /* _Q_PENDING_BITS == 8 */ +/** +* clear_pending - clear the pending bit. +* @lock: Pointer to queued spinlock structure +* +* *,1,* -> *,0,* +*/ +static __always_inline void clear_pending(struct qspinlock *lock) +{ +atomic_andnot (_Q_PENDING_VAL, &lock->val); +} +/** * clear_pending_set_locked - take ownership and clear the pending bit. * @lock: Pointer to queued spinlock structure
@@ -266,7 +288,7 @@ static __always_inline u32 __pv_wait_head_or_lock(struct qspinlock *lock,void queued_spin_lock_slowpath (struct qspinlock *lock,u32 val) {struct mcs_spinlock *prev, *next, *node; -u32 new, old, tail; +u32 old, tail;int idx;BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));
@@ -289,59 +311,51 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) (VAL != _Q_PENDING_VAL) || !cnt--); } +/* +* If we observe any contention; queue. +*/ +if (val & ~_Q_LOCKED_MASK) + goto queue; +/* * trylock || pending * * 0,0,0 -> 0,0,1 ; trylock * 0,0,1 -> 0,1,1 ; pending */ -for (;;) { + val =atomic_fetch_or_acquire (_Q_PENDING_VAL, &lock->val); +if (!(val & ~_Q_LOCKED_MASK)) {/* -* If we observe any contention; queue. +* We're pending, wait for the owner to go away. +* +* *,1,1 -> *,1,0 +* +* this wait loop must be a load-acquire such that we match the +* store-release that clears the locked bit and create lock +* sequentiality; this is because not all +* clear_pending_set_locked() implementations imply full +* barriers. */ -if (val & ~_Q_LOCKED_MASK) - goto queue; - - new = _Q_LOCKED_VAL; -if (val == new) - new |= _Q_PENDING_VAL; +if (val & _Q_LOCKED_MASK) { +smp_cond_load_acquire (&lock->val.counter, + !(VAL & _Q_LOCKED_MASK)); + }/* -* Acquire semantic is required here as the function may -* return immediately if the lock was free. +* take ownership and clear the pending bit. +* +* *,1,0 -> *,0,1 */ - old =atomic_cmpxchg_acquire (&lock->val, val, new); -if (old == val) -break ; - - val = old; - } - -/* -* we won the trylock -*/ -if (new == _Q_LOCKED_VAL) +clear_pending_set_locked (lock);return ; + }/* -* we're pending, wait for the owner to go away. -* -* *,1,1 -> *,1,0 -* -* this wait loop must be a load-acquire such that we match the -* store-release that clears the locked bit and create lock -* sequentiality; this is because not all clear_pending_set_locked() -* implementations imply full barriers. -*/ -smp_cond_load_acquire (&lock->val.counter, !(VAL & _Q_LOCKED_MASK)); - -/* -* take ownership and clear the pending bit. -* -* *,1,0 -> *,0,1 +* If pending was clear but there are waiters in the queue, then +* we need to undo our setting of pending before we queue ourselves. */ -clear_pending_set_locked (lock); -return ; +if (!(val & _Q_PENDING_MASK)) +clear_pending (lock);/* * End of pending bit optimistic spinning and beginning of MCS
@@ -445,15 +459,15 @@ locked:* claim the lock: * * n,0,0 -> 0,0,1 : lock, uncontended -* *,0,0 -> *,0,1 : lock, contended +* *,*,0 -> *,*,1 : lock, contended * -* If the queue head is the only one in the queue (lock value == tail), -* clear the tail code and grab the lock. Otherwise, we only need -* to grab the lock. +* If the queue head is the only one in the queue (lock value == tail) +* and nobody is pending, clear the tail code and grab the lock. +* Otherwise, we only need to grab the lock. */ for (;;) {/* In the PV case we might already have _Q_LOCKED_VAL set */ -if ((val & _Q_TAIL_MASK) != tail) { +if ((val & _Q_TAIL_MASK) != tail || (val & _Q_PENDING_MASK)) {set_locked (lock);break ; }
kernel/locking/qspinlock_paravirt.h +0 -5
@@ -118,11 +118,6 @@ static __always_inline void set_pending(struct qspinlock *lock)WRITE_ONCE (lock->pending,1 ); } -static __always_inline void clear_pending(struct qspinlock *lock) -{ -WRITE_ONCE (lock->pending,0 ); -} -/* * The pending bit check in pv_queued_spin_steal_lock() isn't a memory * barrier. Therefore, an atomic cmpxchg_acquire() is used to acquire the
PATCH v3 6/14 locking/qspinlock: Kill cmpxchg loop when claiming lock from head of queue
In reply to Will DeaconWhen a queued locker reaches the head of the queue, it claims the lock by setting _Q_LOCKED_VAL in the lockword. If there isn't contention, it must also clear the tail as part of this operation so that subsequent lockers can avoid taking the slowpath altogether.
Currently this is expressed as a cmpxchg loop that practically only runs up to two iterations. This is confusing to the reader and unhelpful to the compiler. Rewrite the cmpxchg loop without the loop, so that a failed cmpxchg implies that there is contention and we just need to write to _Q_LOCKED_VAL without considering the rest of the lockword.
--- kernel/locking/qspinlock.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-)
kernel/locking/qspinlock.c +8 -11
@@ -465,24 +465,21 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* and nobody is pending, clear the tail code and grab the lock. * Otherwise, we only need to grab the lock. */ -for (;;) { -/* In the PV case we might already have _Q_LOCKED_VAL set */ -if ((val & _Q_TAIL_MASK) != tail || (val & _Q_PENDING_MASK)) { -set_locked (lock); -break ; - } + +/* In the PV case we might already have _Q_LOCKED_VAL set */ +if ((val & _Q_TAIL_MASK) == tail) {/* * The smp_cond_load_acquire() call above has provided the -* necessary acquire semantics required for locking. At most -* two iterations of this loop may be ran. +* necessary acquire semantics required for locking. */ old =atomic_cmpxchg_relaxed (&lock->val, val, _Q_LOCKED_VAL);if (old == val) - goto release;/* No contention */ - - val = old; + goto release;/* No contention */ } +/* Either somebody is queued behind us or _Q_PENDING_VAL is set */ +set_locked (lock); +/* * contended path; wait for next if not observed yet, release. */
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Kill cmpxchg() loop when claiming lock from head of queue
locking/qspinlock: Kill cmpxchg() loop when claiming lock from head of queue
Commit-ID: c61da58d8a9ba9238250a548f00826eaf44af0f7 Gitweb: https://git.kernel.org/tip/c61da58d8a9ba9238250a548f00826eaf44af0f7 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:20 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:48 +0200
locking/qspinlock: Kill cmpxchg() loop when claiming lock from head of queue
When a queued locker reaches the head of the queue, it claims the lock by setting _Q_LOCKED_VAL in the lockword. If there isn't contention, it must also clear the tail as part of this operation so that subsequent lockers can avoid taking the slowpath altogether.
Currently this is expressed as a cmpxchg() loop that practically only runs up to two iterations. This is confusing to the reader and unhelpful to the compiler. Rewrite the cmpxchg() loop without the loop, so that a failed cmpxchg() implies that there is contention and we just need to write to _Q_LOCKED_VAL without considering the rest of the lockword.
--- kernel/locking/qspinlock.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-)
kernel/locking/qspinlock.c +8 -11
@@ -465,24 +465,21 @@ locked:* and nobody is pending, clear the tail code and grab the lock. * Otherwise, we only need to grab the lock. */ -for (;;) { -/* In the PV case we might already have _Q_LOCKED_VAL set */ -if ((val & _Q_TAIL_MASK) != tail || (val & _Q_PENDING_MASK)) { -set_locked (lock); -break ; - } + +/* In the PV case we might already have _Q_LOCKED_VAL set */ +if ((val & _Q_TAIL_MASK) == tail) {/* * The smp_cond_load_acquire() call above has provided the -* necessary acquire semantics required for locking. At most -* two iterations of this loop may be ran. +* necessary acquire semantics required for locking. */ old =atomic_cmpxchg_relaxed (&lock->val, val, _Q_LOCKED_VAL);if (old == val) - goto release;/* No contention */ - - val = old; + goto release;/* No contention */ } +/* Either somebody is queued behind us or _Q_PENDING_VAL is set */ +set_locked (lock); +/* * contended path; wait for next if not observed yet, release. */
PATCH v3 7/14 locking/qspinlock: Use atomic_cond_read_acquire
In reply to Will DeaconRather than dig into the counter field of the atomic_t inside the qspinlock structure so that we can call smp_cond_load_acquire, use atomic_cond_read_acquire instead, which operates on the atomic_t directly.
--- kernel/locking/qspinlock.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
kernel/locking/qspinlock.c +6 -6
@@ -337,8 +337,8 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* barriers. */ if (val & _Q_LOCKED_MASK) { -smp_cond_load_acquire (&lock->val.counter, - !(VAL & _Q_LOCKED_MASK)); +atomic_cond_read_acquire (&lock->val, + !(VAL & _Q_LOCKED_MASK)); }/*
@@ -441,8 +441,8 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* * The PV pv_wait_head_or_lock function, if active, will acquire * the lock and return a non-zero value. So we have to skip the -* smp_cond_load_acquire() call. As the next PV queue head hasn't been -* designated yet, there is no way for the locked value to become +* atomic_cond_read_acquire() call. As the next PV queue head hasn't +* been designated yet, there is no way for the locked value to become * _Q_SLOW_VAL. So both the set_locked() and the * atomic_cmpxchg_relaxed() calls will be safe. *
@@ -452,7 +452,7 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)if ((val =pv_wait_head_or_lock (lock, node))) goto locked; - val =smp_cond_load_acquire (&lock->val.counter, !(VAL & _Q_LOCKED_PENDING_MASK)); + val =atomic_cond_read_acquire (&lock->val, !(VAL & _Q_LOCKED_PENDING_MASK)); locked:/*
@@ -469,7 +469,7 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)/* In the PV case we might already have _Q_LOCKED_VAL set */ if ((val & _Q_TAIL_MASK) == tail) {/* -* The smp_cond_load_acquire() call above has provided the +* The atomic_cond_read_acquire() call above has provided the * necessary acquire semantics required for locking. */ old =atomic_cmpxchg_relaxed (&lock->val, val, _Q_LOCKED_VAL);
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Use atomic_cond_read_acquire()
locking/qspinlock: Use atomic_cond_read_acquire()
Commit-ID: f9c811fac48cfbbfb452b08d1042386947868d07 Gitweb: https://git.kernel.org/tip/f9c811fac48cfbbfb452b08d1042386947868d07 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:21 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:49 +0200
locking/qspinlock: Use atomic_cond_read_acquire()
Rather than dig into the counter field of the atomic_t inside the qspinlock structure so that we can call smp_cond_load_acquire(), use atomic_cond_read_acquire() instead, which operates on the atomic_t directly.
--- kernel/locking/qspinlock.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
kernel/locking/qspinlock.c +6 -6
@@ -337,8 +337,8 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* barriers. */ if (val & _Q_LOCKED_MASK) { -smp_cond_load_acquire (&lock->val.counter, - !(VAL & _Q_LOCKED_MASK)); +atomic_cond_read_acquire (&lock->val, + !(VAL & _Q_LOCKED_MASK)); }/*
@@ -441,8 +441,8 @@ queue:* * The PV pv_wait_head_or_lock function, if active, will acquire * the lock and return a non-zero value. So we have to skip the -* smp_cond_load_acquire() call. As the next PV queue head hasn't been -* designated yet, there is no way for the locked value to become +* atomic_cond_read_acquire() call. As the next PV queue head hasn't +* been designated yet, there is no way for the locked value to become * _Q_SLOW_VAL. So both the set_locked() and the * atomic_cmpxchg_relaxed() calls will be safe. *
@@ -452,7 +452,7 @@ queue:if ((val =pv_wait_head_or_lock (lock, node))) goto locked; - val =smp_cond_load_acquire (&lock->val.counter, !(VAL & _Q_LOCKED_PENDING_MASK)); + val =atomic_cond_read_acquire (&lock->val, !(VAL & _Q_LOCKED_PENDING_MASK)); locked:/*
@@ -469,7 +469,7 @@ locked:/* In the PV case we might already have _Q_LOCKED_VAL set */ if ((val & _Q_TAIL_MASK) == tail) {/* -* The smp_cond_load_acquire() call above has provided the +* The atomic_cond_read_acquire() call above has provided the * necessary acquire semantics required for locking. */ old =atomic_cmpxchg_relaxed (&lock->val, val, _Q_LOCKED_VAL);
PATCH v3 8/14 locking/mcs: Use smp_cond_load_acquire() in mcs spin loop
In reply to Will DeaconFrom: Jason Low <[email protected]>
For qspinlocks on ARM64, we would like to use WFE instead of purely spinning. Qspinlocks internally have lock contenders spin on an MCS lock.
Update arch_mcs_spin_lock_contended() such that it uses the new smp_cond_load_acquire() so that ARM64 can also override this spin loop with its own implementation using WFE.
On x86, this can also be cheaper than spinning on smp_load_acquire().
--- kernel/locking/mcs_spinlock.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
kernel/locking/mcs_spinlock.h +6 -4
@@ -23,13 +23,15 @@ struct mcs_spinlock {#ifndef arch_mcs_spin_lock_contended/* -* Using smp_load_acquire() provides a memory barrier that ensures -* subsequent operations happen after the lock is acquired. +* Using smp_cond_load_acquire() provides the acquire semantics +* required so that subsequent operations happen after the +* lock is acquired. Additionally, some architectures such as +* ARM64 would like to do spin-waiting instead of purely +* spinning, and smp_cond_load_acquire() provides that behavior. */ #define arch_mcs_spin_lock_contended (l) \ do { \ - while (!(smp_load_acquire(l))) \ - cpu_relax(); \ + smp_cond_load_acquire(l, VAL); \ } while (0)#endif
Signature · 1 line
2.1.4
tip-bot for Jason Low
merge bot[tip:locking/core] locking/mcs: Use smp_cond_load_acquire() in MCS spin loop
locking/mcs: Use smp_cond_load_acquire() in MCS spin loop
Commit-ID: 7f56b58a92aaf2cab049f32a19af7cc57a3972f2 Gitweb: https://git.kernel.org/tip/7f56b58a92aaf2cab049f32a19af7cc57a3972f2 Author: Jason Low <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:22 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:49 +0200
locking/mcs: Use smp_cond_load_acquire() in MCS spin loop
For qspinlocks on ARM64, we would like to use WFE instead of purely spinning. Qspinlocks internally have lock contenders spin on an MCS lock.
Update arch_mcs_spin_lock_contended() such that it uses the new smp_cond_load_acquire() so that ARM64 can also override this spin loop with its own implementation using WFE.
On x86, this can also be cheaper than spinning on smp_load_acquire().
--- kernel/locking/mcs_spinlock.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
kernel/locking/mcs_spinlock.h +6 -4
@@ -23,13 +23,15 @@ struct mcs_spinlock {#ifndef arch_mcs_spin_lock_contended/* -* Using smp_load_acquire() provides a memory barrier that ensures -* subsequent operations happen after the lock is acquired. +* Using smp_cond_load_acquire() provides the acquire semantics +* required so that subsequent operations happen after the +* lock is acquired. Additionally, some architectures such as +* ARM64 would like to do spin-waiting instead of purely +* spinning, and smp_cond_load_acquire() provides that behavior. */ #define arch_mcs_spin_lock_contended (l) \ do { \ - while (!(smp_load_acquire(l))) \ - cpu_relax(); \ + smp_cond_load_acquire(l, VAL); \ } while (0)#endif
PATCH v3 9/14 locking/qspinlock: Use smp_cond_load_relaxed to wait for next node
In reply to Will DeaconWhen a locker reaches the head of the queue and takes the lock, a concurrent locker may enqueue and force the lock holder to spin whilst its node->next field is initialised. Rather than open-code a READ_ONCE/cpu_relax() loop, this can be implemented using smp_cond_load_relaxed instead.
--- kernel/locking/qspinlock.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
kernel/locking/qspinlock.c +2 -4
@@ -483,10 +483,8 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)/* * contended path; wait for next if not observed yet, release. */ -if (!next) { -while (!(next =READ_ONCE (node->next))) -cpu_relax (); - } +if (!next) + next =smp_cond_load_relaxed (&node->next, (VAL ));arch_mcs_spin_unlock_contended (&next->locked);pv_kick_node (lock, next);
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Use smp_cond_load_relaxed() to wait for next node
locking/qspinlock: Use smp_cond_load_relaxed() to wait for next node
Commit-ID: c131a198c497db436b558ac5e9a140cdcb91b304 Gitweb: https://git.kernel.org/tip/c131a198c497db436b558ac5e9a140cdcb91b304 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:23 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:50 +0200
locking/qspinlock: Use smp_cond_load_relaxed() to wait for next node
When a locker reaches the head of the queue and takes the lock, a concurrent locker may enqueue and force the lock holder to spin whilst its node->next field is initialised. Rather than open-code a READ_ONCE/cpu_relax() loop, this can be implemented using smp_cond_load_relaxed() instead.
--- kernel/locking/qspinlock.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
kernel/locking/qspinlock.c +2 -4
@@ -483,10 +483,8 @@ locked:/* * contended path; wait for next if not observed yet, release. */ -if (!next) { -while (!(next =READ_ONCE (node->next))) -cpu_relax (); - } +if (!next) + next =smp_cond_load_relaxed (&node->next, (VAL ));arch_mcs_spin_unlock_contended (&next->locked);pv_kick_node (lock, next);
PATCH v3 10/14 locking/qspinlock: Make queued_spin_unlock use smp_store_release
In reply to Will DeaconA qspinlock can be unlocked simply by writing zero to the locked byte. This can be implemented in the generic code, so do that and remove the arch-specific override for x86 in the !PV case.
--- arch/x86/include/asm/qspinlock.h | 17 ++++++----------- include/asm-generic/qspinlock.h | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-)
arch/x86/include/asm/qspinlock.h +6 -11
@@ -9,6 +9,12 @@#define _Q_PENDING_LOOPS (1 << 9) +#ifdef CONFIG_PARAVIRT_SPINLOCKS +extern void native_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); +extern void __pv_init_lock_hash (void ); +extern void __pv_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); +extern void __raw_callee_save___pv_queued_spin_unlock (struct qspinlock *lock); +#define queued_spin_unlock queued_spin_unlock/** * queued_spin_unlock - release a queued spinlock
@@ -21,12 +27,6 @@ static inline void native_queued_spin_unlock(struct qspinlock *lock)smp_store_release (&lock->locked,0 ); } -#ifdef CONFIG_PARAVIRT_SPINLOCKS -extern void native_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); -extern void __pv_init_lock_hash (void ); -extern void __pv_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); -extern void __raw_callee_save___pv_queued_spin_unlock (struct qspinlock *lock); -static inline void queued_spin_lock_slowpath (struct qspinlock *lock,u32 val) {pv_queued_spin_lock_slowpath (lock, val);
@@ -42,11 +42,6 @@ static inline bool vcpu_is_preempted(long cpu) {return pv_vcpu_is_preempted (cpu); } -#else -static inline void queued_spin_unlock (struct qspinlock *lock) -{ -native_queued_spin_unlock (lock); -}#endif #ifdef CONFIG_PARAVIRT
include/asm-generic/qspinlock.h +1 -1
@@ -100,7 +100,7 @@ static __always_inline void queued_spin_unlock(struct qspinlock *lock)/* * unlock() needs release semantics: */ - (void)atomic_sub_return_release(_Q_LOCKED_VAL, &lock->val); + smp_store_release(&lock->locked, 0); }#endif
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Use smp_store_release() in queued_spin_unlock()
locking/qspinlock: Use smp_store_release() in queued_spin_unlock()
Commit-ID: 626e5fbc14358901ddaa90ce510e0fbeab310432 Gitweb: https://git.kernel.org/tip/626e5fbc14358901ddaa90ce510e0fbeab310432 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:24 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:51 +0200
locking/qspinlock: Use smp_store_release() in queued_spin_unlock()
A qspinlock can be unlocked simply by writing zero to the locked byte. This can be implemented in the generic code, so do that and remove the arch-specific override for x86 in the !PV case.
--- arch/x86/include/asm/qspinlock.h | 17 ++++++----------- include/asm-generic/qspinlock.h | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-)
arch/x86/include/asm/qspinlock.h +6 -11
@@ -9,6 +9,12 @@#define _Q_PENDING_LOOPS (1 << 9) +#ifdef CONFIG_PARAVIRT_SPINLOCKS +extern void native_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); +extern void __pv_init_lock_hash (void ); +extern void __pv_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); +extern void __raw_callee_save___pv_queued_spin_unlock (struct qspinlock *lock); +#define queued_spin_unlock queued_spin_unlock/** * queued_spin_unlock - release a queued spinlock
@@ -21,12 +27,6 @@ static inline void native_queued_spin_unlock(struct qspinlock *lock)smp_store_release (&lock->locked,0 ); } -#ifdef CONFIG_PARAVIRT_SPINLOCKS -extern void native_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); -extern void __pv_init_lock_hash (void ); -extern void __pv_queued_spin_lock_slowpath (struct qspinlock *lock,u32 val); -extern void __raw_callee_save___pv_queued_spin_unlock (struct qspinlock *lock); -static inline void queued_spin_lock_slowpath (struct qspinlock *lock,u32 val) {pv_queued_spin_lock_slowpath (lock, val);
@@ -42,11 +42,6 @@ static inline bool vcpu_is_preempted(long cpu) {return pv_vcpu_is_preempted (cpu); } -#else -static inline void queued_spin_unlock (struct qspinlock *lock) -{ -native_queued_spin_unlock (lock); -}#endif #ifdef CONFIG_PARAVIRT
include/asm-generic/qspinlock.h +1 -1
@@ -100,7 +100,7 @@ static __always_inline void queued_spin_unlock(struct qspinlock *lock)/* * unlock() needs release semantics: */ - (void)atomic_sub_return_release(_Q_LOCKED_VAL, &lock->val); + smp_store_release(&lock->locked, 0); }#endif
PATCH v3 11/14 locking/qspinlock: Elide back-to-back RELEASE operations with smp_wmb()
In reply to Will DeaconThe qspinlock slowpath must ensure that the MCS node is fully initialised before it can be reached by another other CPU. This is currently enforced by using a RELEASE operation when updating the tail and also when linking the node into the waitqueue (since the control dependency off xchg_tail is insufficient to enforce sufficient ordering -- see 95bcade33a8a ("locking/qspinlock: Ensure node is initialised before updating prev->next")).
Back-to-back RELEASE operations may be expensive on some architectures, particularly those that implement them using fences under the hood. We can replace the two RELEASE operations with a single smp_wmb() fence and use RELAXED operations for the subsequent publishing of the node.
--- kernel/locking/qspinlock.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)
kernel/locking/qspinlock.c +17 -16
@@ -164,10 +164,10 @@ static __always_inline void clear_pending_set_locked(struct qspinlock *lock) static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) {/* -* Use release semantics to make sure that the MCS node is properly -* initialized before changing the tail code. +* We can use relaxed semantics since the caller ensures that the +* MCS node is properly initialized before updating the tail. */ -return (u32 )xchg_release (&lock->tail, +return (u32 )xchg_relaxed (&lock->tail, tail >> _Q_TAIL_OFFSET) << _Q_TAIL_OFFSET; }
@@ -212,10 +212,11 @@ static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail)for (;;) { new = (val & _Q_LOCKED_PENDING_MASK) | tail;/* -* Use release semantics to make sure that the MCS node is -* properly initialized before changing the tail code. +* We can use relaxed semantics since the caller ensures that +* the MCS node is properly initialized before updating the +* tail. */ - old =atomic_cmpxchg_release (&lock->val, val, new); + old =atomic_cmpxchg_relaxed (&lock->val, val, new);if (old == val)break ;
@@ -388,12 +389,18 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) goto release;/* +* Ensure that the initialisation of @node is complete before we +* publish the updated tail via xchg_tail() and potentially link +* @node into the waitqueue via WRITE_ONCE(prev->next, node) below. +*/ +smp_wmb (); + +/* +* Publish the updated tail. * We have already touched the queueing cacheline; don't bother with * pending stuff. * * p,*,* -> n,*,* -* -* RELEASE, such that the stores to @node must be complete. */ old =xchg_tail (lock, tail); next =NULL ;
@@ -405,14 +412,8 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)if (old & _Q_TAIL_MASK) { prev =decode_tail (old); -/* -* We must ensure that the stores to @node are observed before -* the write to prev->next. The address dependency from -* xchg_tail is not sufficient to ensure this because the read -* component of xchg_tail is unordered with respect to the -* initialisation of @node. -*/ -smp_store_release (&prev->next, node); +/* Link @node into the waitqueue. */ +WRITE_ONCE (prev->next, node);pv_wait_node (node, prev);arch_mcs_spin_lock_contended (&node->locked);
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Elide back-to-back RELEASE operations with smp_wmb()
locking/qspinlock: Elide back-to-back RELEASE operations with smp_wmb()
Commit-ID: 9d4646d14d51d62b967a12452c30ea7edf8dd8fa Gitweb: https://git.kernel.org/tip/9d4646d14d51d62b967a12452c30ea7edf8dd8fa Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:25 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:52 +0200
locking/qspinlock: Elide back-to-back RELEASE operations with smp_wmb()
The qspinlock slowpath must ensure that the MCS node is fully initialised before it can be reached by another other CPU. This is currently enforced by using a RELEASE operation when updating the tail and also when linking the node into the waitqueue, since the control dependency off xchg_tail is insufficient to enforce sufficient ordering, see:
95bcade33a8a ("locking/qspinlock: Ensure node is initialised before updating prev->next")
Back-to-back RELEASE operations may be expensive on some architectures, particularly those that implement them using fences under the hood. We can replace the two RELEASE operations with a single smp_wmb() fence and use RELAXED operations for the subsequent publishing of the node.
--- kernel/locking/qspinlock.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)
kernel/locking/qspinlock.c +17 -16
@@ -164,10 +164,10 @@ static __always_inline void clear_pending_set_locked(struct qspinlock *lock) static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail) {/* -* Use release semantics to make sure that the MCS node is properly -* initialized before changing the tail code. +* We can use relaxed semantics since the caller ensures that the +* MCS node is properly initialized before updating the tail. */ -return (u32 )xchg_release (&lock->tail, +return (u32 )xchg_relaxed (&lock->tail, tail >> _Q_TAIL_OFFSET) << _Q_TAIL_OFFSET; }
@@ -212,10 +212,11 @@ static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail)for (;;) { new = (val & _Q_LOCKED_PENDING_MASK) | tail;/* -* Use release semantics to make sure that the MCS node is -* properly initialized before changing the tail code. +* We can use relaxed semantics since the caller ensures that +* the MCS node is properly initialized before updating the +* tail. */ - old =atomic_cmpxchg_release (&lock->val, val, new); + old =atomic_cmpxchg_relaxed (&lock->val, val, new);if (old == val)break ;
@@ -388,12 +389,18 @@ queue: goto release;/* +* Ensure that the initialisation of @node is complete before we +* publish the updated tail via xchg_tail() and potentially link +* @node into the waitqueue via WRITE_ONCE(prev->next, node) below. +*/ +smp_wmb (); + +/* +* Publish the updated tail. * We have already touched the queueing cacheline; don't bother with * pending stuff. * * p,*,* -> n,*,* -* -* RELEASE, such that the stores to @node must be complete. */ old =xchg_tail (lock, tail); next =NULL ;
@@ -405,14 +412,8 @@ queue:if (old & _Q_TAIL_MASK) { prev =decode_tail (old); -/* -* We must ensure that the stores to @node are observed before -* the write to prev->next. The address dependency from -* xchg_tail is not sufficient to ensure this because the read -* component of xchg_tail is unordered with respect to the -* initialisation of @node. -*/ -smp_store_release (&prev->next, node); +/* Link @node into the waitqueue. */ +WRITE_ONCE (prev->next, node);pv_wait_node (node, prev);arch_mcs_spin_lock_contended (&node->locked);
PATCH v3 12/14 locking/qspinlock: Use try_cmpxchg instead of cmpxchg when locking
In reply to Will DeaconWhen reaching the head of an uncontended queue on the qspinlock slow-path, using a try_cmpxchg instead of a cmpxchg operation to transition the lock work to _Q_LOCKED_VAL generates slightly better code for x86 and pretty much identical code for arm64.
--- kernel/locking/qspinlock.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-)
kernel/locking/qspinlock.c +9 -10
@@ -467,16 +467,15 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* Otherwise, we only need to grab the lock. */ -/* In the PV case we might already have _Q_LOCKED_VAL set */ -if ((val & _Q_TAIL_MASK) == tail) { -/* -* The atomic_cond_read_acquire() call above has provided the -* necessary acquire semantics required for locking. -*/ - old =atomic_cmpxchg_relaxed (&lock->val, val, _Q_LOCKED_VAL); -if (old == val) - goto release;/* No contention */ - } +/* +* In the PV case we might already have _Q_LOCKED_VAL set. +* +* The atomic_cond_read_acquire() call above has provided the +* necessary acquire semantics required for locking. +*/ +if (((val & _Q_TAIL_MASK) == tail) && +atomic_try_cmpxchg_relaxed (&lock->val, &val, _Q_LOCKED_VAL)) + goto release;/* No contention */ /* Either somebody is queued behind us or _Q_PENDING_VAL is set */ set_locked (lock);
Signature · 1 line
2.1.4
tip-bot for Will Deacon
merge bot[tip:locking/core] locking/qspinlock: Use try_cmpxchg() instead of cmpxchg() when locking
locking/qspinlock: Use try_cmpxchg() instead of cmpxchg() when locking
Commit-ID: ae75d9089ff7095d1d1a12c3cd86b21d3eaf3b15 Gitweb: https://git.kernel.org/tip/ae75d9089ff7095d1d1a12c3cd86b21d3eaf3b15 Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:26 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:52 +0200
locking/qspinlock: Use try_cmpxchg() instead of cmpxchg() when locking
When reaching the head of an uncontended queue on the qspinlock slow-path, using a try_cmpxchg() instead of a cmpxchg() operation to transition the lock work to _Q_LOCKED_VAL generates slightly better code for x86 and pretty much identical code for arm64.
--- kernel/locking/qspinlock.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-)
kernel/locking/qspinlock.c +9 -10
@@ -467,16 +467,15 @@ locked:* Otherwise, we only need to grab the lock. */ -/* In the PV case we might already have _Q_LOCKED_VAL set */ -if ((val & _Q_TAIL_MASK) == tail) { -/* -* The atomic_cond_read_acquire() call above has provided the -* necessary acquire semantics required for locking. -*/ - old =atomic_cmpxchg_relaxed (&lock->val, val, _Q_LOCKED_VAL); -if (old == val) - goto release;/* No contention */ - } +/* +* In the PV case we might already have _Q_LOCKED_VAL set. +* +* The atomic_cond_read_acquire() call above has provided the +* necessary acquire semantics required for locking. +*/ +if (((val & _Q_TAIL_MASK) == tail) && +atomic_try_cmpxchg_relaxed (&lock->val, &val, _Q_LOCKED_VAL)) + goto release;/* No contention */ /* Either somebody is queued behind us or _Q_PENDING_VAL is set */ set_locked (lock);
PATCH v3 13/14 locking/qspinlock: Add stat tracking for pending vs slowpath
In reply to Will DeaconFrom: Waiman Long <[email protected]>
Currently, the qspinlock_stat code tracks only statistical counts in the PV qspinlock code. However, it may also be useful to track the number of locking operations done via the pending code vs. the MCS lock queue slowpath for the non-PV case.
The qspinlock stat code is modified to do that. The stat counter pv_lock_slowpath is renamed to lock_slowpath so that it can be used by both the PV and non-PV cases.
--- kernel/locking/qspinlock.c | 14 +++++++++++--- kernel/locking/qspinlock_paravirt.h | 7 +------ kernel/locking/qspinlock_stat.h | 9 ++++++--- 3 files changed, 18 insertions(+), 12 deletions(-)
kernel/locking/qspinlock.c +11 -3
@@ -12,11 +12,11 @@* GNU General Public License for more details. * * (C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P. -* (C) Copyright 2013-2014 Red Hat, Inc. +* (C) Copyright 2013-2014,2018 Red Hat, Inc. * (C) Copyright 2015 Intel Corp. * (C) Copyright 2015 Hewlett-Packard Enterprise Development LP * -* Authors: Waiman Long <[email protected]> +* Authors: Waiman Long <[email protected]> * Peter Zijlstra <[email protected]> */
@@ -33,6 +33,11 @@#include <asm/qspinlock.h> /* +* Include queued spinlock statistics code +*/ +#include "qspinlock_stat.h" + +/* * The basic principle of a queue-based spinlock can best be understood * by studying a classic queue-based spinlock implementation called the * MCS lock. The paper below provides a good description for this kind
@@ -295,7 +300,7 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));if (pv_enabled ()) - goto queue; + goto pv_queue;if (virt_spin_lock (lock))return ;
@@ -348,6 +353,7 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* *,1,0 -> *,0,1 */ clear_pending_set_locked (lock); +qstat_inc (qstat_lock_pending, true);return ; }
@@ -363,6 +369,8 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* queuing. */ queue: +qstat_inc (qstat_lock_slowpath, true); +pv_queue: node =this_cpu_ptr (&mcs_nodes[0 ]); idx = node->count++; tail =encode_tail (smp_processor_id (), idx);
kernel/locking/qspinlock_paravirt.h +1 -6
@@ -56,11 +56,6 @@ struct pv_node { };/* -* Include queued spinlock statistics code -*/ -#include "qspinlock_stat.h" - -/* * Hybrid PV queued/unfair lock * * By replacing the regular queued_spin_trylock() with the function below,
@@ -428,7 +423,7 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node)/* * Tracking # of slowpath locking operations */ -qstat_inc (qstat_pv_lock_slowpath, true); +qstat_inc (qstat_lock_slowpath, true);for (;; waitcnt++) {/*
kernel/locking/qspinlock_stat.h +6 -3
@@ -22,13 +22,14 @@* pv_kick_wake - # of vCPU kicks used for computing pv_latency_wake * pv_latency_kick - average latency (ns) of vCPU kick operation * pv_latency_wake - average latency (ns) from vCPU kick to wakeup -* pv_lock_slowpath - # of locking operations via the slowpath * pv_lock_stealing - # of lock stealing operations * pv_spurious_wakeup - # of spurious wakeups in non-head vCPUs * pv_wait_again - # of wait's after a queue head vCPU kick * pv_wait_early - # of early vCPU wait's * pv_wait_head - # of vCPU wait's at the queue head * pv_wait_node - # of vCPU wait's at a non-head queue node +* lock_pending - # of locking operations via pending code +* lock_slowpath - # of locking operations via MCS lock queue * * Writing to the "reset_counters" file will reset all the above counter * values.
@@ -46,13 +47,14 @@ enum qlock_stats { qstat_pv_kick_wake, qstat_pv_latency_kick, qstat_pv_latency_wake, - qstat_pv_lock_slowpath, qstat_pv_lock_stealing, qstat_pv_spurious_wakeup, qstat_pv_wait_again, qstat_pv_wait_early, qstat_pv_wait_head, qstat_pv_wait_node, + qstat_lock_pending, + qstat_lock_slowpath, qstat_num,/* Total number of statistical counters */ qstat_reset_cnts = qstat_num, };
@@ -73,12 +75,13 @@ static const char * const qstat_names[qstat_num + 1] = { [qstat_pv_spurious_wakeup] = "pv_spurious_wakeup", [qstat_pv_latency_kick] = "pv_latency_kick", [qstat_pv_latency_wake] = "pv_latency_wake", - [qstat_pv_lock_slowpath] = "pv_lock_slowpath", [qstat_pv_lock_stealing] = "pv_lock_stealing", [qstat_pv_wait_again] = "pv_wait_again", [qstat_pv_wait_early] = "pv_wait_early", [qstat_pv_wait_head] = "pv_wait_head", [qstat_pv_wait_node] = "pv_wait_node", + [qstat_lock_pending] = "lock_pending", + [qstat_lock_slowpath] = "lock_slowpath", [qstat_reset_cnts] = "reset_counters", };
Signature · 1 line
2.1.4
tip-bot for Waiman Long
merge bot[tip:locking/core] locking/qspinlock: Add stat tracking for pending vs. slowpath
locking/qspinlock: Add stat tracking for pending vs. slowpath
Commit-ID: 81d3dc9a349b1e61d77106bbb05a6e6dd29b9d5e Gitweb: https://git.kernel.org/tip/81d3dc9a349b1e61d77106bbb05a6e6dd29b9d5e Author: Waiman Long <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:27 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:53 +0200
locking/qspinlock: Add stat tracking for pending vs. slowpath
Currently, the qspinlock_stat code tracks only statistical counts in the PV qspinlock code. However, it may also be useful to track the number of locking operations done via the pending code vs. the MCS lock queue slowpath for the non-PV case.
The qspinlock stat code is modified to do that. The stat counter pv_lock_slowpath is renamed to lock_slowpath so that it can be used by both the PV and non-PV cases.
--- kernel/locking/qspinlock.c | 14 +++++++++++--- kernel/locking/qspinlock_paravirt.h | 7 +------ kernel/locking/qspinlock_stat.h | 9 ++++++--- 3 files changed, 18 insertions(+), 12 deletions(-)
kernel/locking/qspinlock.c +11 -3
@@ -12,11 +12,11 @@* GNU General Public License for more details. * * (C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P. -* (C) Copyright 2013-2014 Red Hat, Inc. +* (C) Copyright 2013-2014,2018 Red Hat, Inc. * (C) Copyright 2015 Intel Corp. * (C) Copyright 2015 Hewlett-Packard Enterprise Development LP * -* Authors: Waiman Long <[email protected]> +* Authors: Waiman Long <[email protected]> * Peter Zijlstra <[email protected]> */
@@ -32,6 +32,11 @@#include <asm/byteorder.h> #include <asm/qspinlock.h> +/* +* Include queued spinlock statistics code +*/ +#include "qspinlock_stat.h" +/* * The basic principle of a queue-based spinlock can best be understood * by studying a classic queue-based spinlock implementation called the
@@ -295,7 +300,7 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)BUILD_BUG_ON (CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));if (pv_enabled ()) - goto queue; + goto pv_queue;if (virt_spin_lock (lock))return ;
@@ -348,6 +353,7 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* *,1,0 -> *,0,1 */ clear_pending_set_locked (lock); +qstat_inc (qstat_lock_pending, true);return ; }
@@ -363,6 +369,8 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)* queuing. */ queue: +qstat_inc (qstat_lock_slowpath, true); +pv_queue: node =this_cpu_ptr (&mcs_nodes[0 ]); idx = node->count++; tail =encode_tail (smp_processor_id (), idx);
kernel/locking/qspinlock_paravirt.h +1 -6
@@ -55,11 +55,6 @@ struct pv_node {u8 state; }; -/* -* Include queued spinlock statistics code -*/ -#include "qspinlock_stat.h" -/* * Hybrid PV queued/unfair lock *
@@ -428,7 +423,7 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node)/* * Tracking # of slowpath locking operations */ -qstat_inc (qstat_pv_lock_slowpath, true); +qstat_inc (qstat_lock_slowpath, true);for (;; waitcnt++) {/*
kernel/locking/qspinlock_stat.h +6 -3
@@ -22,13 +22,14 @@* pv_kick_wake - # of vCPU kicks used for computing pv_latency_wake * pv_latency_kick - average latency (ns) of vCPU kick operation * pv_latency_wake - average latency (ns) from vCPU kick to wakeup -* pv_lock_slowpath - # of locking operations via the slowpath * pv_lock_stealing - # of lock stealing operations * pv_spurious_wakeup - # of spurious wakeups in non-head vCPUs * pv_wait_again - # of wait's after a queue head vCPU kick * pv_wait_early - # of early vCPU wait's * pv_wait_head - # of vCPU wait's at the queue head * pv_wait_node - # of vCPU wait's at a non-head queue node +* lock_pending - # of locking operations via pending code +* lock_slowpath - # of locking operations via MCS lock queue * * Writing to the "reset_counters" file will reset all the above counter * values.
@@ -46,13 +47,14 @@ enum qlock_stats { qstat_pv_kick_wake, qstat_pv_latency_kick, qstat_pv_latency_wake, - qstat_pv_lock_slowpath, qstat_pv_lock_stealing, qstat_pv_spurious_wakeup, qstat_pv_wait_again, qstat_pv_wait_early, qstat_pv_wait_head, qstat_pv_wait_node, + qstat_lock_pending, + qstat_lock_slowpath, qstat_num,/* Total number of statistical counters */ qstat_reset_cnts = qstat_num, };
@@ -73,12 +75,13 @@ static const char * const qstat_names[qstat_num + 1] = { [qstat_pv_spurious_wakeup] = "pv_spurious_wakeup", [qstat_pv_latency_kick] = "pv_latency_kick", [qstat_pv_latency_wake] = "pv_latency_wake", - [qstat_pv_lock_slowpath] = "pv_lock_slowpath", [qstat_pv_lock_stealing] = "pv_lock_stealing", [qstat_pv_wait_again] = "pv_wait_again", [qstat_pv_wait_early] = "pv_wait_early", [qstat_pv_wait_head] = "pv_wait_head", [qstat_pv_wait_node] = "pv_wait_node", + [qstat_lock_pending] = "lock_pending", + [qstat_lock_slowpath] = "lock_slowpath", [qstat_reset_cnts] = "reset_counters", };
PATCH v3 14/14 MAINTAINERS: Add myself as a co-maintainer for LOCKING PRIMITIVES
In reply to Will DeaconI've been heavily involved with concurrency and memory ordering stuff (see ATOMIC INFRASTRUCTURE and LINUX KERNEL MEMORY CONSISTENCY MODEL) and with arm64 now using qrwlock with a view to using qspinlock in the near future, I'm going to continue being involved with the core locking primitives. Reflect this by adding myself as a co-maintainer alongside Ingo and Peter.
--- MAINTAINERS | 1 + 1 file changed, 1 insertion(+)
MAINTAINERS +1 -0
@@ -8328,6 +8328,7 @@ F: Documentation/admin-guide/LSM/LoadPin.rst LOCKING PRIMITIVES M: Peter Zijlstra <[email protected]> M: Ingo Molnar <[email protected]> +M: Will Deacon <[email protected]> L: linux-kernel at vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git locking/core S: Maintained
Signature · 1 line
2.1.4
Peter Zijlstra
maintainerRe: [PATCH v3 14/14] MAINTAINERS: Add myself as a co-maintainer for LOCKING PRIMITIVES
Quoted 6 lines · Will Deacon
I've been heavily involved with concurrency and memory ordering stuff (see ATOMIC INFRASTRUCTURE and LINUX KERNEL MEMORY CONSISTENCY MODEL) and with arm64 now using qrwlock with a view to using qspinlock in the near future, I'm going to continue being involved with the core locking primitives. Reflect this by adding myself as a co-maintainer alongside Ingo and Peter.
I much value your help on this, thanks!
tip-bot for Will Deacon
merge bot[tip:locking/core] MAINTAINERS: Add myself as a co-maintainer for the locking subsystem
In reply to Will DeaconMAINTAINERS: Add myself as a co-maintainer for the locking subsystem
Commit-ID: baa8c6ddf7be33f2b0ddeb68906d668caf646baa Gitweb: https://git.kernel.org/tip/baa8c6ddf7be33f2b0ddeb68906d668caf646baa Author: Will Deacon <[email protected]> AuthorDate: Thu, 26 Apr 2018 11:34:28 +0100 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 27 Apr 2018 09:48:54 +0200
MAINTAINERS: Add myself as a co-maintainer for the locking subsystem
I've been heavily involved with concurrency and memory ordering stuff (see ATOMIC INFRASTRUCTURE and LINUX KERNEL MEMORY CONSISTENCY MODEL) and with arm64 now using qrwlock with a view to using qspinlock in the near future, I'm going to continue being involved with the core locking primitives. Reflect this by adding myself as a co-maintainer alongside Ingo and Peter.
--- MAINTAINERS | 1 + 1 file changed, 1 insertion(+)
MAINTAINERS +1 -0
@@ -8328,6 +8328,7 @@ F: Documentation/admin-guide/LSM/LoadPin.rst LOCKING PRIMITIVES M: Peter Zijlstra <[email protected]> M: Ingo Molnar <[email protected]> +M: Will Deacon <[email protected]> L: [email protected] T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git locking/core S: Maintained
Ingo, please queue.
Ingo Molnar
AppliedQuoted 3 lines · Peter Zijlstra
Ingo, please queue.
Applied to tip:locking/core, thanks guys!
Ingo
Quoted 42 lines · Will Deacon
Hi all,
This is version three of the qspinlock patches I posted previously:
v1: https://lkml.org/lkml/2018/4/5/496
v2: https://lkml.org/lkml/2018/4/11/618Changes since v2 include:
* Fixed bisection issues
* Fixed x86 PV build
* Added patch proposing me as a co-maintainer
* Rebased onto -rc2All feedback welcome,
Will
--->8
Jason Low (1):
locking/mcs: Usesmp_cond_load_acquire()in mcs spin loopWaiman Long (1):
locking/qspinlock: Add stat tracking for pending vs slowpathWill Deacon (12):
barriers: Introducesmp_cond_load_relaxedandatomic_cond_read_relaxedlocking/qspinlock: Merge struct__qspinlockinto struct qspinlock locking/qspinlock: Bound spinning onpending->lockedtransition in
slowpath
locking/qspinlock/x86: Increase_Q_PENDING_LOOPSupper bound locking/qspinlock: Remove unbounded cmpxchg loop from locking slowpath locking/qspinlock: Kill cmpxchg loop when claiming lock from head of
queue
locking/qspinlock: Useatomic_cond_read_acquirelocking/qspinlock: Usesmp_cond_load_relaxedto wait for next node locking/qspinlock: Makequeued_spin_unlockusesmp_store_releaselocking/qspinlock: Elide back-to-back RELEASE operations withsmp_wmb()locking/qspinlock: Use
try_cmpxchginstead of cmpxchg when locking
MAINTAINERS: Add myself as a co-maintainer for LOCKING PRIMITIVES
Quoted 14 lines
MAINTAINERS | 1 + arch/x86/include/asm/qspinlock.h | 21 ++- arch/x86/include/asm/qspinlock_paravirt.h | 3 +- include/asm-generic/atomic-long.h | 2 + include/asm-generic/barrier.h | 27 +++- include/asm-generic/qspinlock.h | 2 +- include/asm-generic/qspinlock_types.h | 32 +++- include/linux/atomic.h | 2 + kernel/locking/mcs_spinlock.h | 10 +- kernel/locking/qspinlock.c | 247 ++++++++++++++---------------- kernel/locking/qspinlock_paravirt.h | 44 ++---- kernel/locking/qspinlock_stat.h | 9 +- 12 files changed, 209 insertions(+), 191 deletions(-)
Other than my comment on patch 5 (which can wait as the code path is unlikely to be used soon), I have no other issue with this patchset.
Cheers,
Longman