Skip to contentSkip to conversation

rwsem: performance optimizations

PATCH Superseded Tim Chen 9 participants 64 messages Sep 25, 2013 – Oct 2, 2013 1 message not in the archive

Subsystems (5)

v6 of 8 Superseded Latest is v8 →

lore
Share & tools
lore

Tim Chen

author
#1 lore raw

v6 cover rwsem: performance optimizations

In reply to a message not in the archive

We fixed a missing file and fixed various style issues for version 6 of this patchset. We will like to have it merged if there are no objections.

In this patchset, we introduce two categories of optimizations to read write semaphore. The first four patches from Alex Shi reduce cache bouncing of the sem->count field by doing a pre-read of the sem->count and avoid cmpxchg if possible.

The last two patches introduce similar optimistic spinning logic as the mutex code for the writer lock acquisition of rwsem. This addresses the general 'mutexes out perform writer-rwsems' situations that has been seen in more than one case. Users now need not worry about performance issues when choosing between these two locking mechanisms.

Without these optimizations, Davidlohr Bueso saw a -8% regression to aim7's shared and high_systime workloads when he switched i_mmap_mutex to rwsem. Tests were on 8 socket 80 cores system. With the patchset, he got significant improvements to the aim7 suite instead of regressions:

alltests (+16.3%), custom (+20%), disk (+19.5%), high_systime (+7%), shared (+18.4%) and short (+6.3%).

Tim Chen also got a +5% improvements to exim mail server workload on a 40 core system.

Thanks to Ingo Molnar, Peter Hurley and Peter Zijlstra for reviewing this patchset.

Regards,
Tim Chen

Changelog:

v6:
1. Fix missing mcslock.h file.
2. Fix various code style issues.

v5:
1. Try optimistic spinning before we put the writer on the wait queue to avoid bottlenecking at wait queue. This provides 5% boost to exim workload and between 2% to 8% boost to aim7.
2. Put MCS locking code into its own mcslock.h file for better reuse between mutex.c and rwsem.c
3. Remove the configuration RWSEM_SPIN_ON_WRITE_OWNER and make the operations default per Ingo's suggestions.

v4:
1. Fixed a bug in task_struct definition in rwsem_can_spin_on_owner
2. Fix another typo for RWSEM_SPIN_ON_WRITE_OWNER config option

v3:
1. Added ACCESS_ONCE to sem->count access in rwsem_can_spin_on_owner.
2. Fix typo bug for RWSEM_SPIN_ON_WRITE_OWNER option in init/Kconfig

v2:
1. Reorganize changes to down_write_trylock and do_wake into 4 patches and fixed a bug referencing &sem->count when sem->count is intended.
2. Fix unsafe sem->owner de-reference in rwsem_can_spin_on_owner.
the option to be on for more seasoning but can be turned off should it be detrimental.
3. Various patch comments update

Alex Shi (4):
rwsem: check the lock before cpmxchg in down_write_trylock
rwsem: remove 'out' label in do_wake
rwsem: remove try_reader_grant label do_wake rwsem/wake: check lock before do atomic update

Tim Chen (2):
MCS Lock: Restructure the MCS lock defines and locking code into its
own file
rwsem: do optimistic spinning for writer lock acquisition

 include/asm-generic/rwsem.h |    8 +-
 include/linux/mcslock.h     |   58 +++++++++++
 include/linux/rwsem.h       |    6 +-
 kernel/mutex.c              |   58 ++----------
 kernel/rwsem.c              |   19 ++++-
 lib/rwsem.c                 |  228 +++++++++++++++++++++++++++++++++++++-----
 6 files changed, 292 insertions(+), 85 deletions(-)
 create mode 100644 include/linux/mcslock.h
Signature · 1 line
1.7.4.4
author
#2 lore raw

PATCH v6 1/6 rwsem: check the lock before cpmxchg in down_write_trylock

In reply to a message not in the archive

Cmpxchg will cause the cacheline bouning when do the value checking, that cause scalability issue in a large machine (like a 80 core box).

So a lock pre-read can relief this contention.

---
 include/asm-generic/rwsem.h |    8 ++++----
 1 files changed, 4 insertions(+), 4 deletions(-)
include/asm-generic/rwsem.h +4 -4
index bb1e2cd..5ba80e7 100644
@@ -70,11 +70,11 @@ static inline void __down_write(struct rw_semaphore *sem)
 
 static inline int __down_write_trylock(struct rw_semaphore *sem)
 {
-	long tmp;
+	if (unlikely(sem->count != RWSEM_UNLOCKED_VALUE))
+		return 0;
 
-	tmp = cmpxchg(&sem->count, RWSEM_UNLOCKED_VALUE,
-		      RWSEM_ACTIVE_WRITE_BIAS);
-	return tmp == RWSEM_UNLOCKED_VALUE;
+	return cmpxchg(&sem->count, RWSEM_UNLOCKED_VALUE,
+		      RWSEM_ACTIVE_WRITE_BIAS) == RWSEM_UNLOCKED_VALUE;
 }
 
 /*
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>
author
#3 lore raw

PATCH v6 2/6 rwsem: remove 'out' label in do_wake

In reply to a message not in the archive

That make code simple and more readable.

---
 lib/rwsem.c |    5 ++---
 1 files changed, 2 insertions(+), 3 deletions(-)
lib/rwsem.c +2 -3
index 19c5fa9..42f1b1a 100644
@@ -75,7 +75,7 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)
 			 * will block as they will notice the queued writer.
 			 */
 			wake_up_process(waiter->task);
-		goto out;
+		return sem;
 	}
 
 	/* Writers might steal the lock before we grant it to the next reader.
@@ -91,7 +91,7 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)
 			/* A writer stole the lock. Undo our reader grant. */
 			if (rwsem_atomic_update(-adjustment, sem) &
 						RWSEM_ACTIVE_MASK)
-				goto out;
+				return sem;
 			/* Last active locker left. Retry waking readers. */
 			goto try_reader_grant;
 		}
@@ -136,7 +136,6 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)
 	sem->wait_list.next = next;
 	next->prev = &sem->wait_list;
 
- out:
 	return sem;
 }
 
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>
author
#4 lore raw

PATCH v6 3/6 rwsem: remove try_reader_grant label do_wake

In reply to a message not in the archive

That make code simple and more readable

---
 lib/rwsem.c |   12 +++++++-----
 1 files changed, 7 insertions(+), 5 deletions(-)
lib/rwsem.c +7 -5
index 42f1b1a..a8055cf 100644
@@ -85,15 +85,17 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)
 	adjustment = 0;
 	if (wake_type != RWSEM_WAKE_READ_OWNED) {
 		adjustment = RWSEM_ACTIVE_READ_BIAS;
- try_reader_grant:
-		oldcount = rwsem_atomic_update(adjustment, sem) - adjustment;
-		if (unlikely(oldcount < RWSEM_WAITING_BIAS)) {
-			/* A writer stole the lock. Undo our reader grant. */
+		while (1) {
+			oldcount = rwsem_atomic_update(adjustment, sem)
+								- adjustment;
+			if (likely(oldcount >= RWSEM_WAITING_BIAS))
+				break;
+
+			 /* A writer stole the lock.  Undo our reader grant. */
 			if (rwsem_atomic_update(-adjustment, sem) &
 						RWSEM_ACTIVE_MASK)
 				return sem;
 			/* Last active locker left. Retry waking readers. */
-			goto try_reader_grant;
 		}
 	}
 
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>
author
#5 lore raw

PATCH v6 4/6 rwsem/wake: check lock before do atomic update

In reply to a message not in the archive

Atomic update lock and roll back will cause cache bouncing in large machine. A lock status pre-read can relieve this problem

---
 lib/rwsem.c |    8 +++++++-
 1 files changed, 7 insertions(+), 1 deletions(-)
lib/rwsem.c +7 -1
index a8055cf..1d6e6e8 100644
@@ -64,7 +64,7 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)
 	struct rwsem_waiter *waiter;
 	struct task_struct *tsk;
 	struct list_head *next;
-	long oldcount, woken, loop, adjustment;
+	long woken, loop, adjustment;
 
 	waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);
 	if (waiter->type == RWSEM_WAITING_FOR_WRITE) {
@@ -86,6 +86,12 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)
 	if (wake_type != RWSEM_WAKE_READ_OWNED) {
 		adjustment = RWSEM_ACTIVE_READ_BIAS;
 		while (1) {
+			long oldcount;
+
+			/* A writer stole the lock. */
+			if (unlikely(sem->count < RWSEM_WAITING_BIAS))
+				return sem;
+
 			oldcount = rwsem_atomic_update(adjustment, sem)
 								- adjustment;
 			if (likely(oldcount >= RWSEM_WAITING_BIAS))
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>
author
#6 lore raw

PATCH v6 5/6 MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to a message not in the archive

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +58 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_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 mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_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();
+}
+
+#endif
kernel/mutex.c +7 -51

95 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>
author
#63 lore raw

PATCH v6 6/6 rwsem: do optimistic spinning for writer lock acquisition

In reply to a message not in the archive

We want to add optimistic spinning to rwsems because
the writer rwsem does not perform as well as mutexes. Tim noticed that for exim (mail server) workloads, when reverting commit 4fc3f1d6 and Davidlohr noticed it when converting the i_mmap_mutex to a rwsem in some aim7 workloads. We've noticed that the biggest difference
is when we fail to acquire a mutex in the fastpath, optimistic spinning comes in to play and we can avoid a large amount of unnecessary sleeping and overhead of moving tasks in and out of wait queue.

Allowing optimistic spinning before putting the writer on the wait queue reduces wait queue contention and provided greater chance for the rwsem to get acquired. With these changes, rwsem is on par with mutex.

---
 include/linux/rwsem.h |    6 +-
 kernel/rwsem.c        |   19 +++++-
 lib/rwsem.c           |  203 ++++++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 207 insertions(+), 21 deletions(-)
include/linux/rwsem.h +5 -1
index 0616ffe..ef5a83a 100644
@@ -26,6 +26,8 @@ struct rw_semaphore {
 	long			count;
 	raw_spinlock_t		wait_lock;
 	struct list_head	wait_list;
+	struct task_struct	*owner; /* write owner */
+	void			*spin_mlock;
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 	struct lockdep_map	dep_map;
 #endif
@@ -58,7 +60,9 @@ static inline int rwsem_is_locked(struct rw_semaphore *sem)
 #define __RWSEM_INITIALIZER(name)			\
 	{ RWSEM_UNLOCKED_VALUE,				\
 	  __RAW_SPIN_LOCK_UNLOCKED(name.wait_lock),	\
-	  LIST_HEAD_INIT((name).wait_list)		\
+	  LIST_HEAD_INIT((name).wait_list),		\
+	  NULL,						\
+	  NULL						\
 	  __RWSEM_DEP_MAP_INIT(name) }
 
 #define DECLARE_RWSEM(name) \
kernel/rwsem.c +18 -1
index cfff143..d74d1c9 100644
@@ -12,6 +12,16 @@
 
 #include <linux/atomic.h>
 
+static inline void rwsem_set_owner(struct rw_semaphore *sem)
+{
+	sem->owner = current;
+}
+
+static inline void rwsem_clear_owner(struct rw_semaphore *sem)
+{
+	sem->owner = NULL;
+}
+
 /*
  * lock for reading
  */
@@ -48,6 +58,7 @@ void __sched down_write(struct rw_semaphore *sem)
 	rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_);
 
 	LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
+	rwsem_set_owner(sem);
 }
 
 EXPORT_SYMBOL(down_write);
