MCS Lock: Restructure the MCS lock defines and locking code into its own file
A message that is not in the archive (sent off-list or not yet received): [email protected]
PATCH v5 1/4 MCS Lock: Restructure the MCS lock defines and locking code into its own file
We will need the MCS lock code for doing optimistic spinning for rwsem and queue rwlock. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily.
--- include/linux/mcs_spinlock.h | 64 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mutex.h | 5 ++- kernel/locking/mutex.c | 60 ++++---------------------------------- 3 files changed, 74 insertions(+), 55 deletions(-) create mode 100644 include/linux/mcs_spinlock.h
include/linux/mcs_spinlock.h new file +64 -0
@@ -0,0 +1,64 @@ +/* +* MCS lock defines +* +* This file contains the main data structure and API definitions of MCS lock. +* +* The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock +* with the desirable properties of being fair, and with each cpu trying +* to acquire the lock spinning on a local variable. +* It avoids expensive cache bouncings that common test-and-set spin-lock +* implementations incur. +*/ +#ifndef __LINUX_MCS_SPINLOCK_H +#define __LINUX_MCS_SPINLOCK_H + +struct mcs_spinlock { +struct mcs_spinlock *next; +int locked;/* 1 if lock acquired */ +}; + +/* +* We don't inline mcs_spin_lock() so that perf can correctly account for the +* time spent in this lock function. +*/ +static noinline +void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) +{ +struct mcs_spinlock *prev; + +/* Init node */ + node->locked =0 ; + node->next =NULL ; + + prev =xchg (lock, node); +if (likely (prev ==NULL )) { +/* Lock acquired */ + node->locked =1 ; +return ; + } +ACCESS_ONCE (prev->next) = node; +smp_wmb (); +/* Wait until the lock holder passes the lock down */ +while (!ACCESS_ONCE (node->locked)) +arch_mutex_cpu_relax (); +} + +static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +{ +struct mcs_spinlock *next =ACCESS_ONCE (node->next); + +if (likely (!next)) { +/* +* Release the lock by setting it to NULL +*/ +if (cmpxchg (lock, node,NULL ) == node) +return ; +/* Wait until the next pointer is set */ +while (!(next =ACCESS_ONCE (node->next))) +arch_mutex_cpu_relax (); + } +ACCESS_ONCE (next->locked) =1 ; +smp_wmb (); +} + +#endif /* __LINUX_MCS_SPINLOCK_H */
include/linux/mutex.h +3 -2
@@ -46,6 +46,7 @@* - detects multi-task circular deadlocks and prints out all affected * locks and tasks (and only those tasks) */ +struct mcs_spinlock ;struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count;
@@ -55,7 +56,7 @@ struct mutex {struct task_struct *owner;#endif #ifdef CONFIG_MUTEX_SPIN_ON_OWNER -void *spin_mlock;/* Spinner MCS lock */ +struct mcs_spinlock *mcs_lock;/* Spinner MCS lock */ #endif #ifdef CONFIG_DEBUG_MUTEXES const char *name;
@@ -179,4 +180,4 @@ extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);# define arch_mutex_cpu_relax () cpu_relax()#endif -#endif +#endif /* __LINUX_MUTEX_H */
kernel/locking/mutex.c +7 -53
102 lines not on this page · raw message
Signature · 9 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 6 lines · Tim Chen
We will need the MCS lock code for doing optimistic spinning for rwsem and queue rwlock. Extracting the MCS code from
mutex.cand put into its own file allow us to reuse this code easily.Signed-off-by Tim Chen <[email protected]>Signed-off-by Davidlohr Bueso <[email protected]>
Please see comments below.
Thanx, Paul
Quoted 36 lines
--- include/linux/mcs_spinlock.h | 64 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mutex.h | 5 ++- kernel/locking/mutex.c | 60 ++++---------------------------------- 3 files changed, 74 insertions(+), 55 deletions(-) create mode 100644 include/linux/mcs_spinlock.hinclude/linux/mcs_spinlock.h new file +23
-0@@ -0,0 +1,64 @@ +/* +* MCS lock defines +* +* This file contains the main data structure and API definitions of MCS lock. +* +* The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock +* with the desirable properties of being fair, and with each cpu trying +* to acquire the lock spinning on a local variable. +* It avoids expensive cache bouncings that common test-and-set spin-lock +* implementations incur. +*/ +#ifndef __LINUX_MCS_SPINLOCK_H +#define __LINUX_MCS_SPINLOCK_H + +struct mcs_spinlock { +struct mcs_spinlock *next; +int locked;/* 1 if lock acquired */ +}; + +/* +* We don't inline mcs_spin_lock() so that perf can correctly account for the +* time spent in this lock function. +*/
include/linux/mcs_spinlock.h line 33
Quoted 10 lines
include/linux/mcs_spinlock.h new file +10
-0+static noinline +void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) +{ +struct mcs_spinlock *prev; + +/* Init node */ + node->locked =0 ; + node->next =NULL ; + + prev =xchg (lock, node);
OK, the full memory barriers implied by xchg() ensure that *node will be initialized before the "ACCESS_ONCE(prev->next) = node" below puts the node into the list. This rules out the misordering scenario that Tim Chen called out in message-id <1380322005.3467.186.camel@schen9-DESK> on September 27th.
Assuming of course a corresponding barrier on the lock handoff side.
include/linux/mcs_spinlock.h line 40
Quoted 7 lines
include/linux/mcs_spinlock.h new file +7
-0+if (likely (prev ==NULL )) { +/* Lock acquired */ + node->locked =1 ; +return ; + } +ACCESS_ONCE (prev->next) = node; +smp_wmb ();
I don't see what the above memory barrier does. Here are some things that it cannot be doing:
o Ordering the insertion into the list above with the polling
below. First, smp_wmb() does not order prior writes against later reads, and second misordering is harmless. If we start polling before the insertion is complete, all that happens is that the first few polls have no chance of seeing a lock grant.
o Ordering the polling against the initialization -- the above
xchg() is already doing that for us.
So what is its purpose?
include/linux/mcs_spinlock.h line 43
Quoted 3 lines
include/linux/mcs_spinlock.h new file +3
-0+/* Wait until the lock holder passes the lock down */ +while (!ACCESS_ONCE (node->locked)) +arch_mutex_cpu_relax ();
On the other hand, I don't see how we get away without a barrier here. As written, what prevents the caller's load from ->owner from being reordered with the above load from ->locked? (Perhaps you can argue that such reordering is only a performance problem, but if so we need that argument recorded in comments.)
Of course, if anyone ever tries to use mcs_spin_lock() as a full lock, they will need a memory barrier here to prevent the critical section from leaking out.
Quoted 6 lines
include/linux/mcs_spinlock.h new file +6
-0+} + +static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +{ +struct mcs_spinlock *next =ACCESS_ONCE (node->next); +
include/linux/mcs_spinlock.h line 59
Quoted 10 lines
include/linux/mcs_spinlock.h new file +10
-0+if (likely (!next)) { +/* +* Release the lock by setting it to NULL +*/ +if (cmpxchg (lock, node,NULL ) == node) +return ; +/* Wait until the next pointer is set */ +while (!(next =ACCESS_ONCE (node->next))) +arch_mutex_cpu_relax (); + }
We need a memory barrier somewhere before here in this function, otherwise the critical section can leak out. I do not believe that we can rely on the prohibition against speculative stores that Peter Zijlstra and I have been discussing because that does not provide the transitivity required by locking primitives. I believe that we -could- make the access below be an smp_store_release(), though.
Placing the barrier here (or at least not preceding the initial fetch from node->next) has the advantage of allowing it to pair with the xchg() in mcs_spin_lock(), though given the dependency only an smp_read_barrier_depends() is required for that purpose.
include/linux/mcs_spinlock.h line 61
Quoted 2 lines
include/linux/mcs_spinlock.h new file +2
-0+ACCESS_ONCE (next->locked) =1 ; +smp_wmb ();
I don't see what this barrier does for us. It is ordering the unlock store with what, exactly?
If it really is doing something, we need a big fat comment stating what that is, and checkpatch.pl will be happy to inform you. ;-)
Quoted 147 lines
include/linux/mcs_spinlock.h new file +3
-0+} + +#endif /* __LINUX_MCS_SPINLOCK_H */ include/linux/mutex.h +3
-2@@ -46,6 +46,7 @@* - detects multi-task circular deadlocks and prints out all affected * locks and tasks (and only those tasks) */ +struct mcs_spinlock ;struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count;@@ -55,7 +56,7 @@ struct mutex {struct task_struct *owner;#endif #ifdef CONFIG_MUTEX_SPIN_ON_OWNER -void *spin_mlock;/* Spinner MCS lock */ +struct mcs_spinlock *mcs_lock;/* Spinner MCS lock */ #endif #ifdef CONFIG_DEBUG_MUTEXES const char *name;@@ -179,4 +180,4 @@ extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);# define arch_mutex_cpu_relax () cpu_relax()#endif -#endif +#endif /* __LINUX_MUTEX_H */ kernel/locking/mutex.c +7
-53@@ -25,6 +25,7 @@#include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/debug_locks.h> +#include <linux/mcs_spinlock.h> /* * In the DEBUG case we are using the "NULL fastpath" for mutexes, @@ -52,7 +53,7 @@ __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)INIT_LIST_HEAD (&lock->wait_list);mutex_clear_owner (lock);#ifdef CONFIG_MUTEX_SPIN_ON_OWNER - lock->spin_mlock =NULL ; + lock->mcs_lock =NULL ;#endif debug_mutex_init (lock, name, key);@@ -111,54 +112,7 @@ EXPORT_SYMBOL(mutex_lock);* more or less simultaneously, the spinners need to acquire a MCS lock * first before spinning on the owner field. * -* We don't inline mspin_lock() so that perf can correctly account for the -* time spent in this lock function. */ -struct mspin_node { -struct mspin_node *next ; -int locked;/* 1 if lock acquired */ -}; -#define MLOCK (mutex) ((struct mspin_node **)&((mutex)->spin_mlock)) - -static noinline -void mspin_lock(struct mspin_node **lock, struct mspin_node *node) -{ -struct mspin_node *prev; - -/* Init node */ - node->locked =0 ; - node->next =NULL ; - - prev =xchg (lock, node); -if (likely (prev ==NULL )) { -/* Lock acquired */ - node->locked =1 ; -return ; - } -ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) -arch_mutex_cpu_relax (); -} - -static void mspin_unlock (struct mspin_node **lock,struct mspin_node *node) -{ -struct mspin_node *next =ACCESS_ONCE (node->next); - -if (likely (!next)) { -/* -* Release the lock by setting it to NULL -*/ -if (cmpxchg (lock, node,NULL ) == node) -return ; -/* Wait until the next pointer is set */ -while (!(next =ACCESS_ONCE (node->next))) -arch_mutex_cpu_relax (); - } -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); -}/* * Mutex spinning code migrated from kernel/sched/core.c @@ -448,7 +402,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,for (;;) {struct task_struct *owner; -struct mspin_node node; +struct mcs_spinlock node;if (use_ww_ctx && ww_ctx->acquired >0 ) {struct ww_mutex *ww;@@ -470,10 +424,10 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,* If there's an owner, wait for it to either * release the lock or go to sleep. */ -mspin_lock (MLOCK (lock), &node); +mcs_spin_lock (&lock->mcs_lock, &node); owner =ACCESS_ONCE (lock->owner);if (owner && !mutex_spin_on_owner (lock, owner)) { -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node); goto slowpath; }@@ -488,11 +442,11 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, }mutex_set_owner (lock); -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node);preempt_enable ();return 0 ; } -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node);/* * When there's no owner, we might have preempted between the Signature · 1 line
1.7.4.4
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 10 lines · Paul E. McKenney
Quoted 6 lines · Tim Chen
We will need the MCS lock code for doing optimistic spinning for rwsem and queue rwlock. Extracting the MCS code from
mutex.cand put into its own file allow us to reuse this code easily.Signed-off-by Tim Chen <[email protected]>Signed-off-by Davidlohr Bueso <[email protected]>Please see comments below.
Thanks for reviewing the code.
Quoted 75 lines
Thanx, Paul
Quoted 36 lines
--- include/linux/mcs_spinlock.h | 64 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mutex.h | 5 ++- kernel/locking/mutex.c | 60 ++++---------------------------------- 3 files changed, 74 insertions(+), 55 deletions(-) create mode 100644 include/linux/mcs_spinlock.hinclude/linux/mcs_spinlock.h new file +23
-0@@ -0,0 +1,64 @@ +/* +* MCS lock defines +* +* This file contains the main data structure and API definitions of MCS lock. +* +* The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock +* with the desirable properties of being fair, and with each cpu trying +* to acquire the lock spinning on a local variable. +* It avoids expensive cache bouncings that common test-and-set spin-lock +* implementations incur. +*/ +#ifndef __LINUX_MCS_SPINLOCK_H +#define __LINUX_MCS_SPINLOCK_H + +struct mcs_spinlock { +struct mcs_spinlock *next; +int locked;/* 1 if lock acquired */ +}; + +/* +* We don't inline mcs_spin_lock() so that perf can correctly account for the +* time spent in this lock function. +*/ include/linux/mcs_spinlock.h line 33
Quoted 10 lines
include/linux/mcs_spinlock.h new file +10
-0+static noinline +void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) +{ +struct mcs_spinlock *prev; + +/* Init node */ + node->locked =0 ; + node->next =NULL ; + + prev =xchg (lock, node);OK, the full memory barriers implied by
xchg()ensure that *node will be initialized before the "ACCESS_ONCE(prev->next)= node" below puts the node into the list. This rules out the misordering scenario that Tim Chen called out in message-id <1380322005.3467.186.camel@schen9-DESK> on September 27th.Assuming of course a corresponding barrier on the lock handoff side.
include/linux/mcs_spinlock.h line 40
Quoted 7 lines
include/linux/mcs_spinlock.h new file +7
-0+if (likely (prev ==NULL )) { +/* Lock acquired */ + node->locked =1 ; +return ; + } +ACCESS_ONCE (prev->next) = node; +smp_wmb ();I don't see what the above memory barrier does. Here are some things that it cannot be doing:
o Ordering the insertion into the list above with the polling
below. First,smp_wmb()does not order prior writes against later reads, and second misordering is harmless. If we start polling before the insertion is complete, all that happens is that the first few polls have no chance of seeing a lock grant.
Quoted 4 lines
o Ordering the polling against the initialization -- the above
xchg()is already doing that for us.So what is its purpose?
Agree that the smp_wmb is not needed. It is in the existing mcs code residing in mutex.c and we're re-factoring the code only here and hasn't corrected the memory barrier.
The particular smp_wmb() is removed in Patch 4/4 that corrects the memory barriers.
Quoted 11 lines
include/linux/mcs_spinlock.h line 43
Quoted 4 lines
include/linux/mcs_spinlock.h new file +3
-0+/* Wait until the lock holder passes the lock down */ +while (!ACCESS_ONCE (node->locked)) +arch_mutex_cpu_relax ();On the other hand, I don't see how we get away without a barrier here. As written, what prevents the caller's load from ->owner from being reordered with the above load from ->locked? (Perhaps you can argue that such reordering is only a performance problem, but if so we need that argument recorded in comments.)
Quoted 3 lines
Of course, if anyone ever tries to use
mcs_spin_lock()as a full lock, they will need a memory barrier here to prevent the critical section from leaking out.
Agree too. The appropriate memory barrier is added in Patch 4/4.
Quoted 179 lines
Quoted 7 lines
include/linux/mcs_spinlock.h new file +6
-0+} + +static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +{ +struct mcs_spinlock *next =ACCESS_ONCE (node->next); +include/linux/mcs_spinlock.h line 59
Quoted 10 lines
include/linux/mcs_spinlock.h new file +10
-0+if (likely (!next)) { +/* +* Release the lock by setting it to NULL +*/ +if (cmpxchg (lock, node,NULL ) == node) +return ; +/* Wait until the next pointer is set */ +while (!(next =ACCESS_ONCE (node->next))) +arch_mutex_cpu_relax (); + }We need a memory barrier somewhere before here in this function, otherwise the critical section can leak out. I do not believe that we can rely on the prohibition against speculative stores that Peter Zijlstra and I have been discussing because that does not provide the transitivity required by locking primitives. I believe that we -could- make the access below be an
smp_store_release(), though.Placing the barrier here (or at least not preceding the initial fetch from
node->next) has the advantage of allowing it to pair with thexchg()inmcs_spin_lock(), though given the dependency only ansmp_read_barrier_depends()is required for that purpose.include/linux/mcs_spinlock.h line 61
Quoted 2 lines
include/linux/mcs_spinlock.h new file +2
-0+ACCESS_ONCE (next->locked) =1 ; +smp_wmb ();I don't see what this barrier does for us. It is ordering the unlock store with what, exactly?
If it really is doing something, we need a big fat comment stating what that is, and checkpatch.pl will be happy to inform you. ;-)
Quoted 140 lines
include/linux/mcs_spinlock.h new file +3
-0+} + +#endif /* __LINUX_MCS_SPINLOCK_H */ include/linux/mutex.h +3
-2@@ -46,6 +46,7 @@* - detects multi-task circular deadlocks and prints out all affected * locks and tasks (and only those tasks) */ +struct mcs_spinlock ;struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count;@@ -55,7 +56,7 @@ struct mutex {struct task_struct *owner;#endif #ifdef CONFIG_MUTEX_SPIN_ON_OWNER -void *spin_mlock;/* Spinner MCS lock */ +struct mcs_spinlock *mcs_lock;/* Spinner MCS lock */ #endif #ifdef CONFIG_DEBUG_MUTEXES const char *name;@@ -179,4 +180,4 @@ extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);# define arch_mutex_cpu_relax () cpu_relax()#endif -#endif +#endif /* __LINUX_MUTEX_H */ kernel/locking/mutex.c +7
-53@@ -25,6 +25,7 @@#include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/debug_locks.h> +#include <linux/mcs_spinlock.h> /* * In the DEBUG case we are using the "NULL fastpath" for mutexes, @@ -52,7 +53,7 @@ __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)INIT_LIST_HEAD (&lock->wait_list);mutex_clear_owner (lock);#ifdef CONFIG_MUTEX_SPIN_ON_OWNER - lock->spin_mlock =NULL ; + lock->mcs_lock =NULL ;#endif debug_mutex_init (lock, name, key);@@ -111,54 +112,7 @@ EXPORT_SYMBOL(mutex_lock);* more or less simultaneously, the spinners need to acquire a MCS lock * first before spinning on the owner field. * -* We don't inline mspin_lock() so that perf can correctly account for the -* time spent in this lock function. */ -struct mspin_node { -struct mspin_node *next ; -int locked;/* 1 if lock acquired */ -}; -#define MLOCK (mutex) ((struct mspin_node **)&((mutex)->spin_mlock)) - -static noinline -void mspin_lock(struct mspin_node **lock, struct mspin_node *node) -{ -struct mspin_node *prev; - -/* Init node */ - node->locked =0 ; - node->next =NULL ; - - prev =xchg (lock, node); -if (likely (prev ==NULL )) { -/* Lock acquired */ - node->locked =1 ; -return ; - } -ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) -arch_mutex_cpu_relax (); -} - -static void mspin_unlock (struct mspin_node **lock,struct mspin_node *node) -{ -struct mspin_node *next =ACCESS_ONCE (node->next); - -if (likely (!next)) { -/* -* Release the lock by setting it to NULL -*/ -if (cmpxchg (lock, node,NULL ) == node) -return ; -/* Wait until the next pointer is set */ -while (!(next =ACCESS_ONCE (node->next))) -arch_mutex_cpu_relax (); - } -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); -}/* * Mutex spinning code migrated from kernel/sched/core.c @@ -448,7 +402,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,for (;;) {struct task_struct *owner; -struct mspin_node node; +struct mcs_spinlock node;if (use_ww_ctx && ww_ctx->acquired >0 ) {struct ww_mutex *ww;@@ -470,10 +424,10 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,* If there's an owner, wait for it to either * release the lock or go to sleep. */ -mspin_lock (MLOCK (lock), &node); +mcs_spin_lock (&lock->mcs_lock, &node); owner =ACCESS_ONCE (lock->owner);if (owner && !mutex_spin_on_owner (lock, owner)) { -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node); goto slowpath; }@@ -488,11 +442,11 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, }mutex_set_owner (lock); -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node);preempt_enable ();return 0 ; } -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node);
Quoted 13 lines
Quoted 7 lines
kernel/locking/mutex.c +0
-0/* * When there's no owner, we might have preempted between the Signature · 1 line
1.7.4.4--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 89 lines · Tim Chen
Quoted 10 lines · Paul E. McKenney
Quoted 6 lines · Tim Chen
We will need the MCS lock code for doing optimistic spinning for rwsem and queue rwlock. Extracting the MCS code from
mutex.cand put into its own file allow us to reuse this code easily.Signed-off-by Tim Chen <[email protected]>Signed-off-by Davidlohr Bueso <[email protected]>Please see comments below.
Thanks for reviewing the code.
Quoted 75 lines
Thanx, Paul
Quoted 36 lines
--- include/linux/mcs_spinlock.h | 64 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mutex.h | 5 ++- kernel/locking/mutex.c | 60 ++++---------------------------------- 3 files changed, 74 insertions(+), 55 deletions(-) create mode 100644 include/linux/mcs_spinlock.hinclude/linux/mcs_spinlock.h new file +23
-0@@ -0,0 +1,64 @@ +/* +* MCS lock defines +* +* This file contains the main data structure and API definitions of MCS lock. +* +* The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock +* with the desirable properties of being fair, and with each cpu trying +* to acquire the lock spinning on a local variable. +* It avoids expensive cache bouncings that common test-and-set spin-lock +* implementations incur. +*/ +#ifndef __LINUX_MCS_SPINLOCK_H +#define __LINUX_MCS_SPINLOCK_H + +struct mcs_spinlock { +struct mcs_spinlock *next; +int locked;/* 1 if lock acquired */ +}; + +/* +* We don't inline mcs_spin_lock() so that perf can correctly account for the +* time spent in this lock function. +*/ include/linux/mcs_spinlock.h line 33
Quoted 10 lines
include/linux/mcs_spinlock.h new file +10
-0+static noinline +void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) +{ +struct mcs_spinlock *prev; + +/* Init node */ + node->locked =0 ; + node->next =NULL ; + + prev =xchg (lock, node);OK, the full memory barriers implied by
xchg()ensure that *node will be initialized before the "ACCESS_ONCE(prev->next)= node" below puts the node into the list. This rules out the misordering scenario that Tim Chen called out in message-id <1380322005.3467.186.camel@schen9-DESK> on September 27th.Assuming of course a corresponding barrier on the lock handoff side.
include/linux/mcs_spinlock.h line 40
Quoted 7 lines
include/linux/mcs_spinlock.h new file +7
-0+if (likely (prev ==NULL )) { +/* Lock acquired */ + node->locked =1 ; +return ; + } +ACCESS_ONCE (prev->next) = node; +smp_wmb ();I don't see what the above memory barrier does. Here are some things that it cannot be doing:
o Ordering the insertion into the list above with the polling
below. First,smp_wmb()does not order prior writes against later reads, and second misordering is harmless. If we start polling before the insertion is complete, all that happens is that the first few polls have no chance of seeing a lock grant.
Quoted 8 lines
Quoted 4 lines
o Ordering the polling against the initialization -- the above
xchg()is already doing that for us.So what is its purpose?
Agree that the
smp_wmbis not needed. It is in the existing mcs code residing inmutex.cand we're re-factoring the code only here and hasn't corrected the memory barrier.
Ah, so I should have been more aggressive about reviewing some time back, then... ;-)
Thanx, Paul
Quoted 214 lines
The particular
smp_wmb()is removed in Patch 4/4 that corrects the memory barriers.Quoted 11 lines
include/linux/mcs_spinlock.h line 43
Quoted 4 lines
include/linux/mcs_spinlock.h new file +3
-0+/* Wait until the lock holder passes the lock down */ +while (!ACCESS_ONCE (node->locked)) +arch_mutex_cpu_relax ();On the other hand, I don't see how we get away without a barrier here. As written, what prevents the caller's load from ->owner from being reordered with the above load from ->locked? (Perhaps you can argue that such reordering is only a performance problem, but if so we need that argument recorded in comments.)
Quoted 3 lines
Of course, if anyone ever tries to use
mcs_spin_lock()as a full lock, they will need a memory barrier here to prevent the critical section from leaking out.Agree too. The appropriate memory barrier is added in Patch 4/4.
Quoted 192 lines
Quoted 7 lines
include/linux/mcs_spinlock.h new file +6
-0+} + +static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +{ +struct mcs_spinlock *next =ACCESS_ONCE (node->next); +include/linux/mcs_spinlock.h line 59
Quoted 10 lines
include/linux/mcs_spinlock.h new file +10
-0+if (likely (!next)) { +/* +* Release the lock by setting it to NULL +*/ +if (cmpxchg (lock, node,NULL ) == node) +return ; +/* Wait until the next pointer is set */ +while (!(next =ACCESS_ONCE (node->next))) +arch_mutex_cpu_relax (); + }We need a memory barrier somewhere before here in this function, otherwise the critical section can leak out. I do not believe that we can rely on the prohibition against speculative stores that Peter Zijlstra and I have been discussing because that does not provide the transitivity required by locking primitives. I believe that we -could- make the access below be an
smp_store_release(), though.Placing the barrier here (or at least not preceding the initial fetch from
node->next) has the advantage of allowing it to pair with thexchg()inmcs_spin_lock(), though given the dependency only ansmp_read_barrier_depends()is required for that purpose.include/linux/mcs_spinlock.h line 61
Quoted 2 lines
include/linux/mcs_spinlock.h new file +2
-0+ACCESS_ONCE (next->locked) =1 ; +smp_wmb ();I don't see what this barrier does for us. It is ordering the unlock store with what, exactly?
If it really is doing something, we need a big fat comment stating what that is, and checkpatch.pl will be happy to inform you. ;-)
Quoted 147 lines
include/linux/mcs_spinlock.h new file +3
-0+} + +#endif /* __LINUX_MCS_SPINLOCK_H */ include/linux/mutex.h +3
-2@@ -46,6 +46,7 @@* - detects multi-task circular deadlocks and prints out all affected * locks and tasks (and only those tasks) */ +struct mcs_spinlock ;struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count;@@ -55,7 +56,7 @@ struct mutex {struct task_struct *owner;#endif #ifdef CONFIG_MUTEX_SPIN_ON_OWNER -void *spin_mlock;/* Spinner MCS lock */ +struct mcs_spinlock *mcs_lock;/* Spinner MCS lock */ #endif #ifdef CONFIG_DEBUG_MUTEXES const char *name;@@ -179,4 +180,4 @@ extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);# define arch_mutex_cpu_relax () cpu_relax()#endif -#endif +#endif /* __LINUX_MUTEX_H */ kernel/locking/mutex.c +7
-53@@ -25,6 +25,7 @@#include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/debug_locks.h> +#include <linux/mcs_spinlock.h> /* * In the DEBUG case we are using the "NULL fastpath" for mutexes, @@ -52,7 +53,7 @@ __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)INIT_LIST_HEAD (&lock->wait_list);mutex_clear_owner (lock);#ifdef CONFIG_MUTEX_SPIN_ON_OWNER - lock->spin_mlock =NULL ; + lock->mcs_lock =NULL ;#endif debug_mutex_init (lock, name, key);@@ -111,54 +112,7 @@ EXPORT_SYMBOL(mutex_lock);* more or less simultaneously, the spinners need to acquire a MCS lock * first before spinning on the owner field. * -* We don't inline mspin_lock() so that perf can correctly account for the -* time spent in this lock function. */ -struct mspin_node { -struct mspin_node *next ; -int locked;/* 1 if lock acquired */ -}; -#define MLOCK (mutex) ((struct mspin_node **)&((mutex)->spin_mlock)) - -static noinline -void mspin_lock(struct mspin_node **lock, struct mspin_node *node) -{ -struct mspin_node *prev; - -/* Init node */ - node->locked =0 ; - node->next =NULL ; - - prev =xchg (lock, node); -if (likely (prev ==NULL )) { -/* Lock acquired */ - node->locked =1 ; -return ; - } -ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) -arch_mutex_cpu_relax (); -} - -static void mspin_unlock (struct mspin_node **lock,struct mspin_node *node) -{ -struct mspin_node *next =ACCESS_ONCE (node->next); - -if (likely (!next)) { -/* -* Release the lock by setting it to NULL -*/ -if (cmpxchg (lock, node,NULL ) == node) -return ; -/* Wait until the next pointer is set */ -while (!(next =ACCESS_ONCE (node->next))) -arch_mutex_cpu_relax (); - } -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); -}/* * Mutex spinning code migrated from kernel/sched/core.c @@ -448,7 +402,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,for (;;) {struct task_struct *owner; -struct mspin_node node; +struct mcs_spinlock node;if (use_ww_ctx && ww_ctx->acquired >0 ) {struct ww_mutex *ww;@@ -470,10 +424,10 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,* If there's an owner, wait for it to either * release the lock or go to sleep. */ -mspin_lock (MLOCK (lock), &node); +mcs_spin_lock (&lock->mcs_lock, &node); owner =ACCESS_ONCE (lock->owner);if (owner && !mutex_spin_on_owner (lock, owner)) { -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node); goto slowpath; }@@ -488,11 +442,11 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, }mutex_set_owner (lock); -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node);preempt_enable ();return 0 ; } -mspin_unlock (MLOCK (lock), &node); +mcs_spin_unlock (&lock->mcs_lock, &node);/* * When there's no owner, we might have preempted between the Signature · 1 line
1.7.4.4--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
PATCH v5 2/4 MCS Lock: optimizations and extra comments
In reply to a message not in the archiveFrom: Jason Low <[email protected]>
Remove unnecessary operation and make the cmpxchg(lock, node, NULL) == node check in mcs_spin_unlock() likely() as it is likely that a race did not occur most of the time.
Also add in more comments describing how the local node is used in MCS locks.
--- include/linux/mcs_spinlock.h | 13 +++++++++++-- 1 files changed, 11 insertions(+), 2 deletions(-)
include/linux/mcs_spinlock.h +11 -2
@@ -18,6 +18,12 @@ struct mcs_spinlock { };/* +* In order to acquire the lock, the caller should declare a local node and +* pass a reference of the node to this function in addition to the lock. +* If the lock has already been acquired, then this will proceed to spin +* on this node->locked until the previous lock holder sets the node->locked +* in mcs_spin_unlock(). +* * We don't inline mcs_spin_lock() so that perf can correctly account for the * time spent in this lock function. */
@@ -33,7 +39,6 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) prev =xchg (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ - node->locked =1 ;return ; }ACCESS_ONCE (prev->next) = node;
@@ -43,6 +48,10 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)arch_mutex_cpu_relax (); } +/* +* Releases the lock. The caller should pass in the corresponding node that +* was used to acquire the lock. +*/ static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);
@@ -51,7 +60,7 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod/* * Release the lock by setting it to NULL */ -if (cmpxchg (lock, node,NULL ) == node) +if (likely (cmpxchg (lock, node,NULL ) == node))return ;/* Wait until the next pointer is set */ while (!(next =ACCESS_ONCE (node->next)))
Signature · 9 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 2/4] MCS Lock: optimizations and extra comments
Quoted 32 lines · Tim Chen
From: Jason Low <[email protected]>
Remove unnecessary operation and make the
cmpxchg(lock, node, NULL)== node check inmcs_spin_unlock()likely()as it is likely that a race did not occur most of the time.Also add in more comments describing how the local node is used in MCS locks.
Reviewed-by Tim Chen <[email protected]>Signed-off-by Jason Low <[email protected]>--- include/linux/mcs_spinlock.h | 13 +++++++++++-- 1 files changed, 11 insertions(+), 2 deletions(-)include/linux/mcs_spinlock.h +6
-0@@ -18,6 +18,12 @@ struct mcs_spinlock { };/* +* In order to acquire the lock, the caller should declare a local node and +* pass a reference of the node to this function in addition to the lock. +* If the lock has already been acquired, then this will proceed to spin +* on this node->locked until the previous lock holder sets the node->locked +* in mcs_spin_unlock(). +* * We don't inline mcs_spin_lock() so that perf can correctly account for the * time spent in this lock function. */
include/linux/mcs_spinlock.h void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 36
Quoted 4 lines
include/linux/mcs_spinlock.h +0
-1@@ -33,7 +39,6 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) prev =xchg (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ - node->locked =1 ;
Agreed, no one looks at this field in this case, so no need to initialize it, unless for debug purposes.
Quoted 15 lines
include/linux/mcs_spinlock.h +4
-0return ; }ACCESS_ONCE (prev->next) = node;@@ -43,6 +48,10 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)arch_mutex_cpu_relax (); } +/* +* Releases the lock. The caller should pass in the corresponding node that +* was used to acquire the lock. +*/ static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);
include/linux/mcs_spinlock.h static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod · line 63
Quoted 5 lines
include/linux/mcs_spinlock.h +1
-1@@ -51,7 +60,7 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod/* * Release the lock by setting it to NULL */ -if (cmpxchg (lock, node,NULL ) == node) +if (likely (cmpxchg (lock, node,NULL ) == node))
Agreed here as well. Takes a narrow race to hit this.
So, did your testing exercise this path? If the answer is "yes", and if the issues that I called out in patch #1 are resolved:
Quoted 8 lines
include/linux/mcs_spinlock.h +0
-0return ;/* Wait until the next pointer is set */ while (!(next =ACCESS_ONCE (node->next)))Signature · 1 line
1.7.4.4
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 2/4] MCS Lock: optimizations and extra comments
Quoted 64 lines · Paul E. McKenney
Quoted 32 lines · Tim Chen
From: Jason Low <[email protected]>
Remove unnecessary operation and make the
cmpxchg(lock, node, NULL)== node check inmcs_spin_unlock()likely()as it is likely that a race did not occur most of the time.Also add in more comments describing how the local node is used in MCS locks.
Reviewed-by Tim Chen <[email protected]>Signed-off-by Jason Low <[email protected]>--- include/linux/mcs_spinlock.h | 13 +++++++++++-- 1 files changed, 11 insertions(+), 2 deletions(-)include/linux/mcs_spinlock.h +6
-0@@ -18,6 +18,12 @@ struct mcs_spinlock { };/* +* In order to acquire the lock, the caller should declare a local node and +* pass a reference of the node to this function in addition to the lock. +* If the lock has already been acquired, then this will proceed to spin +* on this node->locked until the previous lock holder sets the node->locked +* in mcs_spin_unlock(). +* * We don't inline mcs_spin_lock() so that perf can correctly account for the * time spent in this lock function. */ include/linux/mcs_spinlock.h void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 36
Quoted 4 lines
include/linux/mcs_spinlock.h +0
-1@@ -33,7 +39,6 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) prev =xchg (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ - node->locked =1 ;Agreed, no one looks at this field in this case, so no need to initialize it, unless for debug purposes.
Quoted 15 lines
include/linux/mcs_spinlock.h +4
-0return ; }ACCESS_ONCE (prev->next) = node;@@ -43,6 +48,10 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)arch_mutex_cpu_relax (); } +/* +* Releases the lock. The caller should pass in the corresponding node that +* was used to acquire the lock. +*/ static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);include/linux/mcs_spinlock.h static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod · line 63
Quoted 5 lines
include/linux/mcs_spinlock.h +1
-1@@ -51,7 +60,7 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod/* * Release the lock by setting it to NULL */ -if (cmpxchg (lock, node,NULL ) == node) +if (likely (cmpxchg (lock, node,NULL ) == node))Agreed here as well. Takes a narrow race to hit this.
Quoted 2 lines
So, did your testing exercise this path? If the answer is "yes", and if the issues that I called out in patch #1 are resolved:
I haven't instrumented the code to check the hit rate of this path. But the slow path probably will only get hit in some cases under heavy contention.
Quoted 17 lines
Reviewed-by Paul E. McKenney <[email protected]>Quoted 8 lines
include/linux/mcs_spinlock.h +0
-0return ;/* Wait until the next pointer is set */ while (!(next =ACCESS_ONCE (node->next)))Signature · 1 line
1.7.4.4--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [PATCH v5 2/4] MCS Lock: optimizations and extra comments
In reply to Paul E. McKenneyQuoted 18 lines · Paul E. McKenney
Quoted 9 lines · Tim Chen
include/linux/mcs_spinlock.h +4
-0+/* +* Releases the lock. The caller should pass in the corresponding node that +* was used to acquire the lock. +*/ static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);include/linux/mcs_spinlock.h static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod · line 63
Quoted 5 lines
include/linux/mcs_spinlock.h +1
-1@@ -51,7 +60,7 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod/* * Release the lock by setting it to NULL */ -if (cmpxchg (lock, node,NULL ) == node) +if (likely (cmpxchg (lock, node,NULL ) == node))Agreed here as well. Takes a narrow race to hit this.
Quoted 1 line
So, did your testing exercise this path? If the answer is "yes",
Paul,
I did some instrumentation and confirmed that the path in question has been exercised. So this patch should be okay.
Tim
Quoted 8 lines
and if the issues that I called out in patch #1 are resolved:
Reviewed-by Paul E. McKenney <[email protected]>Quoted 4 lines
include/linux/mcs_spinlock.h +0
-0return ;/* Wait until the next pointer is set */ while (!(next =ACCESS_ONCE (node->next)))
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 2/4] MCS Lock: optimizations and extra comments
Quoted 19 lines · Tim Chen
Quoted 18 lines · Paul E. McKenney
Quoted 9 lines · Tim Chen
include/linux/mcs_spinlock.h +4
-0+/* +* Releases the lock. The caller should pass in the corresponding node that +* was used to acquire the lock. +*/ static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);include/linux/mcs_spinlock.h static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod · line 63
Quoted 5 lines
include/linux/mcs_spinlock.h +1
-1@@ -51,7 +60,7 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod/* * Release the lock by setting it to NULL */ -if (cmpxchg (lock, node,NULL ) == node) +if (likely (cmpxchg (lock, node,NULL ) == node))Agreed here as well. Takes a narrow race to hit this.
Quoted 7 lines
Quoted 1 line
So, did your testing exercise this path? If the answer is "yes",
Paul,
I did some instrumentation and confirmed that the path in question has been exercised. So this patch should be okay.
Very good!
Thanx, Paul
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v5 3/4 MCS Lock: Move mcs_lock/unlock function into its own file
In reply to a message not in the archiveFrom: Waiman Long <[email protected]>
The following changes are made:
1) Create a new mcs_spinlock.c file to contain the mcs_spin_lock() and mcs_spin_unlock() function.
2) Include a number of prerequisite header files and define arch_mutex_cpu_relax(), if not previously defined so the mcs functions can be compiled for multiple architecture without causing problems.
--- include/linux/mcs_spinlock.h | 56 ++------------------ kernel/locking/Makefile | 6 +- .../locking/mcs_spinlock.c | 31 ++++++----- 3 files changed, 23 insertions(+), 70 deletions(-) copy include/linux/mcs_spinlock.h => kernel/locking/mcs_spinlock.c (78%)
include/linux/mcs_spinlock.h +4 -52
@@ -17,57 +17,9 @@ struct mcs_spinlock { int locked; /* 1 if lock acquired */ }; -/* -* In order to acquire the lock, the caller should declare a local node and -* pass a reference of the node to this function in addition to the lock. -* If the lock has already been acquired, then this will proceed to spin -* on this node->locked until the previous lock holder sets the node->locked -* in mcs_spin_unlock(). -* -* We don't inline mcs_spin_lock() so that perf can correctly account for the -* time spent in this lock function. -*/ -static noinline -void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) -{ -struct mcs_spinlock *prev; - -/* Init node */ - node->locked =0 ; - node->next =NULL ; - - prev =xchg (lock, node); -if (likely (prev ==NULL )) { -/* Lock acquired */ -return ; - } -ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) -arch_mutex_cpu_relax (); -} - -/* -* Releases the lock. The caller should pass in the corresponding node that -* was used to acquire the lock. -*/ -static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) -{ -struct mcs_spinlock *next =ACCESS_ONCE (node->next); - -if (likely (!next)) { -/* -* Release the lock by setting it to NULL -*/ -if (likely (cmpxchg (lock, node,NULL ) == node)) -return ; -/* Wait until the next pointer is set */ -while (!(next =ACCESS_ONCE (node->next))) -arch_mutex_cpu_relax (); - } -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); -} +extern +void mcs_spin_lock (struct mcs_spinlock **lock,struct mcs_spinlock *node); +extern +void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node);#endif /* __LINUX_MCS_SPINLOCK_H */
kernel/locking/Makefile +3 -3
@@ -13,12 +13,12 @@ obj-$(CONFIG_LOCKDEP) += lockdep.o ifeq ($(CONFIG_PROC_FS),y) obj-$(CONFIG_LOCKDEP) += lockdep_proc.o endif -obj-$(CONFIG_SMP) += spinlock.o -obj-$(CONFIG_PROVE_LOCKING) += spinlock.o +obj-$(CONFIG_SMP) += spinlock.o mcs_spinlock.o +obj-$(CONFIG_PROVE_LOCKING) += spinlock.o mcs_spinlock.o obj-$(CONFIG_RT_MUTEXES) += rtmutex.o obj-$(CONFIG_DEBUG_RT_MUTEXES) += rtmutex-debug.o obj-$(CONFIG_RT_MUTEX_TESTER) += rtmutex-tester.o -obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock.o +obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock.o mcs_spinlock.o obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock_debug.o obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem-xadd.o
kernel/locking/mcs_spinlock.c renamed from include/linux/mcs_spinlock.h +16 -15
@@ -1,7 +1,5 @@/* -* MCS lock defines -* -* This file contains the main data structure and API definitions of MCS lock. +* MCS lock * * The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock * with the desirable properties of being fair, and with each cpu trying
@@ -9,13 +7,20 @@* It avoids expensive cache bouncings that common test-and-set spin-lock * implementations incur. */ -#ifndef __LINUX_MCS_SPINLOCK_H -#define __LINUX_MCS_SPINLOCK_H +/* +* asm/processor.h may define arch_mutex_cpu_relax(). +* If it is not defined, cpu_relax() will be used. +*/ +#include <asm/barrier.h> +#include <asm/cmpxchg.h> +#include <asm/processor.h> +#include <linux/compiler.h> +#include <linux/mcs_spinlock.h> +#include <linux/export.h> -struct mcs_spinlock { -struct mcs_spinlock *next; -int locked;/* 1 if lock acquired */ -}; +#ifndef arch_mutex_cpu_relax +# define arch_mutex_cpu_relax () cpu_relax() +#endif /* * In order to acquire the lock, the caller should declare a local node and
@@ -23,11 +28,7 @@ struct mcs_spinlock {* If the lock has already been acquired, then this will proceed to spin * on this node->locked until the previous lock holder sets the node->locked * in mcs_spin_unlock(). -* -* We don't inline mcs_spin_lock() so that perf can correctly account for the -* time spent in this lock function. */ -static noinline void mcs_spin_lock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *prev;
@@ -47,6 +48,7 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)while (!ACCESS_ONCE (node->locked))arch_mutex_cpu_relax (); } +EXPORT_SYMBOL_GPL (mcs_spin_lock);/* * Releases the lock. The caller should pass in the corresponding node that
@@ -69,5 +71,4 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nodACCESS_ONCE (next->locked) =1 ;smp_wmb (); } - -#endif /* __LINUX_MCS_SPINLOCK_H */ +EXPORT_SYMBOL_GPL (mcs_spin_unlock);
Signature · 1 line
1.7.4.4
Re: [PATCH v5 3/4] MCS Lock: Move mcs_lock/unlock function into its own file
Quoted 11 lines · Tim Chen
From: Waiman Long <[email protected]>
The following changes are made:
1) Create a new
mcs_spinlock.cfile to contain themcs_spin_lock()andmcs_spin_unlock()function.
2) Include a number of prerequisite header files and definearch_mutex_cpu_relax(), if not previously defined so the mcs functions can be compiled for multiple architecture without causing problems.
Quoted 2 lines
Signed-off-by Waiman Long <[email protected]>Signed-off-by Tim Chen <[email protected]>
Assuming issues called out in patch #1 are resolved and testing called out for patch #2 has happened:
Quoted 168 lines
--- include/linux/mcs_spinlock.h | 56 ++------------------ kernel/locking/Makefile | 6 +- .../locking/mcs_spinlock.c | 31 ++++++----- 3 files changed, 23 insertions(+), 70 deletions(-) copy include/linux/mcs_spinlock.h => kernel/locking/mcs_spinlock.c (78%)include/linux/mcs_spinlock.h +4
-52@@ -17,57 +17,9 @@ struct mcs_spinlock { int locked; /* 1 if lock acquired */ }; -/* -* In order to acquire the lock, the caller should declare a local node and -* pass a reference of the node to this function in addition to the lock. -* If the lock has already been acquired, then this will proceed to spin -* on this node->locked until the previous lock holder sets the node->locked -* in mcs_spin_unlock(). -* -* We don't inline mcs_spin_lock() so that perf can correctly account for the -* time spent in this lock function. -*/ -static noinline -void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) -{ -struct mcs_spinlock *prev; - -/* Init node */ - node->locked =0 ; - node->next =NULL ; - - prev =xchg (lock, node); -if (likely (prev ==NULL )) { -/* Lock acquired */ -return ; - } -ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) -arch_mutex_cpu_relax (); -} - -/* -* Releases the lock. The caller should pass in the corresponding node that -* was used to acquire the lock. -*/ -static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) -{ -struct mcs_spinlock *next =ACCESS_ONCE (node->next); - -if (likely (!next)) { -/* -* Release the lock by setting it to NULL -*/ -if (likely (cmpxchg (lock, node,NULL ) == node)) -return ; -/* Wait until the next pointer is set */ -while (!(next =ACCESS_ONCE (node->next))) -arch_mutex_cpu_relax (); - } -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); -} +extern +void mcs_spin_lock (struct mcs_spinlock **lock,struct mcs_spinlock *node); +extern +void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node);#endif /* __LINUX_MCS_SPINLOCK_H */ kernel/locking/Makefile +3
-3@@ -13,12 +13,12 @@ obj-$(CONFIG_LOCKDEP) += lockdep.o ifeq ($(CONFIG_PROC_FS),y) obj-$(CONFIG_LOCKDEP) += lockdep_proc.o endif -obj-$(CONFIG_SMP) += spinlock.o -obj-$(CONFIG_PROVE_LOCKING) += spinlock.o +obj-$(CONFIG_SMP) += spinlock.o mcs_spinlock.o +obj-$(CONFIG_PROVE_LOCKING) += spinlock.o mcs_spinlock.o obj-$(CONFIG_RT_MUTEXES) += rtmutex.o obj-$(CONFIG_DEBUG_RT_MUTEXES) += rtmutex-debug.o obj-$(CONFIG_RT_MUTEX_TESTER) += rtmutex-tester.o -obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock.o +obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock.o mcs_spinlock.o obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock_debug.o obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem-xadd.okernel/locking/mcs_spinlock.c renamed from include/linux/mcs_spinlock.h +16
-15@@ -1,7 +1,5 @@/* -* MCS lock defines -* -* This file contains the main data structure and API definitions of MCS lock. +* MCS lock * * The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock * with the desirable properties of being fair, and with each cpu trying @@ -9,13 +7,20 @@* It avoids expensive cache bouncings that common test-and-set spin-lock * implementations incur. */ -#ifndef __LINUX_MCS_SPINLOCK_H -#define __LINUX_MCS_SPINLOCK_H +/* +* asm/processor.h may define arch_mutex_cpu_relax(). +* If it is not defined, cpu_relax() will be used. +*/ +#include <asm/barrier.h> +#include <asm/cmpxchg.h> +#include <asm/processor.h> +#include <linux/compiler.h> +#include <linux/mcs_spinlock.h> +#include <linux/export.h> -struct mcs_spinlock { -struct mcs_spinlock *next; -int locked;/* 1 if lock acquired */ -}; +#ifndef arch_mutex_cpu_relax +# define arch_mutex_cpu_relax () cpu_relax() +#endif /* * In order to acquire the lock, the caller should declare a local node and @@ -23,11 +28,7 @@ struct mcs_spinlock {* If the lock has already been acquired, then this will proceed to spin * on this node->locked until the previous lock holder sets the node->locked * in mcs_spin_unlock(). -* -* We don't inline mcs_spin_lock() so that perf can correctly account for the -* time spent in this lock function. */ -static noinline void mcs_spin_lock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *prev;@@ -47,6 +48,7 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)while (!ACCESS_ONCE (node->locked))arch_mutex_cpu_relax (); } +EXPORT_SYMBOL_GPL (mcs_spin_lock);/* * Releases the lock. The caller should pass in the corresponding node that @@ -69,5 +71,4 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nodACCESS_ONCE (next->locked) =1 ;smp_wmb (); } - -#endif /* __LINUX_MCS_SPINLOCK_H */ +EXPORT_SYMBOL_GPL (mcs_spin_unlock);Signature · 1 line
1.7.4.4
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v5 4/4 MCS Lock: Barrier corrections
In reply to a message not in the archiveFrom: Waiman Long <[email protected]>
This patch corrects the way memory barriers are used in the MCS lock with smp_load_acquire and smp_store_release fucnction.
It removes ones that are not needed.
It uses architecture specific load-acquire and store-release primitives for synchronization, if available. Generic implementations are provided in case they are not defined even though they may not be optimal. These generic implementation could be removed later on once changes are made in all the relevant header files.
--- kernel/locking/mcs_spinlock.c | 48 +++++++++++++++++++++++++++++++++++------ 1 files changed, 41 insertions(+), 7 deletions(-)
kernel/locking/mcs_spinlock.c +41 -7
@@ -23,6 +23,31 @@#endif /* +* Fall back to use the regular atomic operations and memory barrier if +* the acquire/release versions are not defined. +*/ +#ifndef xchg_acquire +# define xchg_acquire (p, v) xchg(p, v) +#endif + +#ifndef smp_load_acquire +# define smp_load_acquire (p) \ + ({ \ + typeof(*p) __v = ACCESS_ONCE(*(p)); \ + smp_mb(); \ + __v; \ + }) +#endif + +#ifndef smp_store_release +# define smp_store_release (p, v) \ + do { \ + smp_mb(); \ + ACCESS_ONCE(*(p)) = v; \ + } while (0) +#endif + +/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin
@@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax (); }EXPORT_SYMBOL_GPL (mcs_spin_lock);
@@ -54,7 +83,7 @@ EXPORT_SYMBOL_GPL(mcs_spin_lock);* Releases the lock. The caller should pass in the corresponding node that * was used to acquire the lock. */ -static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);
@@ -68,7 +97,12 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nodwhile (!(next =ACCESS_ONCE (node->next)))arch_mutex_cpu_relax (); } -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); +/* +* Pass lock to next waiter. +* smp_store_release() provides a memory barrier to ensure +* all operations in the critical section has been completed +* before unlocking. +*/ +smp_store_release (&next->locked ,1 ); }EXPORT_SYMBOL_GPL (mcs_spin_unlock);
Signature · 8 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Hello,
Quoted 41 lines · Tim Chen
From: Waiman Long <[email protected]>
This patch corrects the way memory barriers are used in the MCS lock with
smp_load_acquireandsmp_store_releasefucnction.
It removes ones that are not needed.It uses architecture specific load-acquire and store-release primitives for synchronization, if available. Generic implementations are provided in case they are not defined even though they may not be optimal. These generic implementation could be removed later on once changes are made in all the relevant header files.
Suggested-by Michel Lespinasse <[email protected]>Signed-off-by Waiman Long <[email protected]>Signed-off-by Jason Low <[email protected]>Signed-off-by Tim Chen <[email protected]>--- kernel/locking/mcs_spinlock.c | 48 +++++++++++++++++++++++++++++++++++------ 1 files changed, 41 insertions(+), 7 deletions(-)kernel/locking/mcs_spinlock.c +13
-0@@ -23,6 +23,31 @@#endif /* +* Fall back to use the regular atomic operations and memory barrier if +* the acquire/release versions are not defined. +*/ +#ifndef xchg_acquire +# define xchg_acquire (p, v) xchg(p, v) +#endif + +#ifndef smp_load_acquire +# define smp_load_acquire (p) \ + ({ \ + typeof(*p) __v = ACCESS_ONCE(*(p)); \ + smp_mb(); \ + __v; \
kernel/locking/mcs_spinlock.c line 48
Quoted 10 lines
kernel/locking/mcs_spinlock.c +10
-0+ }) +#endif + +#ifndef smp_store_release +# define smp_store_release (p, v) \ + do { \ + smp_mb(); \ + ACCESS_ONCE(*(p)) = v; \ + } while (0) +#endif
PeterZ already has a series implementing acquire/release accessors, so you should probably take a look at that rather than rolling your own here.
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 16 lines
kernel/locking/mcs_spinlock.c +3
-1+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node;
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 10 lines
kernel/locking/mcs_spinlock.c +6
-3-smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();
After a chat with some micro-architects, I'm going to have to disagree with Paul here. For architectures where acquire/release are implemented with explicit barriers (similarly for simple microarchitectures), emitting barriers in a loop *is* going to have an affect on overall performance, since those barriers may well result in traffic outside of the core (at least, on ARM).
Thinking more about that, the real issue here is that arch_mutex_cpu_relax() doesn't have a corresponding hook on the unlock side. On ARM, for example, we can enter a low-power state using the wfe instruction, but that requires the unlocker to wake up the core when the lock is released.
So, although I'm completely in favour of introducing acquire/release accessors, I really think the mcs locking routines would benefit from some arch-specific backend code, even if it's optional (although I would imagine most architectures implementing something to improve power and/or performance).
Will
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Peter Zijlstra
maintainerRe: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 4 lines · Will Deacon
Quoted 4 lines
kernel/locking/mcs_spinlock.c +4
-0+/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired.
Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 3 lines
kernel/locking/mcs_spinlock.c +2
-0+*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();Thinking more about that, the real issue here is that
arch_mutex_cpu_relax()doesn't have a corresponding hook on the unlock side. On ARM, for example, we can enter a low-power state using the wfe instruction, but that requires the unlocker to wake up the core when the lock is released.
That said, it would be ever so awesome if we could come to some sort of conclusion on control dependencies here.
I _know_ C/C++ doesn't do them, but at the end of the day the compiler still generates ASM and as long as we're relatively certain there's a loop there (there has to be, right? :-), we could maybe rely on it anyway.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 14 lines · Peter Zijlstra
Quoted 4 lines · Will Deacon
Quoted 4 lines
kernel/locking/mcs_spinlock.c +4
-0+/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 3 lines
kernel/locking/mcs_spinlock.c +2
-0+*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();Thinking more about that, the real issue here is that
arch_mutex_cpu_relax()doesn't have a corresponding hook on the unlock side. On ARM, for example, we can enter a low-power state using the wfe instruction, but that requires the unlocker to wake up the core when the lock is released.
Quoted 7 lines
That said, it would be ever so awesome if we could come to some sort of conclusion on control dependencies here.
I _know_ C/C++ doesn't do them, but at the end of the day the compiler still generates ASM and as long as we're relatively certain there's a loop there (there has to be, right? :-), we could maybe rely on it anyway.
I believe that we will be able to rely on control dependencies leading to stores (not loads!), but even then they are not guaranteed to provide transitivity. This lack of transitivity sounds problematic for locking primitives.
Thanx, Paul
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
In reply to Will DeaconQuoted 49 lines · Will Deacon
Hello,
Quoted 46 lines · Tim Chen
From: Waiman Long <[email protected]>
This patch corrects the way memory barriers are used in the MCS lock with
smp_load_acquireandsmp_store_releasefucnction.
It removes ones that are not needed.It uses architecture specific load-acquire and store-release primitives for synchronization, if available. Generic implementations are provided in case they are not defined even though they may not be optimal. These generic implementation could be removed later on once changes are made in all the relevant header files.
Suggested-by Michel Lespinasse <[email protected]>Signed-off-by Waiman Long <[email protected]>Signed-off-by Jason Low <[email protected]>Signed-off-by Tim Chen <[email protected]>--- kernel/locking/mcs_spinlock.c | 48 +++++++++++++++++++++++++++++++++++------ 1 files changed, 41 insertions(+), 7 deletions(-)kernel/locking/mcs_spinlock.c +18
-0@@ -23,6 +23,31 @@#endif /* +* Fall back to use the regular atomic operations and memory barrier if +* the acquire/release versions are not defined. +*/ +#ifndef xchg_acquire +# define xchg_acquire (p, v) xchg(p, v) +#endif + +#ifndef smp_load_acquire +# define smp_load_acquire (p) \ + ({ \ + typeof(*p) __v = ACCESS_ONCE(*(p)); \ + smp_mb(); \ + __v; \ + }) +#endif + +#ifndef smp_store_release +# define smp_store_release (p, v) \
Quoted 8 lines
kernel/locking/mcs_spinlock.c line 48
Quoted 5 lines
kernel/locking/mcs_spinlock.c +5
-0+do { \ +smp_mb (); \ +ACCESS_ONCE (*(p)) = v; \ + } while (0) +#endif PeterZ already has a series implementing acquire/release accessors, so you should probably take a look at that rather than rolling your own here.
Yes, we are using Peter Z's implementation here. The above is for anything where smp_load_acquire and smp_store_release are *not* defined. We can remove this once all architectures implement the acquire and release functions as mentioned in the comments of the patch.
Quoted 22 lines
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 18 lines
kernel/locking/mcs_spinlock.c +3
-3+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */
Quoted 9 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 8 lines
kernel/locking/mcs_spinlock.c +6
-1-while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();
An alternate implementation is
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);
Leaving the smp_load_acquire at the end to provide appropriate barrier. Will that be acceptable?
Tim
Quoted 12 lines
After a chat with some micro-architects, I'm going to have to disagree with Paul here. For architectures where acquire/release are implemented with explicit barriers (similarly for simple microarchitectures), emitting barriers in a loop *is* going to have an affect on overall performance, since those barriers may well result in traffic outside of the core (at least, on ARM).
Thinking more about that, the real issue here is that
arch_mutex_cpu_relax()doesn't have a corresponding hook on the unlock side. On ARM, for example, we can enter a low-power state using the wfe instruction, but that requires the unlocker to wake up the core when the lock is released.
Quoted 7 lines
So, although I'm completely in favour of introducing acquire/release accessors, I really think the mcs locking routines would benefit from some arch-specific backend code, even if it's optional (although I would imagine most architectures implementing something to improve power and/or performance).
Will
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 30 lines · Tim Chen
Quoted 21 lines
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 18 lines
kernel/locking/mcs_spinlock.c +3
-3+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 8 lines
kernel/locking/mcs_spinlock.c +6
-1-while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();An alternate implementation is
Quoted 8 lines
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?Tim
I second Tim's opinion. It will be help to have a smp_mb_load_acquire() function that provide a memory barrier with load-acquire semantic. I don't think we need one for store-release as that will not be in a loop.
Peter, what do you think about adding that to your patch?
-Longman
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 38 lines · Waiman Long
Quoted 37 lines · Tim Chen
Quoted 21 lines
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 18 lines
kernel/locking/mcs_spinlock.c +3
-3+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 8 lines
kernel/locking/mcs_spinlock.c +6
-1-while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();An alternate implementation is
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?
Quoted 6 lines
Quoted 1 line
Tim
I second Tim's opinion. It will be help to have a
smp_mb_load_acquire()function that provide a memory barrier with load-acquire semantic. I don't think we need one for store-release as that will not be in a loop.
Hmmm... I guess the ACCESS_ONCE() in the smp_load_acquire() should prevent it from being optimized away. But yes, you then end up with an extra load on the critical lock hand-off patch. And something like an smp_mb_acquire() could then be useful, although I believe that on all current hardware smp_mb_acquire() emits the same code as would an smp_mb_release():
o barrier() on TSO systems such as x86 and s390.
o lwsync instruction on powerpc. (Really old systems would
want a different instruction for smp_mb_acquire(), but let's not optimize for really old systems.)
o dmb instruction on ARM.
o mf instruction on ia64.
So how about an smp_mb_acquire_release() to cover both use cases? This could be used to further optimize circular buffers, for example.
Thanx, Paul
Quoted 4 lines
Peter, what do you think about adding that to your patch?
-Longman
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 59 lines · Paul E. McKenney
Quoted 38 lines · Waiman Long
Quoted 37 lines · Tim Chen
Quoted 21 lines
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 18 lines
kernel/locking/mcs_spinlock.c +3
-3+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 8 lines
kernel/locking/mcs_spinlock.c +6
-1-while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();An alternate implementation is
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?Quoted 6 lines
Quoted 1 line
Tim
I second Tim's opinion. It will be help to have a
smp_mb_load_acquire()function that provide a memory barrier with load-acquire semantic. I don't think we need one for store-release as that will not be in a loop.Hmmm... I guess the
ACCESS_ONCE()in thesmp_load_acquire()should prevent it from being optimized away. But yes, you then end up with an extra load on the critical lock hand-off patch. And something like ansmp_mb_acquire()could then be useful, although I believe that on all current hardwaresmp_mb_acquire()emits the same code as would ansmp_mb_release():o
barrier()on TSO systems such as x86 and s390.o lwsync instruction on powerpc. (Really old systems would
want a different instruction forsmp_mb_acquire(), but let's not optimize for really old systems.)
Quoted 5 lines
o dmb instruction on ARM.
o mf instruction on ia64.
So how about an
smp_mb_acquire_release()to cover both use cases?
I guess we haven't addressed Will's preference to use wfe for ARM instead of doing a spin loop. I'll like some suggestions on how to proceed here. Should we do arch_mcs_lock and arch_mcs_unlock, which defaults to the existing mcs_lock and mcs_unlock code, but allow architecture specific implementation?
Tim
Quoted 9 lines
This could be used to further optimize circular buffers, for example.
Thanx, Paul
Quoted 4 lines
Peter, what do you think about adding that to your patch?
-Longman
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 64 lines · Tim Chen
Quoted 63 lines · Paul E. McKenney
Quoted 38 lines · Waiman Long
Quoted 37 lines · Tim Chen
Quoted 21 lines
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 18 lines
kernel/locking/mcs_spinlock.c +3
-3+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 8 lines
kernel/locking/mcs_spinlock.c +6
-1-while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();An alternate implementation is
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?Quoted 6 lines
Quoted 1 line
Tim
I second Tim's opinion. It will be help to have a
smp_mb_load_acquire()function that provide a memory barrier with load-acquire semantic. I don't think we need one for store-release as that will not be in a loop.Hmmm... I guess the
ACCESS_ONCE()in thesmp_load_acquire()should prevent it from being optimized away. But yes, you then end up with an extra load on the critical lock hand-off patch. And something like ansmp_mb_acquire()could then be useful, although I believe that on all current hardwaresmp_mb_acquire()emits the same code as would ansmp_mb_release():o
barrier()on TSO systems such as x86 and s390.o lwsync instruction on powerpc. (Really old systems would
want a different instruction forsmp_mb_acquire(), but let's not optimize for really old systems.)o dmb instruction on ARM.
o mf instruction on ia64.
Quoted 7 lines
Quoted 1 line
So how about an
smp_mb_acquire_release()to cover both use cases?I guess we haven't addressed Will's preference to use wfe for ARM instead of doing a spin loop. I'll like some suggestions on how to proceed here. Should we do
arch_mcs_lockandarch_mcs_unlock, which defaults to the existingmcs_lockandmcs_unlockcode, but allow architecture specific implementation?
It would be nice to confine the architecture-specific pieces if at all possible. For example, can the architecture-specific piece be confined to the actual high-contention lock handoff?
Thanx, Paul
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
In reply to Tim ChenQuoted 30 lines · Tim Chen
Quoted 21 lines
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 18 lines
kernel/locking/mcs_spinlock.c +3
-3+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 8 lines
kernel/locking/mcs_spinlock.c +6
-1-while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();An alternate implementation is
Quoted 8 lines
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?Tim
I second Tim's opinion. It will be help to have a smp_mb_load_acquire() function that provide a memory barrier with load-acquire semantic. I don't think we need one for store-release as that will not be in a loop.
Peter, what do you think about adding that to your patch?
-Longman
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
In reply to Tim ChenQuoted 30 lines · Tim Chen
Quoted 21 lines
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 18 lines
kernel/locking/mcs_spinlock.c +3
-3+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 8 lines
kernel/locking/mcs_spinlock.c +6
-1-while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();An alternate implementation is
Quoted 8 lines
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?Tim
I second Tim's opinion. It will be help to have a smp_mb_load_acquire() function that provide a memory barrier with load-acquire semantic. I don't think we need one for store-release as that will not be in a loop.
Peter, what do you think about adding that to your patch?
-Longman
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
In reply to Tim ChenQuoted 37 lines · Tim Chen
Quoted 27 lines · Will Deacon
Quoted 26 lines · Tim Chen
kernel/locking/mcs_spinlock.c +18
-0@@ -23,6 +23,31 @@#endif /* +* Fall back to use the regular atomic operations and memory barrier if +* the acquire/release versions are not defined. +*/ +#ifndef xchg_acquire +# define xchg_acquire (p, v) xchg(p, v) +#endif + +#ifndef smp_load_acquire +# define smp_load_acquire (p) \ + ({ \ + typeof(*p) __v = ACCESS_ONCE(*(p)); \ + smp_mb(); \ + __v; \ + }) +#endif + +#ifndef smp_store_release +# define smp_store_release (p, v) \Quoted 8 lines
kernel/locking/mcs_spinlock.c line 48
Quoted 5 lines
kernel/locking/mcs_spinlock.c +5
-0+do { \ +smp_mb (); \ +ACCESS_ONCE (*(p)) = v; \ + } while (0) +#endif PeterZ already has a series implementing acquire/release accessors, so you should probably take a look at that rather than rolling your own here.
Quoted 4 lines
Yes, we are using Peter Z's implementation here. The above is for anything where
smp_load_acquireandsmp_store_releaseare *not* defined. We can remove this once all architectures implement the acquire and release functions as mentioned in the comments of the patch.
Right, so you can use barrier.h and asm-generic will define generic versions (identical to the above) for you if the architecture doesn't have an optimised variant. You don't need to reproduce that in your .c file.
Quoted 20 lines
Quoted 15 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 7 lines
kernel/locking/mcs_spinlock.c +6
-0+/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();After a chat with some micro-architects, I'm going to have to disagree with Paul here. For architectures where acquire/release are implemented with explicit barriers (similarly for simple microarchitectures), emitting barriers in a loop *is* going to have an affect on overall performance, since those barriers may well result in traffic outside of the core (at least, on ARM).
Quoted 4 lines
Thinking more about that, the real issue here is that
arch_mutex_cpu_relax()doesn't have a corresponding hook on the unlock side. On ARM, for example, we can enter a low-power state using the wfe instruction, but that requires the unlocker to wake up the core when the lock is released.
Quoted 7 lines
An alternate implementation is
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?
It still doesn't solve my problem though: I want a way to avoid that busy loop by some architecture-specific manner. The arch_mutex_cpu_relax() hook is a start, but there is no corresponding hook on the unlock side to issue a wakeup. Given a sensible relax implementation, I don't have an issue with putting a load-acquire in a loop, since it shouldn't be aggresively spinning anymore.
Will
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 8 lines · Tim Chen
Quoted 7 lines
An alternate implementation is
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?
Quoted 6 lines · Will Deacon
It still doesn't solve my problem though: I want a way to avoid that busy loop by some architecture-specific manner. The
arch_mutex_cpu_relax()hook is a start, but there is no corresponding hook on the unlock side to issue a wakeup. Given a sensible relax implementation, I don't have an issue with putting a load-acquire in a loop, since it shouldn't be aggresively spinning anymore.
So you want something like this?
/*
* This is a spin-wait with acquire semantics. That is, accesses after
* this are not allowed to be reordered before the load that meets
* the specified condition. This requires that it end with either a
* load-acquire or a full smp_mb(). The optimal way to do this is likely
* to be architecture-dependent. E.g. x86 MONITOR/MWAIT instructions. */
#ifndef smp_load_acquire_until
#define smp_load_acquire_until(addr, cond) \
while (!(smp_load_acquire(addr) cond)) { \
do { \
arch_mutex_cpu_relax(); \
} while (!(ACCESS_ONCE(*(addr)) cond)); \
}#endif
smp_load_acquire_until(&node->locked, != 0);
Alternative implementations:
#define smp_load_acquire_until(addr, cond) { \
while (!(ACCESS_ONCE(*(addr)) cond)) \
arch_mutex_cpu_relax(); \
smp_mb(); }#define smp_load_acquire_until(addr, cond) \
if (!(smp_load_acquire(addr) cond)) { \
do { \
arch_mutex_cpu_relax(); \
} while (!(ACCESS_ONCE(*(addr)) cond)); \
smp_mb(); \
}Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 44 lines · George Spelvin
Quoted 8 lines · Tim Chen
Quoted 7 lines
An alternate implementation is
while (!ACCESS_ONCE(node->locked)) arch_mutex_cpu_relax(); smp_load_acquire(&node->locked);Leaving the
smp_load_acquireat the end to provide appropriate barrier. Will that be acceptable?Quoted 6 lines · Will Deacon
It still doesn't solve my problem though: I want a way to avoid that busy loop by some architecture-specific manner. The
arch_mutex_cpu_relax()hook is a start, but there is no corresponding hook on the unlock side to issue a wakeup. Given a sensible relax implementation, I don't have an issue with putting a load-acquire in a loop, since it shouldn't be aggresively spinning anymore.So you want something like this?
/*
* This is a spin-wait with acquire semantics. That is, accesses after
* this are not allowed to be reordered before the load that meets
* the specified condition. This requires that it end with either a
* load-acquire or a fullsmp_mb(). The optimal way to do this is likely
* to be architecture-dependent. E.g. x86 MONITOR/MWAIT instructions. */
#ifndefsmp_load_acquire_until#define smp_load_acquire_until(addr, cond) \ while (!(smp_load_acquire(addr) cond)) { \ do { \ arch_mutex_cpu_relax(); \ } while (!(ACCESS_ONCE(*(addr)) cond)); \ }#endif
smp_load_acquire_until(&node->locked, != 0);Alternative implementations:
#define smp_load_acquire_until(addr, cond) { \ while (!(ACCESS_ONCE(*(addr)) cond)) \ arch_mutex_cpu_relax(); \ smp_mb(); }
Quoted 7 lines
#define smp_load_acquire_until(addr, cond) \
if (!(smp_load_acquire(addr) cond)) { \
do { \
arch_mutex_cpu_relax(); \
} while (!(ACCESS_ONCE(*(addr)) cond)); \
smp_mb(); \
}Not really...
To be clear: having the load-acquire in a loop is fine, provided that arch_mutex_cpu_relax is something which causes the load to back-off (you mentioned the MONITOR/MWAIT instructions on x86).
On ARM, our equivalent of those instructions also has a counterpart instruction that needs to be executed by the CPU doing the unlock. That means we can do one of two things:
1. Add an arch hook in the unlock path to pair with the relax() call on the lock path (arch_mutex_cpu_wake() ?).
2. Move most of the code into arch_mcs_[un]lock, like we do for spinlocks.
Whilst (1) would suffice, (2) would allow further optimisation on arm64, where we can play tricks to avoid the explicit wakeup if we can control the way in which the lock value is written.
Will
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
In reply to Will DeaconQuoted 86 lines · Will Deacon
Hello,
Quoted 41 lines · Tim Chen
From: Waiman Long <[email protected]>
This patch corrects the way memory barriers are used in the MCS lock with
smp_load_acquireandsmp_store_releasefucnction.
It removes ones that are not needed.It uses architecture specific load-acquire and store-release primitives for synchronization, if available. Generic implementations are provided in case they are not defined even though they may not be optimal. These generic implementation could be removed later on once changes are made in all the relevant header files.
Suggested-by Michel Lespinasse <[email protected]>Signed-off-by Waiman Long <[email protected]>Signed-off-by Jason Low <[email protected]>Signed-off-by Tim Chen <[email protected]>--- kernel/locking/mcs_spinlock.c | 48 +++++++++++++++++++++++++++++++++++------ 1 files changed, 41 insertions(+), 7 deletions(-)kernel/locking/mcs_spinlock.c +13
-0@@ -23,6 +23,31 @@#endif /* +* Fall back to use the regular atomic operations and memory barrier if +* the acquire/release versions are not defined. +*/ +#ifndef xchg_acquire +# define xchg_acquire (p, v) xchg(p, v) +#endif + +#ifndef smp_load_acquire +# define smp_load_acquire (p) \ + ({ \ + typeof(*p) __v = ACCESS_ONCE(*(p)); \ + smp_mb(); \ + __v; \kernel/locking/mcs_spinlock.c line 48
Quoted 10 lines
kernel/locking/mcs_spinlock.c +10
-0+ }) +#endif + +#ifndef smp_store_release +# define smp_store_release (p, v) \ + do { \ + smp_mb(); \ + ACCESS_ONCE(*(p)) = v; \ + } while (0) +#endif PeterZ already has a series implementing acquire/release accessors, so you should probably take a look at that rather than rolling your own here.
You could then augment that with [cmp]xchg_{acquire,release} as appropriate.
Quoted 25 lines
kernel/locking/mcs_spinlock.c +9
-4+/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node; -smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))
Quoted 8 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 1 line
kernel/locking/mcs_spinlock.c +0
-0arch_mutex_cpu_relax ();After a chat with some micro-architects, I'm going to have to disagree with Paul here. For architectures where acquire/release are implemented with explicit barriers (similarly for simple microarchitectures), emitting barriers in a loop *is* going to have an affect on overall performance, since those barriers may well result in traffic outside of the core (at least, on ARM).
OK, that is a new one on me...
Quoted 10 lines
Thinking more about that, the real issue here is that
arch_mutex_cpu_relax()doesn't have a corresponding hook on the unlock side. On ARM, for example, we can enter a low-power state using the wfe instruction, but that requires the unlocker to wake up the core when the lock is released.So, although I'm completely in favour of introducing acquire/release accessors, I really think the mcs locking routines would benefit from some arch-specific backend code, even if it's optional (although I would imagine most architectures implementing something to improve power and/or performance).
I would argue that if we have so much lock contention that spinners want to enter low-power states, other fixes are in order. Especially given relatively small CPU counts.
Thanx, Paul
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
In reply to Tim ChenQuoted 12 lines · Tim Chen
From: Waiman Long <[email protected]>
This patch corrects the way memory barriers are used in the MCS lock with
smp_load_acquireandsmp_store_releasefucnction.
It removes ones that are not needed.It uses architecture specific load-acquire and store-release primitives for synchronization, if available. Generic implementations are provided in case they are not defined even though they may not be optimal. These generic implementation could be removed later on once changes are made in all the relevant header files.
Quoted 4 lines
Suggested-by Michel Lespinasse <[email protected]>Signed-off-by Waiman Long <[email protected]>Signed-off-by Jason Low <[email protected]>Signed-off-by Tim Chen <[email protected]>
Please see comments below.
Thanx, Paul
Quoted 41 lines
--- kernel/locking/mcs_spinlock.c | 48 +++++++++++++++++++++++++++++++++++------ 1 files changed, 41 insertions(+), 7 deletions(-)kernel/locking/mcs_spinlock.c +25
-0@@ -23,6 +23,31 @@#endif /* +* Fall back to use the regular atomic operations and memory barrier if +* the acquire/release versions are not defined. +*/ +#ifndef xchg_acquire +# define xchg_acquire (p, v) xchg(p, v) +#endif + +#ifndef smp_load_acquire +# define smp_load_acquire (p) \ + ({ \ + typeof(*p) __v = ACCESS_ONCE(*(p)); \ + smp_mb(); \ + __v; \ + }) +#endif + +#ifndef smp_store_release +# define smp_store_release (p, v) \ + do { \ + smp_mb(); \ + ACCESS_ONCE(*(p)) = v; \ + } while (0) +#endif + +/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 66
Quoted 6 lines
kernel/locking/mcs_spinlock.c +2
-1@@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ; - prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);
But if this is xchg_acquire() with only acquire semantics, it need not ensure that the initializations of node->locked and node->next above will happen before the "ACCESS_ONCE(prev->next) = node" below. This therefore needs to remain xchg(). Or you need an smp_store_release() below instead of an ACCESS_ONCE() assignment.
As currently written, the poor CPU doing the unlock can be fatally disappointed by seeing pre-initialized values of ->locked and ->next. This could, among other things, result in a hang where the handoff happens before the initialization.
Quoted 5 lines
kernel/locking/mcs_spinlock.c +0
-0if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node;
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 10 lines
kernel/locking/mcs_spinlock.c +6
-3-smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();
OK, this smp_load_acquire() makes sense!
Quoted 14 lines
kernel/locking/mcs_spinlock.c +1
-1}EXPORT_SYMBOL_GPL (mcs_spin_lock);@@ -54,7 +83,7 @@ EXPORT_SYMBOL_GPL(mcs_spin_lock);* Releases the lock. The caller should pass in the corresponding node that * was used to acquire the lock. */ -static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);@@ -68,7 +97,12 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nodwhile (!(next =ACCESS_ONCE (node->next)))arch_mutex_cpu_relax ();
kernel/locking/mcs_spinlock.c static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod · line 106
Quoted 10 lines
kernel/locking/mcs_spinlock.c +7
-2} -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); +/* +* Pass lock to next waiter. +* smp_store_release() provides a memory barrier to ensure +* all operations in the critical section has been completed +* before unlocking. +*/ +smp_store_release (&next->locked ,1 );
This smp_store_release() makes sense as well!
Could you please get rid of the extraneous space before the comma?
Quoted 6 lines
kernel/locking/mcs_spinlock.c +0
-0}EXPORT_SYMBOL_GPL (mcs_spin_unlock);Signature · 1 line
1.7.4.4
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH v5 4/4] MCS Lock: Barrier corrections
Quoted 66 lines · Paul E. McKenney
Quoted 12 lines · Tim Chen
From: Waiman Long <[email protected]>
This patch corrects the way memory barriers are used in the MCS lock with
smp_load_acquireandsmp_store_releasefucnction.
It removes ones that are not needed.It uses architecture specific load-acquire and store-release primitives for synchronization, if available. Generic implementations are provided in case they are not defined even though they may not be optimal. These generic implementation could be removed later on once changes are made in all the relevant header files.
Quoted 4 lines
Suggested-by Michel Lespinasse <[email protected]>Signed-off-by Waiman Long <[email protected]>Signed-off-by Jason Low <[email protected]>Signed-off-by Tim Chen <[email protected]>Please see comments below.
Thanx, Paul
Quoted 44 lines
--- kernel/locking/mcs_spinlock.c | 48 +++++++++++++++++++++++++++++++++++------ 1 files changed, 41 insertions(+), 7 deletions(-)kernel/locking/mcs_spinlock.c +25
-0@@ -23,6 +23,31 @@#endif /* +* Fall back to use the regular atomic operations and memory barrier if +* the acquire/release versions are not defined. +*/ +#ifndef xchg_acquire +# define xchg_acquire (p, v) xchg(p, v) +#endif + +#ifndef smp_load_acquire +# define smp_load_acquire (p) \ + ({ \ + typeof(*p) __v = ACCESS_ONCE(*(p)); \ + smp_mb(); \ + __v; \ + }) +#endif + +#ifndef smp_store_release +# define smp_store_release (p, v) \ + do { \ + smp_mb(); \ + ACCESS_ONCE(*(p)) = v; \ + } while (0) +#endif + +/* * In order to acquire the lock, the caller should declare a local node and * pass a reference of the node to this function in addition to the lock. * If the lock has already been acquired, then this will proceed to spin @@ -37,15 +62,19 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) node->locked =0 ; node->next =NULL ;
Quoted 9 lines
kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 66
Quoted 3 lines
kernel/locking/mcs_spinlock.c +2
-1- prev =xchg (lock, node); +/* xchg() provides a memory barrier */ + prev =xchg_acquire (lock, node);But if this is
xchg_acquire()with only acquire semantics, it need not ensure that the initializations ofnode->lockedandnode->nextabove will happen before the "ACCESS_ONCE(prev->next)= node" below. This therefore needs to remainxchg(). Or you need ansmp_store_release()below instead of anACCESS_ONCE()assignment.
Good point. Will keep it as xchg.
Quoted 51 lines
As currently written, the poor CPU doing the unlock can be fatally disappointed by seeing pre-initialized values of ->locked and ->next. This could, among other things, result in a hang where the handoff happens before the initialization.
Quoted 5 lines
kernel/locking/mcs_spinlock.c +0
-0if (likely (prev ==NULL )) {/* Lock acquired */ return ; }ACCESS_ONCE (prev->next) = node;kernel/locking/mcs_spinlock.c void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) · line 78
Quoted 10 lines
kernel/locking/mcs_spinlock.c +6
-3-smp_wmb (); -/* Wait until the lock holder passes the lock down */ -while (!ACCESS_ONCE (node->locked)) +/* +* Wait until the lock holder passes the lock down. +* Using smp_load_acquire() provides a memory barrier that +* ensures subsequent operations happen after the lock is acquired. +*/ +while (!(smp_load_acquire (&node->locked)))arch_mutex_cpu_relax ();OK, this
smp_load_acquire()makes sense!Quoted 14 lines
kernel/locking/mcs_spinlock.c +1
-1}EXPORT_SYMBOL_GPL (mcs_spin_lock);@@ -54,7 +83,7 @@ EXPORT_SYMBOL_GPL(mcs_spin_lock);* Releases the lock. The caller should pass in the corresponding node that * was used to acquire the lock. */ -static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) {struct mcs_spinlock *next =ACCESS_ONCE (node->next);@@ -68,7 +97,12 @@ static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nodwhile (!(next =ACCESS_ONCE (node->next)))arch_mutex_cpu_relax ();kernel/locking/mcs_spinlock.c static void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *nod · line 106
Quoted 10 lines
kernel/locking/mcs_spinlock.c +7
-2} -ACCESS_ONCE (next->locked) =1 ; -smp_wmb (); +/* +* Pass lock to next waiter. +* smp_store_release() provides a memory barrier to ensure +* all operations in the critical section has been completed +* before unlocking. +*/ +smp_store_release (&next->locked ,1 );This
smp_store_release()makes sense as well!
Quoted 1 line
Could you please get rid of the extraneous space before the comma?
Will do.
Quoted 8 lines
Quoted 7 lines
kernel/locking/mcs_spinlock.c +0
-0}EXPORT_SYMBOL_GPL (mcs_spin_unlock);Signature · 1 line
1.7.4.4