Skip to contentSkip to conversation

Kernel fixes to spin_is_locked()

PATCH New Paul E. McKenney 2 participants 7 messages May 14, 2018 – May 15, 2018

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover Kernel fixes to spin_is_locked()

Hello!

This series contains fixes to the kernel related to the semantics of spin_is_locked(), all courtesy of Andrea Parri, and all ready for inclusion in -tip:

1. Document the semantics of spin_is_locked() by adding a docbook
header comment.

2. Remove smp_mb() from arch_spin_is_locked(), given that the
new order-free spin_is_locked() semantics require no such barrier.

3. Clean up comment and #ifndef for {,queued_}spin_is_locked().
The comment was "XXX think about spin_is_locked", and I can attest that we have now done some serious thinking. ;-)

Thanx, Paul

------------------------------------------------------------------------

#1 lore raw

PATCH 1/3 locking: Document the semantics of spin_is_locked()

From: Andrea Parri <[email protected]>

There appeared to be a certain, recurrent uncertainty concerning the semantics of spin_is_locked(), likely a consequence of the fact that this semantics remains undocumented or that it has been historically linked to the (likewise unclear) semantics of spin_unlock_wait().

A recent auditing [1] of the callers of the primitive confirmed that none of them are relying on particular ordering guarantees; document this semantics by adding a docbook header to spin_is_locked(). Also, describe behaviors specific to certain CONFIG_SMP=n builds.

[1] https://marc.info/?l=linux-kernel&m=151981440005264&w=2

    https://marc.info/?l=linux-kernel&m=152042843808540&w=2
    https://marc.info/?l=linux-kernel&m=152043346110262&w=2
---
 include/linux/spinlock.h | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
include/linux/spinlock.h +18 -0
index 4894d322d258..1e8a46435838 100644
@@ -380,6 +380,24 @@ static __always_inline int spin_trylock_irq(spinlock_t *lock)
 	raw_spin_trylock_irqsave(spinlock_check(lock), flags); \
 })
 
+/**
+ * spin_is_locked() - Check whether a spinlock is locked.
+ * @lock: Pointer to the spinlock.
+ *
+ * This function is NOT required to provide any memory ordering
+ * guarantees; it could be used for debugging purposes or, when
+ * additional synchronization is needed, accompanied with other
+ * constructs (memory barriers) enforcing the synchronization.
+ *
+ * Returns: 1 if @lock is locked, 0 otherwise.
+ *
+ * Note that the function only tells you that the spinlock is
+ * seen to be locked, not that it is locked on your CPU.
+ *
+ * Further, on CONFIG_SMP=n builds with CONFIG_DEBUG_SPINLOCK=n,
+ * the return value is always 0 (see include/linux/spinlock_up.h).
+ * Therefore you should not rely heavily on the return value.
+ */
 static __always_inline int spin_is_locked(spinlock_t *lock)
 {
 	return raw_spin_is_locked(&lock->rlock);
Signature · 1 line
2.5.2
#2 lore raw

[tip:locking/core] locking/spinlocks: Document the semantics of spin_is_locked()

locking/spinlocks: Document the semantics of spin_is_locked()
Commit-ID:  b7e4aadef28f217de8907eec60a964328797a2be
Gitweb:     https://git.kernel.org/tip/b7e4aadef28f217de8907eec60a964328797a2be
Author:     Andrea Parri <[email protected]>
AuthorDate: Mon, 14 May 2018 16:01:27 -0700
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 15 May 2018 08:11:15 +0200

locking/spinlocks: Document the semantics of spin_is_locked()

There appeared to be a certain, recurrent uncertainty concerning the semantics of spin_is_locked(), likely a consequence of the fact that this semantics remains undocumented or that it has been historically linked to the (likewise unclear) semantics of spin_unlock_wait().

A recent auditing [1] of the callers of the primitive confirmed that none of them are relying on particular ordering guarantees; document this semantics by adding a docbook header to spin_is_locked(). Also, describe behaviors specific to certain CONFIG_SMP=n builds.

[1] https://marc.info/?l=linux-kernel&m=151981440005264&w=2

    https://marc.info/?l=linux-kernel&m=152042843808540&w=2
    https://marc.info/?l=linux-kernel&m=152043346110262&w=2
---
 include/linux/spinlock.h | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
include/linux/spinlock.h +18 -0
index 4894d322d258..1e8a46435838 100644
@@ -380,6 +380,24 @@ static __always_inline int spin_trylock_irq(spinlock_t *lock)
 	raw_spin_trylock_irqsave(spinlock_check(lock), flags); \
 })
 