@@ -59,8 +70,10 @@ int down_write_trylock(struct rw_semaphore *sem)
 {
 	int ret = __down_write_trylock(sem);
 
-	if (ret == 1)
+	if (ret == 1) {
 		rwsem_acquire(&sem->dep_map, 0, 1, _RET_IP_);
+		rwsem_set_owner(sem);
+	}
 	return ret;
 }
 
@@ -86,6 +99,7 @@ void up_write(struct rw_semaphore *sem)
 	rwsem_release(&sem->dep_map, 1, _RET_IP_);
 
 	__up_write(sem);
+	rwsem_clear_owner(sem);
 }
 
 EXPORT_SYMBOL(up_write);
@@ -100,6 +114,7 @@ void downgrade_write(struct rw_semaphore *sem)
 	 * dependency.
 	 */
 	__downgrade_write(sem);
+	rwsem_clear_owner(sem);
 }
 
 EXPORT_SYMBOL(downgrade_write);
@@ -122,6 +137,7 @@ void _down_write_nest_lock(struct rw_semaphore *sem, struct lockdep_map *nest)
 	rwsem_acquire_nest(&sem->dep_map, 0, 0, nest, _RET_IP_);
 
 	LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
+	rwsem_set_owner(sem);
 }
 
 EXPORT_SYMBOL(_down_write_nest_lock);
@@ -141,6 +157,7 @@ void down_write_nested(struct rw_semaphore *sem, int subclass)
 	rwsem_acquire(&sem->dep_map, subclass, 0, _RET_IP_);
 
 	LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
+	rwsem_set_owner(sem);
 }
 
 EXPORT_SYMBOL(down_write_nested);
lib/rwsem.c +184 -19

249 lines not on this page · raw message

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>
#7 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Tim Chen
Quoted 18 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +0 -0
new file mode 100644
index 0000000..20fd3f0

include/linux/mcslock.h line 4

Quoted 4 lines
include/linux/mcslock.h new file +4 -0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.

A (very) short blurb about what an MCS lock is would be nice here.

include/linux/mcslock.h line 12

Quoted 8 lines
include/linux/mcslock.h new file +8 -0
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *next;
+	int		  locked;	/* 1 if lock acquired */
+};

The vertical alignment looks broken here.

Quoted 36 lines
include/linux/mcslock.h new file +36 -0
+
+/*
+ * 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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_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 mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *next = ACCESS_ONCE(node->next);
+
+	if (likely(!next)) {
+		/*
+		 * Release the lock by setting it to NULL
+		 */
+		if (cmpxchg(lock, node, NULL) == node)

include/linux/mcslock.h line 58

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+			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

We typically close header guards not via a plain #endif but like this:

#endif /* __LINUX_SPINLOCK_H */

#endif /* __LINUX_SPINLOCK_TYPES_H */

etc.

Thanks,

Ingo

#64 lore raw

Re: [PATCH v6 6/6] rwsem: do optimistic spinning for writer lock acquisition

In reply to Tim Chen
Quoted 34 lines · Tim Chen

We want to add optimistic spinning to rwsems because
the writer rwsem does not perform as well as mutexes. Tim noticed that for exim (mail server) workloads, when reverting commit 4fc3f1d6 and Davidlohr noticed it when converting the i_mmap_mutex to a rwsem in some aim7 workloads. We've noticed that the biggest difference
is when we fail to acquire a mutex in the fastpath, optimistic spinning comes in to play and we can avoid a large amount of unnecessary sleeping and overhead of moving tasks in and out of wait queue.

Allowing optimistic spinning before putting the writer on the wait queue reduces wait queue contention and provided greater chance for the rwsem to get acquired. With these changes, rwsem is on par with mutex.

---
 include/linux/rwsem.h |    6 +-
 kernel/rwsem.c        |   19 +++++-
 lib/rwsem.c           |  203 ++++++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 207 insertions(+), 21 deletions(-)
include/linux/rwsem.h +2 -0
index 0616ffe..ef5a83a 100644
@@ -26,6 +26,8 @@ struct rw_semaphore {
 	long			count;
 	raw_spinlock_t		wait_lock;
 	struct list_head	wait_list;
+	struct task_struct	*owner; /* write owner */
+	void			*spin_mlock;

include/linux/rwsem.h struct rw_semaphore {

Quoted 10 lines
include/linux/rwsem.h +5 -0
 
+#define MLOCK(rwsem)    ((struct mcs_spin_node **)&((rwsem)->spin_mlock))
 
+		mcs_spin_lock(MLOCK(sem), &node);
 
+			mcs_spin_unlock(MLOCK(sem), &node);
 
+			mcs_spin_unlock(MLOCK(sem), &node);
 
+		mcs_spin_unlock(MLOCK(sem), &node);

That forced type casting is ugly and fragile.

To avoid having to include mcslock.h into rwsem.h just add a forward struct declaration, before the struct rw_semaphore definition:

struct mcs_spin_node;

Then define spin_mlock with the right type:

	struct mcs_spin_node		*spin_mlock;

I'd also suggest renaming 'spin_mlock', to reduce unnecessary variants. If the lock type name is 'struct mcs_spin_node' then 'mcs_lock' would be a perfect field name, right?

While at it, renaming mcs_spin_node to mcs_spinlock might be wise as well, and the include file would be named mcs_spinlock.h.

Thanks,

Ingo

maintainer
#8 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Ingo Molnar
Quoted 6 lines · Ingo Molnar
Quoted 4 lines

+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.

A (very) short blurb about what an MCS lock is would be nice here.

A while back I suggested including a link to something like:

http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf

Its a fairly concise write-up of the idea; only 6 pages. The sad part about linking to the web is that links tend to go dead after a while.

--
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>

#9 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 6 lines · Peter Zijlstra
Quoted 5 lines · Ingo Molnar
Quoted 4 lines

+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.

Quoted 8 lines
Quoted 1 line

A (very) short blurb about what an MCS lock is would be nice here.

A while back I suggested including a link to something like:

http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf

Its a fairly concise write-up of the idea; only 6 pages. The sad part about linking to the web is that links tend to go dead after a while.

So what I wanted to see was to add just a few sentences summing up the concept - so that people blundering into this file in include/linux/ have an idea what it's all about!

Thanks,

Ingo

author
#10 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Peter Zijlstra
Quoted 6 lines · Peter Zijlstra
Quoted 5 lines · Ingo Molnar
Quoted 4 lines

+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.

Quoted 8 lines
Quoted 1 line

A (very) short blurb about what an MCS lock is would be nice here.

A while back I suggested including a link to something like:

http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf

Its a fairly concise write-up of the idea; only 6 pages. The sad part about linking to the web is that links tend to go dead after a while.

Link rot is a problem. If I provide a few details about MCS lock I think people should be able to google for it.

Tim

--
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>

#11 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Tim Chen
Quoted 38 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +20 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{

include/linux/mcslock.h line 30

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 8 lines
include/linux/mcslock.h new file +8 -0
+               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();
+}

include/linux/mcslock.h line 48

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+
+static void mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
+{
+       struct mcs_spin_node *next = ACCESS_ONCE(node->next);
+
+       if (likely(!next)) {
+               /*
+                * Release the lock by setting it to NULL
+                */
+               if (cmpxchg(lock, node, NULL) == node)

And can we make this check likely()?

#12 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 43 lines · Jason Low
Quoted 42 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +24 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
Quoted 8 lines

include/linux/mcslock.h line 30

Quoted 6 lines
include/linux/mcslock.h new file +6 -0
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

I don't follow, the whole idea is to spin on the local variable.

#13 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 53 lines · Davidlohr Bueso
Quoted 43 lines · Jason Low
Quoted 42 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +24 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
Quoted 8 lines

include/linux/mcslock.h line 30

Quoted 6 lines
include/linux/mcslock.h new file +6 -0
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 1 line

I don't follow, the whole idea is to spin on the local variable.

If prev == NULL, doesn't that mean it won't proceed to spin on the variable because the lock is already free and we call return? In that case where we directly acquire the lock, I was wondering if it is necessary to set node->locked = 1.

--
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>

#14 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 54 lines · Jason Low
Quoted 53 lines · Davidlohr Bueso
Quoted 51 lines · Jason Low
Quoted 38 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +20 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{

include/linux/mcslock.h line 30

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 6 lines
Quoted 1 line

I don't follow, the whole idea is to spin on the local variable.

If prev == NULL, doesn't that mean it won't proceed to spin on the variable because the lock is already free and we call return? In that case where we directly acquire the lock, I was wondering if it is necessary to set node->locked = 1.

Yes, that's true, but we need to flag the lock as acquired (the node's lock is initially set to unlocked), otherwise others trying to acquire the lock can spin forever:

	/* Wait until the lock holder passes the lock down */
	while (!ACCESS_ONCE(node->locked))
		arch_mutex_cpu_relax();

The ->locked variable in this implementation refers to if the lock is acquired, and *not* to if busy-waiting is necessary.

Thanks,
Davidlohr

--
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>

#15 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 66 lines · Davidlohr Bueso
Quoted 54 lines · Jason Low
Quoted 53 lines · Davidlohr Bueso
Quoted 51 lines · Jason Low
Quoted 38 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +20 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{

include/linux/mcslock.h line 30

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 6 lines
Quoted 1 line

I don't follow, the whole idea is to spin on the local variable.

If prev == NULL, doesn't that mean it won't proceed to spin on the variable because the lock is already free and we call return? In that case where we directly acquire the lock, I was wondering if it is necessary to set node->locked = 1.

Yes, that's true, but we need to flag the lock as acquired (the node's lock is initially set to unlocked), otherwise others trying to acquire the lock can spin forever:

Quoted 6 lines
	/* Wait until the lock holder passes the lock down */
	while (!ACCESS_ONCE(node->locked))
		arch_mutex_cpu_relax();

The ->locked variable in this implementation refers to if the lock is acquired, and *not* to if busy-waiting is necessary.

hmm, others threads acquiring the lock will be spinning on their own local nodes, not this node's node->locked. And if prev == NULL, the current thread won't be reading it's node->lock either since we return. So what other thread is going to be reading this node's node->lock?

Thanks,
Jason

author
#16 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 74 lines · Jason Low
Quoted 66 lines · Davidlohr Bueso
Quoted 54 lines · Jason Low
Quoted 53 lines · Davidlohr Bueso
Quoted 51 lines · Jason Low
Quoted 38 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +20 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{

include/linux/mcslock.h line 30

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 6 lines
Quoted 1 line

I don't follow, the whole idea is to spin on the local variable.

If prev == NULL, doesn't that mean it won't proceed to spin on the variable because the lock is already free and we call return? In that case where we directly acquire the lock, I was wondering if it is necessary to set node->locked = 1.

Yes, that's true, but we need to flag the lock as acquired (the node's lock is initially set to unlocked), otherwise others trying to acquire the lock can spin forever:

Quoted 6 lines
	/* Wait until the lock holder passes the lock down */
	while (!ACCESS_ONCE(node->locked))
		arch_mutex_cpu_relax();

The ->locked variable in this implementation refers to if the lock is acquired, and *not* to if busy-waiting is necessary.

Quoted 7 lines

hmm, others threads acquiring the lock will be spinning on their own local nodes, not this node's node->locked. And if prev == NULL, the current thread won't be reading it's node->lock either since we return. So what other thread is going to be reading this node's node->lock?

Thanks,
Jason

I think setting node->locked = 1 for the prev==NULL case is not necessary functionally, but was done for semantics consistency.

Tim

--
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>

#40 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Jason Low
Quoted 71 lines · Jason Low
Quoted 70 lines · Davidlohr Bueso
Quoted 54 lines · Jason Low
Quoted 53 lines · Davidlohr Bueso
Quoted 51 lines · Jason Low
Quoted 38 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +20 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{

include/linux/mcslock.h line 30

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 6 lines
Quoted 1 line

I don't follow, the whole idea is to spin on the local variable.

If prev == NULL, doesn't that mean it won't proceed to spin on the variable because the lock is already free and we call return? In that case where we directly acquire the lock, I was wondering if it is necessary to set node->locked = 1.

Yes, that's true, but we need to flag the lock as acquired (the node's lock is initially set to unlocked), otherwise others trying to acquire the lock can spin forever:

	/* Wait until the lock holder passes the lock down */
	while (!ACCESS_ONCE(node->locked))
		arch_mutex_cpu_relax();
Quoted 5 lines
Quoted 2 lines

The ->locked variable in this implementation refers to if the lock is acquired, and *not* to if busy-waiting is necessary.

hmm, others threads acquiring the lock will be spinning on their own local nodes, not this node's node->locked.

But we're xchg'ing memory locations, not values - so after the xchg() call, when we modify node->locked, we're also changing lock, which isn't local.

Quoted 11 lines

And if prev == NULL, the
current thread won't be reading it's node->lock either since we return. So what other thread is going to be reading this node's node->lock?

Thanks,
Jason

--
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>

#17 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Tim Chen
Quoted 80 lines · Tim Chen
Quoted 79 lines · Jason Low
Quoted 66 lines · Davidlohr Bueso
Quoted 54 lines · Jason Low
Quoted 53 lines · Davidlohr Bueso
Quoted 51 lines · Jason Low
Quoted 38 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +20 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{

include/linux/mcslock.h line 30

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 6 lines
Quoted 1 line

I don't follow, the whole idea is to spin on the local variable.

If prev == NULL, doesn't that mean it won't proceed to spin on the variable because the lock is already free and we call return? In that case where we directly acquire the lock, I was wondering if it is necessary to set node->locked = 1.

Yes, that's true, but we need to flag the lock as acquired (the node's lock is initially set to unlocked), otherwise others trying to acquire the lock can spin forever:

Quoted 6 lines
	/* Wait until the lock holder passes the lock down */
	while (!ACCESS_ONCE(node->locked))
		arch_mutex_cpu_relax();

The ->locked variable in this implementation refers to if the lock is acquired, and *not* to if busy-waiting is necessary.

hmm, others threads acquiring the lock will be spinning on their own local nodes, not this node's node->locked. And if prev == NULL, the current thread won't be reading it's node->lock either since we return. So what other thread is going to be reading this node's node->lock?

Quoted 5 lines
Quoted 2 lines

Thanks,
Jason

I think setting node->locked = 1 for the prev==NULL case is not necessary functionally, but was done for semantics consistency.

Okay, that would makes sense for consistency because we always first set node->lock = 0 at the top of the function.

If we prefer to optimize this a bit though, perhaps we can first move the node->lock = 0 so that it gets executed after the "if (likely(prev == NULL)) {}" code block and then delete "node->lock = 1" inside the code block.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
       struct mcs_spin_node *prev;

       /* Init node */
       node->next   = NULL;

       prev = xchg(lock, node);
       if (likely(prev == NULL)) {
               /* Lock acquired */
               return;
       }
       node->locked = 0;
       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();
}
author
#18 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 87 lines · Jason Low
Quoted 80 lines · Tim Chen
Quoted 79 lines · Jason Low
Quoted 66 lines · Davidlohr Bueso
Quoted 54 lines · Jason Low
Quoted 53 lines · Davidlohr Bueso
Quoted 51 lines · Jason Low
Quoted 38 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +20 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+       struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{

include/linux/mcslock.h line 30

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+       struct mcs_spin_node *prev;
+
+       /* Init node */
+       node->locked = 0;
+       node->next   = NULL;
+
+       prev = xchg(lock, node);
+       if (likely(prev == NULL)) {
+               /* Lock acquired */
+               node->locked = 1;

If we don't spin on the local node, is it necessary to set this variable?

Quoted 6 lines
Quoted 1 line

I don't follow, the whole idea is to spin on the local variable.

If prev == NULL, doesn't that mean it won't proceed to spin on the variable because the lock is already free and we call return? In that case where we directly acquire the lock, I was wondering if it is necessary to set node->locked = 1.

Yes, that's true, but we need to flag the lock as acquired (the node's lock is initially set to unlocked), otherwise others trying to acquire the lock can spin forever:

Quoted 6 lines
	/* Wait until the lock holder passes the lock down */
	while (!ACCESS_ONCE(node->locked))
		arch_mutex_cpu_relax();

The ->locked variable in this implementation refers to if the lock is acquired, and *not* to if busy-waiting is necessary.

hmm, others threads acquiring the lock will be spinning on their own local nodes, not this node's node->locked. And if prev == NULL, the current thread won't be reading it's node->lock either since we return. So what other thread is going to be reading this node's node->lock?

Quoted 5 lines
Quoted 2 lines

Thanks,
Jason

I think setting node->locked = 1 for the prev==NULL case is not necessary functionally, but was done for semantics consistency.

Quoted 7 lines

Okay, that would makes sense for consistency because we always first set node->lock = 0 at the top of the function.

If we prefer to optimize this a bit though, perhaps we can first move the node->lock = 0 so that it gets executed after the "if (likely(prev == NULL)) {}" code block and then delete "node->lock = 1" inside the code block.

I suppose we can save one single assignment.
The gain is probably not noticeable as once we set node->next to NULL, node->locked is likely in local cache line and the assignment operation is cheap.

Regards,
Tim

Quoted 13 lines

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
       struct mcs_spin_node *prev;

       /* Init node */
       node->next   = NULL;

       prev = xchg(lock, node);
       if (likely(prev == NULL)) {
               /* Lock acquired */
               return;
Quoted 9 lines
       }
       node->locked = 0;
       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();
}

--
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>

#19 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 8 lines · Tim Chen
Quoted 4 lines

If we prefer to optimize this a bit though, perhaps we can first move the node->lock = 0 so that it gets executed after the "if (likely(prev == NULL)) {}" code block and then delete "node->lock = 1" inside the code block.

I suppose we can save one single assignment. The gain is probably not noticeable as once we set node->next to NULL, node->locked is likely in local cache line and the assignment operation is cheap.

Would be nice to have this as a separate, add-on patch. Every single instruction removal that has no downside is an upside!

You can add a comment that explains it.

Thanks,

Ingo

--
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>

#20 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 7 lines · Ingo Molnar
Quoted 5 lines · Tim Chen
Quoted 4 lines

If we prefer to optimize this a bit though, perhaps we can first move the node->lock = 0 so that it gets executed after the "if (likely(prev == NULL)) {}" code block and then delete "node->lock = 1" inside the code block.

Quoted 8 lines
Quoted 3 lines

I suppose we can save one single assignment. The gain is probably not noticeable as once we set node->next to NULL, node->locked is likely in local cache line and the assignment operation is cheap.

Would be nice to have this as a separate, add-on patch. Every single instruction removal that has no downside is an upside!

You can add a comment that explains it.

Yup, especially a spin lock (and one that I have found to be be used very frequently when running workloads on big machines).

--
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>

maintainer
#21 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Ingo Molnar
Quoted 4 lines · Ingo Molnar

Would be nice to have this as a separate, add-on patch. Every single instruction removal that has no downside is an upside!

You can add a comment that explains it.

If someone is going to do add-on patches to the mcslock.h file, please also consider doing a patch that adds comments to the memory barriers in there.

Also, checkpatch.pl should really warn about that; and it appears there code in there for that; however:

# grep -C3 smp_mb scripts/checkpatch.pl 
                        }
                }
# check for memory barriers without a comment.
                if ($line =~ /\b(mb|rmb|wmb|read_barrier_depends|smp_mb|smp_rmb|smp_wmb|smp_read_barrier_depends)\(/) {
                        if (!ctx_has_comment($first_line, $linenr)) {
                                CHK("MEMORY_BARRIER",
                                    "memory barrier without comment\n" . $herecurr);
# grep -C3 smp_wmb kernel/mutex.c
                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();
--
                        arch_mutex_cpu_relax();
        }
        ACCESS_ONCE(next->locked) = 1;
        smp_wmb();
}

/*

# scripts/checkpatch.pl -f kernel/mutex.c 2>&1 | grep memory
#

so that appears to be completely broken :/

Joe, any clue what's up with that?

--
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>

maintainer
#22 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 4 lines · Peter Zijlstra

checkpatch.pl should really warn about that; and it appears there code in there for that; however:

# grep -C3 smp_mb scripts/checkpatch.pl 

[]

Quoted 5 lines
# check for memory barriers without a comment.
                if ($line =~ /\b(mb|rmb|wmb|read_barrier_depends|smp_mb|smp_rmb|smp_wmb|smp_read_barrier_depends)\(/) {
                        if (!ctx_has_comment($first_line, $linenr)) {
                                CHK("MEMORY_BARRIER",
                                    "memory barrier without comment\n" . $herecurr);

[]

Quoted 6 lines
# scripts/checkpatch.pl -f kernel/mutex.c 2>&1 | grep memory
#

so that appears to be completely broken :/

Joe, any clue what's up with that?

It's a CHK test, so it's only tested with --strict

$ scripts/checkpatch.pl -f --strict kernel/mutex.c 2>&1 | grep memory
CHECK: memory barrier without comment
CHECK: memory barrier without comment

It could be changed to WARN so it's always on.

--
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>

maintainer
#23 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 7 lines · Joe Perches

It's a CHK test, so it's only tested with --strict

$ scripts/checkpatch.pl -f --strict kernel/mutex.c 2>&1 | grep memory
CHECK: memory barrier without comment
CHECK: memory barrier without comment

It could be changed to WARN so it's always on.

Yes please, we can't be too careful with memory barriers.

--
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>

maintainer
#24 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 10 lines · Peter Zijlstra
Quoted 7 lines · Joe Perches

It's a CHK test, so it's only tested with --strict

$ scripts/checkpatch.pl -f --strict kernel/mutex.c 2>&1 | grep memory
CHECK: memory barrier without comment
CHECK: memory barrier without comment

It could be changed to WARN so it's always on.

Yes please, we can't be too careful with memory barriers.

I'll send the patch separately.

It seems a pretty noisy test.
There are 13 hits just in arch/x86/kernel/

$ ./scripts/checkpatch.pl -f arch/x86/kernel/*.c | grep -A3 "memory barrier"
WARNING: memory barrier without comment
#685: FILE: x86/kernel/alternative.c:685:
+	smp_wmb();

--
WARNING: memory barrier without comment
#401: FILE: x86/kernel/kvm.c:401:
+ rmb();

WARNING: memory barrier without comment
#403: FILE: x86/kernel/kvm.c:403:
+ rmb();

--
WARNING: memory barrier without comment
#702: FILE: x86/kernel/kvm.c:702:
+ smp_wmb();

WARNING: memory barrier without comment
#704: FILE: x86/kernel/kvm.c:704:
+ smp_wmb();

--
WARNING: memory barrier without comment
#62: FILE: x86/kernel/ldt.c:62:
+ wmb();

WARNING: memory barrier without comment
#64: FILE: x86/kernel/ldt.c:64:
+ wmb();

--
WARNING: memory barrier without comment
#204: FILE: x86/kernel/smpboot.c:204:
+ wmb();

WARNING: memory barrier without comment
#265: FILE: x86/kernel/smpboot.c:265:
+ wmb();

--
WARNING: memory barrier without comment
#557: FILE: x86/kernel/smpboot.c:557:
+ mb();

--
WARNING: memory barrier without comment
#1065: FILE: x86/kernel/smpboot.c:1065:
+ mb();

--
WARNING: memory barrier without comment
#1321: FILE: x86/kernel/smpboot.c:1321:
+ mb();

WARNING: memory barrier without comment
#1399: FILE: x86/kernel/smpboot.c:1399:
+ mb();

--
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>

maintainer
#26 lore raw

PATCH 1/1 checkpatch: Make the memory barrier test noisier

In reply to Peter Zijlstra

Peter Zijlstra prefers that comments be required near uses of memory barriers.

Change the message level for memory barrier uses from a --strict test only to a normal WARN so it's always emitted.

This might produce false positives around insertions of memory barriers when a comment is outside the patch context block.

And checkpatch is still stupid, it only looks for existence of any comment, not at the comment content.

---
 scripts/checkpatch.pl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
scripts/checkpatch.pl +2 -2
index c03e427..bd4103a 100755
@@ -3816,8 +3816,8 @@ sub string_find_replace {
 # check for memory barriers without a comment.
 		if ($line =~ /\b(mb|rmb|wmb|read_barrier_depends|smp_mb|smp_rmb|smp_wmb|smp_read_barrier_depends)\(/) {
 			if (!ctx_has_comment($first_line, $linenr)) {
-				CHK("MEMORY_BARRIER",
-				    "memory barrier without comment\n" . $herecurr);
+				WARN("MEMORY_BARRIER",
+				     "memory barrier without comment\n" . $herecurr);
 			}
 		}
 # check of hardware specific defines

--
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>

maintainer
#25 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Joe Perches
Quoted 8 lines · Joe Perches
Quoted 7 lines · Peter Zijlstra
Quoted 6 lines · Joe Perches

It's a CHK test, so it's only tested with --strict

$ scripts/checkpatch.pl -f --strict kernel/mutex.c 2>&1 | grep memory
CHECK: memory barrier without comment
CHECK: memory barrier without comment
Quoted 8 lines
Quoted 3 lines
Quoted 1 line

It could be changed to WARN so it's always on.

Yes please, we can't be too careful with memory barriers.

I'll send the patch separately.

It seems a pretty noisy test.
There are 13 hits just in arch/x86/kernel/

Urgh. that wants fixing. We really need to stop getting more and that's where checkpatch is good at.

At very very bare minimum the comment should mention where the pairing barrier is; but ideally it should describe the actual ordering.

--
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>

maintainer Acked-by on v1 1/1
#27 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

In reply to Joe Perches
Quoted 9 lines · Joe Perches

Peter Zijlstra prefers that comments be required near uses of memory barriers.

Change the message level for memory barrier uses from a --strict test only to a normal WARN so it's always emitted.

This might produce false positives around insertions of memory barriers when a comment is outside the patch context block.

One would argue that in that case they're too far away in any case :-)

Quoted 2 lines

And checkpatch is still stupid, it only looks for existence of any comment, not at the comment content.

Could we try and alleviate this by giving a slightly more verbose warning?

Maybe something like:

memory barrier without comment; please refer to the pairing barrier and describe the ordering requirements.

Quoted 2 lines

--
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>

maintainer
#28 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

Quoted 16 lines · Peter Zijlstra
Quoted 9 lines · Joe Perches

Peter Zijlstra prefers that comments be required near uses of memory barriers.

Change the message level for memory barrier uses from a --strict test only to a normal WARN so it's always emitted.

This might produce false positives around insertions of memory barriers when a comment is outside the patch context block.

One would argue that in that case they're too far away in any case :-)

Quoted 2 lines

And checkpatch is still stupid, it only looks for existence of any comment, not at the comment content.

Quoted 7 lines

Could we try and alleviate this by giving a slightly more verbose warning?

Maybe something like:

memory barrier without comment; please refer to the pairing barrier and describe the ordering requirements.

That would make it seem as if all barriers are SMP no?

Maybe just refer to Documentation/memory-barriers.txt and/or say something like "please document appropriately"

--
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>

maintainer
#29 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

Quoted 1 line · Joe Perches

That would make it seem as if all barriers are SMP no?

I would think any memory barrier is ordering against someone else; if not smp then a device/hardware -- like for instance the hardware page table walker.

Barriers are fundamentally about order; and order only makes sense if there's more than 1 party to the game.

Quoted 2 lines

Maybe just refer to Documentation/memory-barriers.txt and/or say something like "please document appropriately"

Documentation/memory-barriers.txt is always good; appropriately doesn't seem to quantify anything much at all. Someone might think:

/* */
smp_mb();

appropriate...

--
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>

#30 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

Quoted 9 lines · Peter Zijlstra
Quoted 1 line · Joe Perches

That would make it seem as if all barriers are SMP no?

I would think any memory barrier is ordering against someone else; if not smp then a device/hardware -- like for instance the hardware page table walker.

Barriers are fundamentally about order; and order only makes sense if there's more than 1 party to the game.

Oddly enough, there is one exception that proves the rule... On Itanium, suppose we have the following code, with x initially equal to zero:

CPU 1: ACCESS_ONCE(x) = 1;

CPU 2: r1 = ACCESS_ONCE(x); r2 = ACCESS_ONCE(x);

Itanium architects have told me that it really is possible for CPU 2 to see r1==1 and r2==0. Placing a memory barrier between CPU 2's pair of fetches prevents this, but without any other memory barrier to pair with.

Quoted 10 lines
Quoted 2 lines

Maybe just refer to Documentation/memory-barriers.txt and/or say something like "please document appropriately"

Documentation/memory-barriers.txt is always good; appropriately doesn't seem to quantify anything much at all. Someone might think:

/* */
smp_mb();

appropriate...

I end up doing this:

/* */
smp_mb(); /* See above block comment. */

But it would be nice for the prior comment to be recognized as belonging to the memory barrier without the additional "See above" comment.

In any case, please feel free to add:

to the original checkpatch.pl patch.

Thanx, Paul

#41 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Tim Chen
Quoted 63 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +45 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_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 mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *next = ACCESS_ONCE(node->next);
+
+	if (likely(!next)) {
+		/*

include/linux/mcslock.h line 55

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+		 * 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();

Shouldn't the memory barrier precede the "ACCESS_ONCE(next->locked) = 1;"? Maybe in an "else" clause of the prior "if" statement, given that the cmpxchg() does it otherwise.

Otherwise, in the case where the "if" conditionn is false, the critical section could bleed out past the unlock.

Thanx, Paul

Quoted 117 lines
include/linux/mcslock.h new file +3 -0
+}
+
+#endif
kernel/mutex.c +7 -51
index 6d647ae..1b6ba3f 100644
@@ -25,6 +25,7 @@
 #include <linux/spinlock.h>
 #include <linux/interrupt.h>
 #include <linux/debug_locks.h>
+#include <linux/mcslock.h>
 
 /*
  * In the DEBUG case we are using the "NULL fastpath" for mutexes,
@@ -111,54 +112,9 @@ 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();
-}
+#define	MLOCK(mutex)	((struct mcs_spin_node **)&((mutex)->spin_mlock))
 
 /*
  * Mutex spinning code migrated from kernel/sched/core.c
@@ -448,7 +404,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 
 	for (;;) {
 		struct task_struct *owner;
-		struct mspin_node  node;
+		struct mcs_spin_node  node;
 
 		if (!__builtin_constant_p(ww_ctx == NULL) && ww_ctx->acquired > 0) {
 			struct ww_mutex *ww;
@@ -470,10 +426,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(MLOCK(lock), &node);
 		owner = ACCESS_ONCE(lock->owner);
 		if (owner && !mutex_spin_on_owner(lock, owner)) {
-			mspin_unlock(MLOCK(lock), &node);
+			mcs_spin_unlock(MLOCK(lock), &node);
 			goto slowpath;
 		}
 
@@ -488,11 +444,11 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 			}
 
 			mutex_set_owner(lock);
-			mspin_unlock(MLOCK(lock), &node);
+			mcs_spin_unlock(MLOCK(lock), &node);
 			preempt_enable();
 			return 0;
 		}
-		mspin_unlock(MLOCK(lock), &node);
+		mcs_spin_unlock(MLOCK(lock), &node);
 
 		/*
 		 * When there's no owner, we might have preempted between the
Signature · 9 lines
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/
maintainer
#31 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

In reply to Paul E. McKenney
Quoted 6 lines · Paul E. McKenney
Quoted 2 lines

Barriers are fundamentally about order; and order only makes sense if there's more than 1 party to the game.

Oddly enough, there is one exception that proves the rule... On Itanium, suppose we have the following code, with x initially equal to zero:

Quoted 7 lines

CPU 1: ACCESS_ONCE(x) = 1;

CPU 2: r1 = ACCESS_ONCE(x); r2 = ACCESS_ONCE(x);

Itanium architects have told me that it really is possible for CPU 2 to see r1==1 and r2==0. Placing a memory barrier between CPU 2's pair of fetches prevents this, but without any other memory barrier to pair with.

Oh man.. its really past time to sink that itanic already.

I suppose it allows the cpu to reorder the reads in its pipeline and the memory barrier disallows this. Curious.. does our memory-barriers.txt file mention this 'fun' fact?

--
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>

#32 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

Quoted 15 lines · Peter Zijlstra
Quoted 6 lines · Paul E. McKenney
Quoted 2 lines

Barriers are fundamentally about order; and order only makes sense if there's more than 1 party to the game.

Oddly enough, there is one exception that proves the rule... On Itanium, suppose we have the following code, with x initially equal to zero:

Quoted 7 lines

CPU 1: ACCESS_ONCE(x) = 1;

CPU 2: r1 = ACCESS_ONCE(x); r2 = ACCESS_ONCE(x);

Itanium architects have told me that it really is possible for CPU 2 to see r1=1 and r2=0. Placing a memory barrier between CPU 2's pair of fetches prevents this, but without any other memory barrier to pair with.

Quoted 5 lines

Oh man.. its really past time to sink that itanic already.

I suppose it allows the cpu to reorder the reads in its pipeline and the memory barrier disallows this. Curious.. does our memory-barriers.txt file mention this 'fun' fact?

Probably not. I was recently reminded of it by some people on the C++ standards committee. I had first heard of it about 5 years ago, but hadn't heard definitively until quite recently.

I defer to the Itanium maintainers to actually make the required changes, should they choose to do so. I suppose that one way to handle it in the Linux kernel would be to make ACCESS_ONCE() be architecture specific, with Itanium placing a memory barrier either before or after --- either would work. But since Itanium seems to run Linux reliably, I am guessing that the probability of misordering is quite low. But again, the ball is firmly in the Itanium maintainers' courts, and I have added them on CC.

Thanx, Paul

#35 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Ingo Molnar
Quoted 2 lines · Ingo Molnar

Would be nice to have this as a separate, add-on patch. Every single instruction removal that has no downside is an upside!

Okay, so here is a patch. Tim, would you like to add this to v7?

...
Subject: MCS lock: Remove and reorder unnecessary assignments in mcs_spin_lock()

In mcs_spin_lock(), if (likely(prev == NULL)) is true, then the lock is free and we won't spin on the local node. In that case, we don't have to assign node->locked because it won't be used. We can also move the node->locked = 0 assignment so that it occurs after the if (likely(prev == NULL)) check.

This might also help make it clearer as to how the node->locked variable is used in MCS locks.

---
 include/linux/mcslock.h |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)
include/linux/mcslock.h +1 -2
index 20fd3f0..1167d57 100644
@@ -21,15 +21,14 @@ void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
 	struct mcs_spin_node *prev;
 
 	/* Init node */
-	node->locked = 0;
 	node->next   = NULL;
 
 	prev = xchg(lock, node);
 	if (likely(prev == NULL)) {
 		/* Lock acquired */
-		node->locked = 1;
 		return;
 	}
+	node->locked = 0;
 	ACCESS_ONCE(prev->next) = node;
 	smp_wmb();
 	/* Wait until the lock holder passes the lock down */
Signature · 8 lines
1.7.1


--
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>
author
#36 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 5 lines · Jason Low
Quoted 2 lines · Ingo Molnar

Would be nice to have this as a separate, add-on patch. Every single instruction removal that has no downside is an upside!

Okay, so here is a patch. Tim, would you like to add this to v7?

Okay. Will do.

Tim

Quoted 28 lines

...
Subject: MCS lock: Remove and reorder unnecessary assignments in mcs_spin_lock()

In mcs_spin_lock(), if (likely(prev == NULL)) is true, then the lock is free and we won't spin on the local node. In that case, we don't have to assign node->locked because it won't be used. We can also move the node->locked = 0 assignment so that it occurs after the if (likely(prev == NULL)) check.

This might also help make it clearer as to how the node->locked variable is used in MCS locks.

---
 include/linux/mcslock.h |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)
include/linux/mcslock.h +0 -1
index 20fd3f0..1167d57 100644
@@ -21,15 +21,14 @@ void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
 	struct mcs_spin_node *prev;
 
 	/* Init node */
-	node->locked = 0;
 	node->next   = NULL;
 

include/linux/mcslock.h void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node) · line 34

Quoted 10 lines
include/linux/mcslock.h +1 -1
 	prev = xchg(lock, node);
 	if (likely(prev == NULL)) {
 		/* Lock acquired */
-		node->locked = 1;
 		return;
 	}
+	node->locked = 0;
 	ACCESS_ONCE(prev->next) = node;
 	smp_wmb();
 	/* Wait until the lock holder passes the lock down */

--
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>

author
#42 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Paul E. McKenney
Quoted 79 lines · Paul E. McKenney
Quoted 63 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +45 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_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 mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *next = ACCESS_ONCE(node->next);
+
+	if (likely(!next)) {
+		/*

include/linux/mcslock.h line 55

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+		 * 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();

Shouldn't the memory barrier precede the "ACCESS_ONCE(next->locked) = 1;"? Maybe in an "else" clause of the prior "if" statement, given that the cmpxchg() does it otherwise.

Quoted 2 lines

Otherwise, in the case where the "if" conditionn is false, the critical section could bleed out past the unlock.

Yes, I agree with you that the smp_wmb should be moved before ACCESS_ONCE to prevent critical section from bleeding. Copying Waiman who is the original author of the mcs code to see if he has any comments on things we may have missed.

Tim

--
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>

author
#44 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Paul E. McKenney
Quoted 45 lines · Paul E. McKenney
Quoted 44 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +26 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *prev;
+
+	/* Init node */
+	node->locked = 0;
+	node->next   = NULL;
+
Quoted 8 lines

include/linux/mcslock.h line 34

Quoted 8 lines
include/linux/mcslock.h new file +8 -0
+	prev = xchg(lock, node);
+	if (likely(prev == NULL)) {
+		/* Lock acquired */
+		node->locked = 1;
+		return;
+	}
+	ACCESS_ONCE(prev->next) = node;
+	smp_wmb();

BTW, is the above memory barrier necessary? It seems like the xchg instruction already provided a memory barrier.

Now if we made the changes that Jason suggested:

        /* Init node */
-       node->locked = 0;
        node->next   = NULL;
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired */
-               node->locked = 1;
                return;
        }
+       node->locked = 0;
        ACCESS_ONCE(prev->next) = node;
        smp_wmb();

We are probably still okay as other cpus do not read the value of node->locked, which is a local variable.

Tim

Quoted 31 lines
Quoted 11 lines
include/linux/mcslock.h new file +11 -0
+	/* 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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *next = ACCESS_ONCE(node->next);
+
+	if (likely(!next)) {
+		/*

include/linux/mcslock.h line 55

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+		 * 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();

Shouldn't the memory barrier precede the "ACCESS_ONCE(next->locked) = 1;"? Maybe in an "else" clause of the prior "if" statement, given that the cmpxchg() does it otherwise.

Otherwise, in the case where the "if" conditionn is false, the critical section could bleed out past the unlock.

Thanx, Paul

#45 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 20 lines · Tim Chen

BTW, is the above memory barrier necessary? It seems like the xchg instruction already provided a memory barrier.

Now if we made the changes that Jason suggested:

        /* Init node */
(quoted diff) +1 -2
-       node->locked = 0;
        node->next   = NULL;
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired */
-               node->locked = 1;
                return;
        }