+/**
+ * spin_is_locked() - Check whether a spinlock is locked.
+ * @lock: Pointer to the spinlock.
+ *
+ * This function is NOT required to provide any memory ordering
+ * guarantees; it could be used for debugging purposes or, when
+ * additional synchronization is needed, accompanied with other
+ * constructs (memory barriers) enforcing the synchronization.
+ *
+ * Returns: 1 if @lock is locked, 0 otherwise.
+ *
+ * Note that the function only tells you that the spinlock is
+ * seen to be locked, not that it is locked on your CPU.
+ *
+ * Further, on CONFIG_SMP=n builds with CONFIG_DEBUG_SPINLOCK=n,
+ * the return value is always 0 (see include/linux/spinlock_up.h).
+ * Therefore you should not rely heavily on the return value.
+ */
 static __always_inline int spin_is_locked(spinlock_t *lock)
 {
 	return raw_spin_is_locked(&lock->rlock);
#3 lore raw

PATCH 2/3 arm64: Remove smp_mb() from arch_spin_is_locked()

In reply to Paul E. McKenney

From: Andrea Parri <[email protected]>

Commit 38b850a73034f ("arm64: spinlock: order spin_{is_locked,unlock_wait} against local locks") added an smp_mb() to arch_spin_is_locked(), in order "to ensure that the lock value is always loaded after any other locks have been taken by the current CPU", and reported one example (the "insane case" in ipc/sem.c) relying on such guarantee.

It is however understood that spin_is_locked() is not required to provide such an ordering guarantee (a guarantee that is currently not provided by all the implementations/archs), and that callers relying on such ordering should instead insert suitable memory barriers before acting on the result of spin_is_locked().

Following a recent auditing [1] of the callers of {,raw_}spin_is_locked(), revealing that none of them are relying on the ordering guarantee anymore, this commit removes the leading smp_mb() from the primitive thus reverting 38b850a73034f.

[1] https://marc.info/?l=linux-kernel&m=151981440005264&w=2

    https://marc.info/?l=linux-kernel&m=152042843808540&w=2
    https://marc.info/?l=linux-kernel&m=152043346110262&w=2
Cc Catalin Marinas <[email protected]>
Cc Will Deacon <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc "Paul E. McKenney" <[email protected]>
Cc Linus Torvalds <[email protected]>
---
 arch/arm64/include/asm/spinlock.h | 5 -----
 1 file changed, 5 deletions(-)
arch/arm64/include/asm/spinlock.h +0 -5
index ebdae15d665d..26c5bd7d88d8 100644
@@ -122,11 +122,6 @@ static inline int arch_spin_value_unlocked(arch_spinlock_t lock)
 
 static inline int arch_spin_is_locked(arch_spinlock_t *lock)
 {
-	/*
-	 * Ensure prior spin_lock operations to other locks have completed
-	 * on this CPU before we test whether "lock" is locked.
-	 */
-	smp_mb(); /* ^^^ */
 	return !arch_spin_value_unlocked(READ_ONCE(*lock));
 }
 
Signature · 1 line
2.5.2
#4 lore raw

[tip:locking/core] locking/spinlocks/arm64: Remove smp_mb() from arch_spin_is_locked()

locking/spinlocks/arm64: Remove smp_mb() from arch_spin_is_locked()
Commit-ID:  c6f5d02b6a0fb91be5d656885ce02cf28952181d
Gitweb:     https://git.kernel.org/tip/c6f5d02b6a0fb91be5d656885ce02cf28952181d
Author:     Andrea Parri <[email protected]>
AuthorDate: Mon, 14 May 2018 16:01:28 -0700
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 15 May 2018 08:11:15 +0200

locking/spinlocks/arm64: Remove smp_mb() from arch_spin_is_locked()

The following commit:

38b850a73034f ("arm64: spinlock: order spin_{is_locked,unlock_wait} against local locks")

... added an smp_mb() to arch_spin_is_locked(), in order
"to ensure that the lock value is always loaded after any other locks have been taken by the current CPU", and reported one example (the "insane case" in ipc/sem.c) relying on such guarantee.

It is however understood that spin_is_locked() is not required to provide such an ordering guarantee (a guarantee that is currently not provided by all the implementations/archs), and that callers relying on such ordering should instead insert suitable memory barriers before acting on the result of spin_is_locked().

Following a recent auditing [1] of the callers of {,raw_}spin_is_locked(), revealing that none of them are relying on the ordering guarantee anymore, this commit removes the leading smp_mb() from the primitive thus reverting 38b850a73034f.

[1] https://marc.info/?l=linux-kernel&m=151981440005264&w=2

    https://marc.info/?l=linux-kernel&m=152042843808540&w=2
    https://marc.info/?l=linux-kernel&m=152043346110262&w=2
---
 arch/arm64/include/asm/spinlock.h | 5 -----
 1 file changed, 5 deletions(-)
arch/arm64/include/asm/spinlock.h +0 -5
index ebdae15d665d..26c5bd7d88d8 100644
@@ -122,11 +122,6 @@ static inline int arch_spin_value_unlocked(arch_spinlock_t lock)
 
 static inline int arch_spin_is_locked(arch_spinlock_t *lock)
 {
-	/*
-	 * Ensure prior spin_lock operations to other locks have completed
-	 * on this CPU before we test whether "lock" is locked.
-	 */
-	smp_mb(); /* ^^^ */
 	return !arch_spin_value_unlocked(READ_ONCE(*lock));
 }
 
#5 lore raw

PATCH 3/3 locking: Clean up comment and #ifndef for {,queued_}spin_is_locked()

In reply to Paul E. McKenney

From: Andrea Parri <[email protected]>

Removes "#ifndef queued_spin_is_locked" from the generic code: this is unused and it's reasonable to conclude that it will continue to be unused.

Also removes the comment about spin_is_locked() from mutex_is_locked():
the comment remains valid but not particularly useful.

---
 include/asm-generic/qspinlock.h | 2 --
 include/linux/mutex.h           | 3 ---
 2 files changed, 5 deletions(-)
include/asm-generic/qspinlock.h +0 -2
index b37b4ad7eb94..dc4e4ac4937e 100644
@@ -26,7 +26,6 @@
  * @lock: Pointer to queued spinlock structure
  * Return: 1 if it is locked, 0 otherwise
  */
-#ifndef queued_spin_is_locked
 static __always_inline int queued_spin_is_locked(struct qspinlock *lock)
 {
 	/*
@@ -35,7 +34,6 @@ static __always_inline int queued_spin_is_locked(struct qspinlock *lock)
 	 */
 	return atomic_read(&lock->val);
 }
-#endif
 
 /**
  * queued_spin_value_unlocked - is the spinlock structure unlocked?
include/linux/mutex.h +0 -3
index 14bc0d5d0ee5..3093dd162424 100644
@@ -146,9 +146,6 @@ extern void __mutex_init(struct mutex *lock, const char *name,
  */
 static inline bool mutex_is_locked(struct mutex *lock)
 {
-	/*
-	 * XXX think about spin_is_locked
-	 */
 	return __mutex_owner(lock) != NULL;
 }
 
Signature · 1 line
2.5.2
#6 lore raw

[tip:locking/core] locking/spinlocks: Clean up comment and #ifndef for {,queued_}spin_is_locked()

locking/spinlocks: Clean up comment and #ifndef for {,queued_}spin_is_locked()
Commit-ID:  1362ae43c503a4e333ab6948fc4c6e0e794e1558
Gitweb:     https://git.kernel.org/tip/1362ae43c503a4e333ab6948fc4c6e0e794e1558
Author:     Andrea Parri <[email protected]>
AuthorDate: Mon, 14 May 2018 16:01:29 -0700
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 15 May 2018 08:11:15 +0200

locking/spinlocks: Clean up comment and #ifndef for {,queued_}spin_is_locked()

Removes "#ifndef queued_spin_is_locked" from the generic code: this is unused and it's reasonable to conclude that it will continue to be unused.

Also removes the comment about spin_is_locked() from mutex_is_locked():
the comment remains valid but not particularly useful.

---
 include/asm-generic/qspinlock.h | 2 --
 include/linux/mutex.h           | 3 ---
 2 files changed, 5 deletions(-)
include/asm-generic/qspinlock.h +0 -2
index a8ed0a352d75..9cc457597ddf 100644
@@ -26,7 +26,6 @@
  * @lock: Pointer to queued spinlock structure
  * Return: 1 if it is locked, 0 otherwise
  */
-#ifndef queued_spin_is_locked
 static __always_inline int queued_spin_is_locked(struct qspinlock *lock)
 {
 	/*
@@ -35,7 +34,6 @@ static __always_inline int queued_spin_is_locked(struct qspinlock *lock)
 	 */
 	return atomic_read(&lock->val);
 }
-#endif
 
 /**
  * queued_spin_value_unlocked - is the spinlock structure unlocked?
include/linux/mutex.h +0 -3
index 14bc0d5d0ee5..3093dd162424 100644
@@ -146,9 +146,6 @@ extern void __mutex_init(struct mutex *lock, const char *name,
  */
 static inline bool mutex_is_locked(struct mutex *lock)
 {
-	/*
-	 * XXX think about spin_is_locked
-	 */
 	return __mutex_owner(lock) != NULL;
 }