+       node->locked = 0;
        ACCESS_ONCE(prev->next) = node;
        smp_wmb();
Quoted 2 lines

We are probably still okay as other cpus do not read the value of node->locked, which is a local variable.

Similarly, I was wondering if we should also move smp_wmb() so that it is before the ACCESS_ONCE(prev->next) = node and after the node->locked = 0. Would we want to guarantee that the node->locked gets set before it is added to the linked list where a previous thread calling mcs_spin_unlock() would potentially modify node->locked?

--
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>

#46 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Tim Chen
Quoted 75 lines · Tim Chen
Quoted 45 lines · Paul E. McKenney
Quoted 44 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +26 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *prev;
+
+	/* Init node */
+	node->locked = 0;
+	node->next   = NULL;
+
Quoted 8 lines

include/linux/mcslock.h line 34

Quoted 8 lines
include/linux/mcslock.h new file +8 -0
+	prev = xchg(lock, node);
+	if (likely(prev == NULL)) {
+		/* Lock acquired */
+		node->locked = 1;
+		return;
+	}
+	ACCESS_ONCE(prev->next) = node;
+	smp_wmb();

BTW, is the above memory barrier necessary? It seems like the xchg instruction already provided a memory barrier.

Now if we made the changes that Jason suggested:

        /* Init node */
(quoted diff) +1 -2
-       node->locked = 0;
        node->next   = NULL;
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired */
-               node->locked = 1;
                return;
        }
+       node->locked = 0;
        ACCESS_ONCE(prev->next) = node;
        smp_wmb();
Quoted 2 lines

We are probably still okay as other cpus do not read the value of node->locked, which is a local variable.

I don't immediately see the need for the smp_wmb() in either case.

Quoted 5 lines

Tim

Quoted 3 lines

include/linux/mcslock.h line 37

Quoted 3 lines
include/linux/mcslock.h new file +3 -0
+	/* Wait until the lock holder passes the lock down */
+	while (!ACCESS_ONCE(node->locked))
+		arch_mutex_cpu_relax();

However, you do need a full memory barrier here in order to ensure that you see the effects of the previous lock holder's critical section.

Thanx, Paul

Quoted 23 lines
Quoted 23 lines
Quoted 8 lines
include/linux/mcslock.h new file +8 -0
+}
+
+static void mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *next = ACCESS_ONCE(node->next);
+
+	if (likely(!next)) {
+		/*

include/linux/mcslock.h line 55

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+		 * 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();

Shouldn't the memory barrier precede the "ACCESS_ONCE(next->locked) = 1;"? Maybe in an "else" clause of the prior "if" statement, given that the cmpxchg() does it otherwise.

Quoted 7 lines
Quoted 5 lines

Otherwise, in the case where the "if" conditionn is false, the critical section could bleed out past the unlock.

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>

author
#47 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 76 lines · Paul E. McKenney
Quoted 75 lines · Tim Chen
Quoted 45 lines · Paul E. McKenney
Quoted 44 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +26 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *prev;
+
+	/* Init node */
+	node->locked = 0;
+	node->next   = NULL;
+
Quoted 8 lines

include/linux/mcslock.h line 34

Quoted 8 lines
include/linux/mcslock.h new file +8 -0
+	prev = xchg(lock, node);
+	if (likely(prev == NULL)) {
+		/* Lock acquired */
+		node->locked = 1;
+		return;
+	}
+	ACCESS_ONCE(prev->next) = node;
+	smp_wmb();

BTW, is the above memory barrier necessary? It seems like the xchg instruction already provided a memory barrier.

Now if we made the changes that Jason suggested:

        /* Init node */
(quoted diff) +1 -2
-       node->locked = 0;
        node->next   = NULL;
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired */
-               node->locked = 1;
                return;
        }
+       node->locked = 0;
        ACCESS_ONCE(prev->next) = node;
        smp_wmb();
Quoted 4 lines
Quoted 2 lines

We are probably still okay as other cpus do not read the value of node->locked, which is a local variable.

I don't immediately see the need for the smp_wmb() in either case.

Thinking a bit more, the following could happen in Jason's initial patch proposal. In this case variable "prev" referenced by CPU1 points to "node" referenced by CPU2

	CPU 1 (calling lock)			CPU 2 (calling unlock)
	ACCESS_ONCE(prev->next) = node
						*next = ACCESS_ONCE(node->next);
						ACCESS_ONCE(next->locked) = 1;
	node->locked = 0;

Then we will be spinning forever on CPU1 as we overwrite the lock passed from CPU2 before we check it. The original code assign "node->locked = 0" before xchg does not have this issue. Doing the following change of moving smp_wmb immediately after node->locked assignment (suggested by Jason)

	node->locked = 0;
	smp_wmb();
	ACCESS_ONCE(prev->next) = node;

could avoid the problem, but will need closer scrutiny to see if there are other pitfalls if wmb happen before

	ACCESS_ONCE(prev->next) = node;
Quoted 7 lines
Quoted 4 lines
Quoted 4 lines

include/linux/mcslock.h line 37

Quoted 4 lines
include/linux/mcslock.h new file +3 -0
+	/* Wait until the lock holder passes the lock down */
+	while (!ACCESS_ONCE(node->locked))
+		arch_mutex_cpu_relax();

However, you do need a full memory barrier here in order to ensure that you see the effects of the previous lock holder's critical section.

Is it necessary to add a memory barrier after acquiring the lock if the previous lock holder execute smp_wmb before passing the lock?

Thanks.

Tim

--
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>

#48 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 99 lines · Tim Chen
Quoted 76 lines · Paul E. McKenney
Quoted 75 lines · Tim Chen
Quoted 45 lines · Paul E. McKenney
Quoted 44 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
 include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |   58 +++++-----------------------------------------
 2 files changed, 65 insertions(+), 51 deletions(-)
 create mode 100644 include/linux/mcslock.h
include/linux/mcslock.h new file +26 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *prev;
+
+	/* Init node */
+	node->locked = 0;
+	node->next   = NULL;
+
Quoted 8 lines

include/linux/mcslock.h line 34

Quoted 8 lines
include/linux/mcslock.h new file +8 -0
+	prev = xchg(lock, node);
+	if (likely(prev == NULL)) {
+		/* Lock acquired */
+		node->locked = 1;
+		return;
+	}
+	ACCESS_ONCE(prev->next) = node;
+	smp_wmb();

BTW, is the above memory barrier necessary? It seems like the xchg instruction already provided a memory barrier.

Now if we made the changes that Jason suggested:

        /* Init node */
(quoted diff) +1 -2
-       node->locked = 0;
        node->next   = NULL;
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired */
-               node->locked = 1;
                return;
        }
+       node->locked = 0;
        ACCESS_ONCE(prev->next) = node;
        smp_wmb();
Quoted 4 lines
Quoted 2 lines

We are probably still okay as other cpus do not read the value of node->locked, which is a local variable.

I don't immediately see the need for the smp_wmb() in either case.

Thinking a bit more, the following could happen in Jason's initial patch proposal. In this case variable "prev" referenced by CPU1 points to "node" referenced by CPU2

	CPU 1 (calling lock)			CPU 2 (calling unlock)
	ACCESS_ONCE(prev->next) = node
						*next = ACCESS_ONCE(node->next);
						ACCESS_ONCE(next->locked) = 1;
	node->locked = 0;

Then we will be spinning forever on CPU1 as we overwrite the lock passed from CPU2 before we check it. The original code assign "node->locked = 0" before xchg does not have this issue. Doing the following change of moving smp_wmb immediately after node->locked assignment (suggested by Jason)

Quoted 8 lines
	node->locked = 0;
	smp_wmb();
	ACCESS_ONCE(prev->next) = node;

could avoid the problem, but will need closer scrutiny to see if there are other pitfalls if wmb happen before

	ACCESS_ONCE(prev->next) = node;

I could believe that an smp_wmb() might be needed before the "ACCESS_ONCE(prev->next) = node;", just not after.

Quoted 10 lines
Quoted 6 lines
Quoted 3 lines
Quoted 3 lines

include/linux/mcslock.h line 37

Quoted 3 lines
include/linux/mcslock.h new file +3 -0
+	/* Wait until the lock holder passes the lock down */
+	while (!ACCESS_ONCE(node->locked))
+		arch_mutex_cpu_relax();

However, you do need a full memory barrier here in order to ensure that you see the effects of the previous lock holder's critical section.

Is it necessary to add a memory barrier after acquiring the lock if the previous lock holder execute smp_wmb before passing the lock?

Yep. The previous lock holder's smp_wmb() won't keep either the compiler or the CPU from reordering things for the new lock holder. They could for example reorder the critical section to precede the node->locked check, which would be very bad.

Thanx, Paul

#33 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

In reply to Peter Zijlstra
Quoted 9 lines · Peter Zijlstra
Quoted 1 line · Joe Perches

That would make it seem as if all barriers are SMP no?

I would think any memory barrier is ordering against someone else; if not smp then a device/hardware -- like for instance the hardware page table walker.

Barriers are fundamentally about order; and order only makes sense if there's more than 1 party to the game.

But not necessarily more than 1 kind of parties. It is perfectly possible to have a barrier against other threads running the same function.

Regards
Oliver

--
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>

#49 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Paul E. McKenney
Quoted 4 lines · Paul E. McKenney

Yep. The previous lock holder's smp_wmb() won't keep either the compiler or the CPU from reordering things for the new lock holder. They could for example reorder the critical section to precede the node->locked check, which would be very bad.

Paul, Tim, Longman,

How would you like the proposed changes below?

---
Subject: [PATCH] MCS: optimizations and barrier corrections

Delete the node->locked = 1 assignment if the lock is free as it won't be used.

Delete the smp_wmb() in mcs_spin_lock() and add a full memory barrier at the end of the mcs_spin_lock() function. As Paul McKenney suggested, "you do need a full memory barrier here in order to ensure that you see the effects of the previous lock holder's critical section." And in the mcs_spin_unlock(), move the memory barrier so that it is before the "ACCESS_ONCE(next->locked) = 1;".

---
 include/linux/mcslock.h |    7 +++----
 1 files changed, 3 insertions(+), 4 deletions(-)
include/linux/mcslock.h +0 -0
index 20fd3f0..edd57d2 100644
@@ -26,15 +26,14 @@ void mcs_spin_lock(struct mcs_spin_node **lock,

struct mcs_spin_node *node)

        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
-               /* Lock acquired */
-               node->locked = 1;
+               /* Lock acquired. No need to set node->locked since it
won't be used */
                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();

+ smp_mb();
}

static void mcs_spin_unlock(struct mcs_spin_node **lock, struct
mcs_spin_node *node)
@@ -51,8 +50,8 @@ static void mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *n

                while (!(next = ACCESS_ONCE(node->next)))
                        arch_mutex_cpu_relax();
        }
-       ACCESS_ONCE(next->locked) = 1;
        smp_wmb();

+ ACCESS_ONCE(next->locked) = 1;
}

#endif

Signature · 1 line
1.7.1
#50 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 7 lines · Jason Low
Quoted 4 lines · Paul E. McKenney

Yep. The previous lock holder's smp_wmb() won't keep either the compiler or the CPU from reordering things for the new lock holder. They could for example reorder the critical section to precede the node->locked check, which would be very bad.

Quoted 6 lines

Paul, Tim, Longman,

How would you like the proposed changes below?

---
Subject: [PATCH] MCS: optimizations and barrier corrections

We *really* need to comment those barriers - explicitly that is :)

Quoted 28 lines

Delete the node->locked = 1 assignment if the lock is free as it won't be used.

Delete the smp_wmb() in mcs_spin_lock() and add a full memory barrier at the end of the mcs_spin_lock() function. As Paul McKenney suggested, "you do need a full memory barrier here in order to ensure that you see the effects of the previous lock holder's critical section." And in the mcs_spin_unlock(), move the memory barrier so that it is before the "ACCESS_ONCE(next->locked) = 1;".

---
 include/linux/mcslock.h |    7 +++----
 1 files changed, 3 insertions(+), 4 deletions(-)
include/linux/mcslock.h +1 -2
index 20fd3f0..edd57d2 100644
@@ -26,15 +26,14 @@ void mcs_spin_lock(struct mcs_spin_node **lock,
 struct mcs_spin_node *node)
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
-               /* Lock acquired */
-               node->locked = 1;
+               /* Lock acquired. No need to set node->locked since it
Quoted 1 line

won't be used */

Then, we need to explain/comment then the relationship between this situation and the locked being set in mspin_unlock(), passing the lock holder down the list.

Quoted 23 lines
                return;
        }
        ACCESS_ONCE(prev->next) = node;
include/linux/mcslock.h +1 -1
-       smp_wmb();
        /* Wait until the lock holder passes the lock down */
        while (!ACCESS_ONCE(node->locked))
                arch_mutex_cpu_relax();
+       smp_mb();
 }
 
 static void mcs_spin_unlock(struct mcs_spin_node **lock, struct

mcs_spin_node *node)

include/linux/mcslock.h +1 -1
@@ -51,8 +50,8 @@ static void mcs_spin_unlock(struct mcs_spin_node
 **lock, struct mcs_spin_node *n
                while (!(next = ACCESS_ONCE(node->next)))
                        arch_mutex_cpu_relax();
        }
-       ACCESS_ONCE(next->locked) = 1;
        smp_wmb();
+       ACCESS_ONCE(next->locked) = 1;
 }
 

#endif

#51 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Jason Low
Quoted 10 lines · Jason Low
Quoted 4 lines · Paul E. McKenney

Yep. The previous lock holder's smp_wmb() won't keep either the compiler or the CPU from reordering things for the new lock holder. They could for example reorder the critical section to precede the node->locked check, which would be very bad.

Paul, Tim, Longman,

How would you like the proposed changes below?

Could you point me at what this applies to? I can find flaws looking at random pieces, given a little luck, but at some point I need to look at the whole thing. ;-)

Thanx, Paul

Quoted 57 lines

---
Subject: [PATCH] MCS: optimizations and barrier corrections

Delete the node->locked = 1 assignment if the lock is free as it won't be used.

Delete the smp_wmb() in mcs_spin_lock() and add a full memory barrier at the end of the mcs_spin_lock() function. As Paul McKenney suggested, "you do need a full memory barrier here in order to ensure that you see the effects of the previous lock holder's critical section." And in the mcs_spin_unlock(), move the memory barrier so that it is before the "ACCESS_ONCE(next->locked) = 1;".

---
 include/linux/mcslock.h |    7 +++----
 1 files changed, 3 insertions(+), 4 deletions(-)
include/linux/mcslock.h +1 -2
index 20fd3f0..edd57d2 100644
@@ -26,15 +26,14 @@ void mcs_spin_lock(struct mcs_spin_node **lock,
 struct mcs_spin_node *node)
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
-               /* Lock acquired */
-               node->locked = 1;
+               /* Lock acquired. No need to set node->locked since it

won't be used */

                return;
        }
        ACCESS_ONCE(prev->next) = node;
include/linux/mcslock.h +1 -1
-       smp_wmb();
        /* Wait until the lock holder passes the lock down */
        while (!ACCESS_ONCE(node->locked))
                arch_mutex_cpu_relax();
+       smp_mb();
 }
 
 static void mcs_spin_unlock(struct mcs_spin_node **lock, struct

mcs_spin_node *node)

include/linux/mcslock.h +1 -1
@@ -51,8 +50,8 @@ static void mcs_spin_unlock(struct mcs_spin_node
 **lock, struct mcs_spin_node *n
                while (!(next = ACCESS_ONCE(node->next)))
                        arch_mutex_cpu_relax();
        }
-       ACCESS_ONCE(next->locked) = 1;
        smp_wmb();
+       ACCESS_ONCE(next->locked) = 1;
 }
 

#endif

Signature · 1 line
1.7.1
#43 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Tim Chen
Quoted 79 lines · Tim Chen
Quoted 78 lines · Paul E. McKenney
Quoted 63 lines · Tim Chen

We will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.

---
  include/linux/mcslock.h |   58 +++++++++++++++++++++++++++++++++++++++++++++++
  kernel/mutex.c          |   58 +++++-----------------------------------------
  2 files changed, 65 insertions(+), 51 deletions(-)

create mode 100644 include/linux/mcslock.h

include/linux/mcslock.h new file +45 -0
new file mode 100644
index 0000000..20fd3f0
@@ -0,0 +1,58 @@
+/*
+ * MCS lock defines
+ *
+ * This file contains the main data structure and API definitions of MCS lock.
+ */
+#ifndef __LINUX_MCSLOCK_H
+#define __LINUX_MCSLOCK_H
+
+struct mcs_spin_node {
+	struct mcs_spin_node *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_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_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 mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)
+{
+	struct mcs_spin_node *next = ACCESS_ONCE(node->next);
+
+	if (likely(!next)) {
+		/*

include/linux/mcslock.h line 55

Quoted 10 lines
include/linux/mcslock.h new file +10 -0
+		 * 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();

Shouldn't the memory barrier precede the "ACCESS_ONCE(next->locked) = 1;"? Maybe in an "else" clause of the prior "if" statement, given that the cmpxchg() does it otherwise.

Quoted 8 lines
Quoted 2 lines

Otherwise, in the case where the "if" conditionn is false, the critical section could bleed out past the unlock.

Yes, I agree with you that the smp_wmb should be moved before ACCESS_ONCE to prevent critical section from bleeding. Copying Waiman who is the original author of the mcs code to see if he has any comments on things we may have missed.

Tim

As a more general lock/unlock mechanism, I also agreed that we should move smp_wmb() before ACCESS_ONCE(). For the mutex case, it is used as a queuing mechanism rather than guarding critical section, so it doesn't really matter.

Regards,
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>

#52 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Paul E. McKenney
Quoted 8 lines · Paul E. McKenney
Quoted 7 lines · Jason Low
Quoted 4 lines · Paul E. McKenney

Yep. The previous lock holder's smp_wmb() won't keep either the compiler or the CPU from reordering things for the new lock holder. They could for example reorder the critical section to precede the node->locked check, which would be very bad.

Quoted 7 lines
Quoted 3 lines

Paul, Tim, Longman,

How would you like the proposed changes below?

Could you point me at what this applies to? I can find flaws looking at random pieces, given a little luck, but at some point I need to look at the whole thing. ;-)

Sure. Here is a link to the patch we are trying to modify:
https://lkml.org/lkml/2013/9/25/532

Also, below is what the mcs_spin_lock() and mcs_spin_unlock() functions would look like after applying the proposed changes.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
        struct mcs_spin_node *prev;

        /* Init node */
        node->locked = 0;
        node->next   = NULL;

        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired. No need to set node->locked since it
won't be used */
                return;
        }
        ACCESS_ONCE(prev->next) = node;
        /* Wait until the lock holder passes the lock down */
        while (!ACCESS_ONCE(node->locked))
                arch_mutex_cpu_relax();
        smp_mb();
}

static void mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
        struct mcs_spin_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();
        }
        smp_wmb();
        ACCESS_ONCE(next->locked) = 1;
}

--
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>

maintainer
#34 lore raw

Re: [PATCH] checkpatch: Make the memory barrier test noisier

In reply to Oliver Neukum
Quoted 8 lines · Oliver Neukum
Quoted 7 lines · Peter Zijlstra
Quoted 1 line · Joe Perches

That would make it seem as if all barriers are SMP no?

I would think any memory barrier is ordering against someone else; if not smp then a device/hardware -- like for instance the hardware page table walker.

Quoted 6 lines
Quoted 2 lines

Barriers are fundamentally about order; and order only makes sense if there's more than 1 party to the game.

But not necessarily more than 1 kind of parties. It is perfectly possible to have a barrier against other threads running the same function.

Then that makes a good comment ;-)

--
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>

#53 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Jason Low
Quoted 12 lines · Jason Low
Quoted 12 lines

Also, below is what the mcs_spin_lock() and mcs_spin_unlock() functions would look like after applying the proposed changes.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
         struct mcs_spin_node *prev;

         /* Init node */
         node->locked = 0;
         node->next   = NULL;
Quoted 11 lines
Quoted 11 lines
         prev = xchg(lock, node);
         if (likely(prev == NULL)) {
                 /* Lock acquired. No need to set node->locked since it
won't be used */
                 return;
         }
         ACCESS_ONCE(prev->next) = node;
         /* Wait until the lock holder passes the lock down */
         while (!ACCESS_ONCE(node->locked))
                 arch_mutex_cpu_relax();
         smp_mb();

I wonder if a memory barrier is really needed here.

Quoted 7 lines
Quoted 7 lines

}

static void mcs_spin_unlock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
         struct mcs_spin_node *next = ACCESS_ONCE(node->next);
Quoted 13 lines
Quoted 5 lines
         if (likely(!next)) {
                 /*
                  * Release the lock by setting it to NULL
                  */
                 if (cmpxchg(lock, node, NULL) == node)
Quoted 8 lines
                         return;
                 /* Wait until the next pointer is set */
                 while (!(next = ACCESS_ONCE(node->next)))
                         arch_mutex_cpu_relax();
         }
         smp_wmb();
         ACCESS_ONCE(next->locked) = 1;
}

Instead, I think what we need may be:

if (likely(!next)) {
    ....
} else
    smp_mb();

ACCESS_ONCE(next->locked) = 1;

That will ensure a memory barrier in the unlock path.

Regards,
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>

#54 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 25 lines · Waiman Long
Quoted 12 lines · Jason Low
Quoted 12 lines

Also, below is what the mcs_spin_lock() and mcs_spin_unlock() functions would look like after applying the proposed changes.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
         struct mcs_spin_node *prev;

         /* Init node */
         node->locked = 0;
         node->next   = NULL;
Quoted 11 lines
Quoted 11 lines
         prev = xchg(lock, node);
         if (likely(prev == NULL)) {
                 /* Lock acquired. No need to set node->locked since it
won't be used */
                 return;
         }
         ACCESS_ONCE(prev->next) = node;
         /* Wait until the lock holder passes the lock down */
         while (!ACCESS_ONCE(node->locked))
                 arch_mutex_cpu_relax();
         smp_mb();
Quoted 1 line

I wonder if a memory barrier is really needed here.

If the compiler can reorder the while (!ACCESS_ONCE(node->locked)) check so that the check occurs after an instruction in the critical section, then the barrier may be necessary.

author
#62 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Paul E. McKenney
Quoted 10 lines · Paul E. McKenney
Quoted 9 lines · Jason Low
Quoted 4 lines · Paul E. McKenney

Yep. The previous lock holder's smp_wmb() won't keep either the compiler or the CPU from reordering things for the new lock holder. They could for example reorder the critical section to precede the node->locked check, which would be very bad.

Paul, Tim, Longman,

Quoted 7 lines
Quoted 1 line

How would you like the proposed changes below?

Could you point me at what this applies to? I can find flaws looking at random pieces, given a little luck, but at some point I need to look at the whole thing. ;-)

Thanx, Paul

Jason's patch is on top of the following patchset:

https://lkml.org/lkml/2013/9/26/674

Thanks.

Tim

Quoted 64 lines
Quoted 54 lines

---
Subject: [PATCH] MCS: optimizations and barrier corrections

Delete the node->locked = 1 assignment if the lock is free as it won't be used.

Delete the smp_wmb() in mcs_spin_lock() and add a full memory barrier at the end of the mcs_spin_lock() function. As Paul McKenney suggested, "you do need a full memory barrier here in order to ensure that you see the effects of the previous lock holder's critical section." And in the mcs_spin_unlock(), move the memory barrier so that it is before the "ACCESS_ONCE(next->locked) = 1;".

---
 include/linux/mcslock.h |    7 +++----
 1 files changed, 3 insertions(+), 4 deletions(-)
include/linux/mcslock.h +1 -2
index 20fd3f0..edd57d2 100644
@@ -26,15 +26,14 @@ void mcs_spin_lock(struct mcs_spin_node **lock,
 struct mcs_spin_node *node)
 
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
-               /* Lock acquired */
-               node->locked = 1;
+               /* Lock acquired. No need to set node->locked since it

won't be used */

                return;
        }
        ACCESS_ONCE(prev->next) = node;
include/linux/mcslock.h +1 -1
-       smp_wmb();
        /* Wait until the lock holder passes the lock down */
        while (!ACCESS_ONCE(node->locked))
                arch_mutex_cpu_relax();
+       smp_mb();
 }
 
 static void mcs_spin_unlock(struct mcs_spin_node **lock, struct

mcs_spin_node *node)

include/linux/mcslock.h +1 -1
@@ -51,8 +50,8 @@ static void mcs_spin_unlock(struct mcs_spin_node
 **lock, struct mcs_spin_node *n
                while (!(next = ACCESS_ONCE(node->next)))
                        arch_mutex_cpu_relax();
        }
-       ACCESS_ONCE(next->locked) = 1;
        smp_wmb();
+       ACCESS_ONCE(next->locked) = 1;
 }
 
Quoted 4 lines

#endif

Signature · 1 line
1.7.1

--
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/

#55 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Jason Low
Quoted 25 lines · Jason Low
Quoted 24 lines · Waiman Long
Quoted 23 lines · Jason Low
Quoted 23 lines

Also, below is what the mcs_spin_lock() and mcs_spin_unlock() functions would look like after applying the proposed changes.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
          struct mcs_spin_node *prev;

          /* Init node */
          node->locked = 0;
          node->next   = NULL;

          prev = xchg(lock, node);
          if (likely(prev == NULL)) {
                  /* Lock acquired. No need to set node->locked since it
won't be used */
                  return;
          }
          ACCESS_ONCE(prev->next) = node;
          /* Wait until the lock holder passes the lock down */
          while (!ACCESS_ONCE(node->locked))
                  arch_mutex_cpu_relax();
          smp_mb();
Quoted 5 lines
Quoted 1 line

I wonder if a memory barrier is really needed here.

If the compiler can reorder the while (!ACCESS_ONCE(node->locked)) check so that the check occurs after an instruction in the critical section, then the barrier may be necessary.

In that case, just a barrier() call should be enough.

-Longman

author
#56 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 26 lines · Waiman Long
Quoted 25 lines · Jason Low
Quoted 24 lines · Waiman Long
Quoted 23 lines · Jason Low
Quoted 23 lines

Also, below is what the mcs_spin_lock() and mcs_spin_unlock() functions would look like after applying the proposed changes.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
          struct mcs_spin_node *prev;

          /* Init node */
          node->locked = 0;
          node->next   = NULL;

          prev = xchg(lock, node);
          if (likely(prev == NULL)) {
                  /* Lock acquired. No need to set node->locked since it
won't be used */
                  return;
          }
          ACCESS_ONCE(prev->next) = node;
          /* Wait until the lock holder passes the lock down */
          while (!ACCESS_ONCE(node->locked))
                  arch_mutex_cpu_relax();
          smp_mb();
Quoted 7 lines
Quoted 5 lines
Quoted 1 line

I wonder if a memory barrier is really needed here.

If the compiler can reorder the while (!ACCESS_ONCE(node->locked)) check so that the check occurs after an instruction in the critical section, then the barrier may be necessary.

In that case, just a barrier() call should be enough.

The cpu could still be executing out of order load instruction from the critical section before checking node->locked? Probably smp_mb() is still needed.

Tim

#57 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 32 lines · Tim Chen
Quoted 31 lines · Waiman Long
Quoted 30 lines · Jason Low
Quoted 24 lines · Waiman Long
Quoted 12 lines · Jason Low
Quoted 12 lines

Also, below is what the mcs_spin_lock() and mcs_spin_unlock() functions would look like after applying the proposed changes.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
           struct mcs_spin_node *prev;

           /* Init node */
           node->locked = 0;
           node->next   = NULL;
Quoted 11 lines
Quoted 11 lines
           prev = xchg(lock, node);
           if (likely(prev == NULL)) {
                   /* Lock acquired. No need to set node->locked since it
won't be used */
                   return;
           }
           ACCESS_ONCE(prev->next) = node;
           /* Wait until the lock holder passes the lock down */
           while (!ACCESS_ONCE(node->locked))
                   arch_mutex_cpu_relax();
           smp_mb();
Quoted 1 line

I wonder if a memory barrier is really needed here.

If the compiler can reorder the while (!ACCESS_ONCE(node->locked)) check so that the check occurs after an instruction in the critical section, then the barrier may be necessary.

Quoted 6 lines
Quoted 1 line

In that case, just a barrier() call should be enough.

The cpu could still be executing out of order load instruction from the critical section before checking node->locked? Probably smp_mb() is still needed.

Tim

But this is the lock function, a barrier() call should be enough to prevent the critical section from creeping up there. We certainly need some kind of memory barrier at the end of the unlock function.

-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>

author
#58 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 38 lines · Waiman Long
Quoted 37 lines · Tim Chen
Quoted 26 lines · Waiman Long
Quoted 25 lines · Jason Low
Quoted 24 lines · Waiman Long
Quoted 23 lines · Jason Low
Quoted 23 lines

Also, below is what the mcs_spin_lock() and mcs_spin_unlock() functions would look like after applying the proposed changes.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
           struct mcs_spin_node *prev;

           /* Init node */
           node->locked = 0;
           node->next   = NULL;

           prev = xchg(lock, node);
           if (likely(prev == NULL)) {
                   /* Lock acquired. No need to set node->locked since it
won't be used */
                   return;
           }
           ACCESS_ONCE(prev->next) = node;
           /* Wait until the lock holder passes the lock down */
           while (!ACCESS_ONCE(node->locked))
                   arch_mutex_cpu_relax();
           smp_mb();
Quoted 6 lines
Quoted 5 lines
Quoted 1 line

I wonder if a memory barrier is really needed here.

If the compiler can reorder the while (!ACCESS_ONCE(node->locked)) check so that the check occurs after an instruction in the critical section, then the barrier may be necessary.

In that case, just a barrier() call should be enough.

The cpu could still be executing out of order load instruction from the critical section before checking node->locked? Probably smp_mb() is still needed.

Quoted 5 lines
Quoted 1 line

Tim

But this is the lock function, a barrier() call should be enough to prevent the critical section from creeping up there. We certainly need some kind of memory barrier at the end of the unlock function.

I may be missing something. My understanding is that barrier only prevents the compiler from rearranging instructions, but not for cpu out of order execution (as in smp_mb). So cpu could read memory in the next critical section, before node->locked is true, (i.e. unlock has been completed). If we only have a simple barrier at end of mcs_lock, then say the code on CPU1 is

	mcs_lock
	x = 1;
	...
	x = 2;
	mcs_unlock

and CPU 2 is

	mcs_lock
	y = x;
	...
	mcs_unlock

We expect y to be 2 after the "y = x" assignment. But we we may execute the code as

	CPU1		CPU2
		
	x = 1;
	...		y = x;  ( y=1, out of order load)
	x = 2
	mcs_unlock
			Check node->locked==true
			continue executing critical section (y=1 when we expect y=2)

So we get y to be 1 when we expect that it should be 2. Adding smp_mb after the node->locked check in lock code

           ACCESS_ONCE(prev->next) = node;
           /* Wait until the lock holder passes the lock down */
           while (!ACCESS_ONCE(node->locked))
                    arch_mutex_cpu_relax();
           smp_mb();

should prevent this scenario.

Thanks.
Tim

Quoted 7 lines

-Longman
--
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>

#59 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 51 lines · Tim Chen
Quoted 9 lines · Waiman Long
Quoted 6 lines

The cpu could still be executing out of order load instruction from the critical section before checking node->locked? Probably smp_mb() is still needed.

Tim

But this is the lock function, a barrier() call should be enough to prevent the critical section from creeping up there. We certainly need some kind of memory barrier at the end of the unlock function.

I may be missing something. My understanding is that barrier only prevents the compiler from rearranging instructions, but not for cpu out of order execution (as in smp_mb). So cpu could read memory in the next critical section, before node->locked is true, (i.e. unlock has been completed). If we only have a simple barrier at end of mcs_lock, then say the code on CPU1 is

	mcs_lock
	x = 1;
	...
	x = 2;
	mcs_unlock

and CPU 2 is

	mcs_lock
	y = x;
	...
	mcs_unlock

We expect y to be 2 after the "y = x" assignment. But we we may execute the code as

	CPU1		CPU2
		
	x = 1;
	...		y = x;  ( y=1, out of order load)
	x = 2
	mcs_unlock
			Check node->locked==true
			continue executing critical section (y=1 when we expect y=2)

So we get y to be 1 when we expect that it should be 2. Adding smp_mb after the node->locked check in lock code

            ACCESS_ONCE(prev->next) = node;
            /* Wait until the lock holder passes the lock down */
            while (!ACCESS_ONCE(node->locked))
                     arch_mutex_cpu_relax();
            smp_mb();
Quoted 4 lines

should prevent this scenario.

Thanks.
Tim

If the lock and unlock functions are done right, there should be no overlap of critical section. So it is job of the lock/unlock functions to make sure that critical section code won't leak out. There should be some kind of memory barrier at the beginning of the lock function and the end of the unlock function.

The critical section also likely to have branches. The CPU may speculatively execute code on the 2 branches, but one of them will be discarded once the branch condition is known. Also arch_mutex_cpu_relax() is a compiler barrier by itself. So we may not need a barrier() after all. The while statement is a branch instruction, any code after that can only be speculatively executed and cannot be committed until the branch is done.

In x86, the smp_mb() function translated to a mfence instruction which cost time. That is why I try to get rid of it if it is not necessary.

Regards,
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>

author
#60 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 63 lines · Waiman Long
Quoted 51 lines · Tim Chen
Quoted 9 lines · Waiman Long
Quoted 6 lines

The cpu could still be executing out of order load instruction from the critical section before checking node->locked? Probably smp_mb() is still needed.

Tim

But this is the lock function, a barrier() call should be enough to prevent the critical section from creeping up there. We certainly need some kind of memory barrier at the end of the unlock function.

I may be missing something. My understanding is that barrier only prevents the compiler from rearranging instructions, but not for cpu out of order execution (as in smp_mb). So cpu could read memory in the next critical section, before node->locked is true, (i.e. unlock has been completed). If we only have a simple barrier at end of mcs_lock, then say the code on CPU1 is

	mcs_lock
	x = 1;
	...
	x = 2;
	mcs_unlock

and CPU 2 is

	mcs_lock
	y = x;
	...
	mcs_unlock

We expect y to be 2 after the "y = x" assignment. But we we may execute the code as

	CPU1		CPU2
		
	x = 1;
	...		y = x;  ( y=1, out of order load)
	x = 2
	mcs_unlock
			Check node->locked==true
			continue executing critical section (y=1 when we expect y=2)

So we get y to be 1 when we expect that it should be 2. Adding smp_mb after the node->locked check in lock code

            ACCESS_ONCE(prev->next) = node;
            /* Wait until the lock holder passes the lock down */
            while (!ACCESS_ONCE(node->locked))
                     arch_mutex_cpu_relax();
            smp_mb();
Quoted 4 lines

should prevent this scenario.

Thanks.
Tim

If the lock and unlock functions are done right, there should be no overlap of critical section. So it is job of the lock/unlock functions to make sure that critical section code won't leak out. There should be some kind of memory barrier at the beginning of the lock function and the end of the unlock function.

Quoted 7 lines

The critical section also likely to have branches. The CPU may speculatively execute code on the 2 branches, but one of them will be discarded once the branch condition is known. Also arch_mutex_cpu_relax() is a compiler barrier by itself. So we may not need a barrier() after all. The while statement is a branch instruction, any code after that can only be speculatively executed and cannot be committed until the branch is done.

But the condition code may be checked after speculative execution? The condition may not be true during speculative execution and only turns true when we check the condition, and take that branch?

The thing that bothers me is without memory barrier after the while statement, we could speculatively execute before affirming the lock is in acquired state. Then when we check the lock, the lock is set to acquired state in the mean time.
We could be loading some memory entry *before*
the node->locked has been set true. I think a smp_rmb (if not a smp_mb) should be set after the while statement.

At first I was also thinking that the memory barrier is not necessary but Paul convinced me otherwise in a previous email.
https://lkml.org/lkml/2013/9/27/523

Quoted 4 lines

In x86, the smp_mb() function translated to a mfence instruction which cost time. That is why I try to get rid of it if it is not necessary.

I also hope that the memory barrier is not necessary and I am missing something obvious. But I haven't been able to persuade myself.

Tim

--
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>

#37 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Jason Low
Quoted 17 lines · Jason Low
Quoted 16 lines · Tim Chen

Okay, that would makes sense for consistency because we always first set node->lock = 0 at the top of the function.

If we prefer to optimize this a bit though, perhaps we can first move the node->lock = 0 so that it gets executed after the "if (likely(prev == NULL)) {}" code block and then delete "node->lock = 1" inside the code block.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
        struct mcs_spin_node *prev;

        /* Init node */
        node->next   = NULL;
Quoted 6 lines
Quoted 6 lines
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired */
                return;
        }
        node->locked = 0;

You can remove the locked flag setting statement inside if (prev == NULL), but you can't clear the locked flag after xchg(). In the interval between xchg() and locked=0, the previous lock owner may come in and set the flag. Now if your clear it, the thread will loop forever. You have to clear it before xchg().

-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>

#38 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

Quoted 29 lines · Waiman Long
Quoted 20 lines · Jason Low
Quoted 18 lines · Tim Chen

Okay, that would makes sense for consistency because we always first set node->lock = 0 at the top of the function.

If we prefer to optimize this a bit though, perhaps we can first move the node->lock = 0 so that it gets executed after the "if (likely(prev == NULL)) {}" code block and then delete "node->lock = 1" inside the code block.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
        struct mcs_spin_node *prev;

        /* Init node */
        node->next   = NULL;
Quoted 6 lines
Quoted 6 lines
        prev = xchg(lock, node);
        if (likely(prev == NULL)) {
                /* Lock acquired */
                return;
        }
        node->locked = 0;
Quoted 5 lines

You can remove the locked flag setting statement inside if (prev == NULL), but you can't clear the locked flag after xchg(). In the interval between xchg() and locked=0, the previous lock owner may come in and set the flag. Now if your clear it, the thread will loop forever. You have to clear it before xchg().

Yes, in my most recent version, I left locked = 0 in its original place so that the xchg() can act as a barrier for it.

The other option would have been to put another barrier after locked =
0. I went with leaving locked = 0 in its original place so that we don't need that extra barrier.

--
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>

#61 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Tim Chen
Quoted 19 lines · Tim Chen
Quoted 7 lines · Waiman Long

If the lock and unlock functions are done right, there should be no overlap of critical section. So it is job of the lock/unlock functions to make sure that critical section code won't leak out. There should be some kind of memory barrier at the beginning of the lock function and the end of the unlock function.

Quoted 7 lines

The critical section also likely to have branches. The CPU may speculatively execute code on the 2 branches, but one of them will be discarded once the branch condition is known. Also arch_mutex_cpu_relax() is a compiler barrier by itself. So we may not need a barrier() after all. The while statement is a branch instruction, any code after that can only be speculatively executed and cannot be committed until the branch is done.

But the condition code may be checked after speculative execution? The condition may not be true during speculative execution and only turns true when we check the condition, and take that branch?

Quoted 7 lines

The thing that bothers me is without memory barrier after the while statement, we could speculatively execute before affirming the lock is in acquired state. Then when we check the lock, the lock is set to acquired state in the mean time.
We could be loading some memory entry *before*
the node->locked has been set true. I think a smp_rmb (if not a smp_mb) should be set after the while statement.

Yes, I think a smp_rmb() make sense here to correspond to the smp_wmb() in the unlock path.

BTW, you need to move the node->locked = 0; statement before xchg() if you haven't done so.

-Longman

#39 lore raw

Re: [PATCH v6 5/6] MCS Lock: Restructure the MCS lock defines and locking code into its own file

In reply to Jason Low
Quoted 32 lines · Jason Low
Quoted 26 lines · Waiman Long
Quoted 18 lines · Jason Low
Quoted 17 lines · Tim Chen

Okay, that would makes sense for consistency because we always first set node->lock = 0 at the top of the function.

If we prefer to optimize this a bit though, perhaps we can first move the node->lock = 0 so that it gets executed after the "if (likely(prev == NULL)) {}" code block and then delete "node->lock = 1" inside the code block.

static noinline
void mcs_spin_lock(struct mcs_spin_node **lock, struct mcs_spin_node *node)

{
         struct mcs_spin_node *prev;

         /* Init node */
         node->next   = NULL;
Quoted 6 lines
Quoted 6 lines
         prev = xchg(lock, node);
         if (likely(prev == NULL)) {
                 /* Lock acquired */
                 return;
         }
         node->locked = 0;
Quoted 5 lines

You can remove the locked flag setting statement inside if (prev == NULL), but you can't clear the locked flag after xchg(). In the interval between xchg() and locked=0, the previous lock owner may come in and set the flag. Now if your clear it, the thread will loop forever. You have to clear it before xchg().

Quoted 6 lines

Yes, in my most recent version, I left locked = 0 in its original place so that the xchg() can act as a barrier for it.

The other option would have been to put another barrier after locked =
0. I went with leaving locked = 0 in its original place so that we don't need that extra barrier.

I don't think putting another barrier after locked=0 will work. Chronologically, the flag must be cleared before the node address is saved in the lock field. There is no way to guarantee that except by putting the locked=0 before xchg().

-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>