Skip to contentSkip to conversation

locking/mutex: Rewrite basic mutex

PATCH In review Peter Zijlstra 13 participants 59 messages Oct 7, 2016 – Jan 12, 2017

Subsystems (28)
DRM drivers Arm (32-bit) x86 PowerPC +24 more subsystems
lore
Share & tools
lore

Peter Zijlstra

author maintainer
#0 lore raw

cover locking/mutex: Rewrite basic mutex

Hi all,

Since you all should not be sending patches during the merge window, I figured I should to keep you all occupied with something.

Please review, test and otherwise try to break these here patches.

I would like to get these patches into -tip (and -next) once the merge window closes, so please spend these quiet days staring at this stuff.

Small changes only, mostly the handoff logic as suggested by Waiman last time.

---
 arch/alpha/include/asm/mutex.h           |   9 -
 arch/arc/include/asm/mutex.h             |  18 -
 arch/arm/include/asm/mutex.h             |  21 --
 arch/arm64/include/asm/Kbuild            |   1 -
 arch/avr32/include/asm/mutex.h           |   9 -
 arch/blackfin/include/asm/Kbuild         |   1 -
 arch/c6x/include/asm/mutex.h             |   6 -
 arch/cris/include/asm/mutex.h            |   9 -
 arch/frv/include/asm/mutex.h             |   9 -
 arch/h8300/include/asm/mutex.h           |   9 -
 arch/hexagon/include/asm/mutex.h         |   8 -
 arch/ia64/include/asm/mutex.h            |  90 -----
 arch/m32r/include/asm/mutex.h            |   9 -
 arch/m68k/include/asm/Kbuild             |   1 -
 arch/metag/include/asm/Kbuild            |   1 -
 arch/microblaze/include/asm/mutex.h      |   1 -
 arch/mips/include/asm/Kbuild             |   1 -
 arch/mn10300/include/asm/mutex.h         |  16 -
 arch/nios2/include/asm/mutex.h           |   1 -
 arch/openrisc/include/asm/mutex.h        |  27 --
 arch/parisc/include/asm/Kbuild           |   1 -
 arch/powerpc/include/asm/mutex.h         | 132 -------
 arch/s390/include/asm/mutex.h            |   9 -
 arch/score/include/asm/mutex.h           |   6 -
 arch/sh/include/asm/mutex-llsc.h         | 109 ------
 arch/sh/include/asm/mutex.h              |  12 -
 arch/sparc/include/asm/Kbuild            |   1 -
 arch/tile/include/asm/Kbuild             |   1 -
 arch/um/include/asm/Kbuild               |   1 -
 arch/unicore32/include/asm/mutex.h       |  20 --
 arch/x86/include/asm/mutex.h             |   5 -
 arch/x86/include/asm/mutex_32.h          | 110 ------
 arch/x86/include/asm/mutex_64.h          | 127 -------
 arch/xtensa/include/asm/mutex.h          |   9 -
 drivers/gpu/drm/i915/i915_gem_shrinker.c |  26 +-
 drivers/gpu/drm/msm/msm_gem_shrinker.c   |  23 +-
 include/asm-generic/mutex-dec.h          |  88 -----
 include/asm-generic/mutex-null.h         |  19 --
 include/asm-generic/mutex-xchg.h         | 120 -------
 include/asm-generic/mutex.h              |   9 -
 include/linux/mutex-debug.h              |  24 --
 include/linux/mutex.h                    |  46 ++-
 kernel/Kconfig.locks                     |   2 +-
 kernel/locking/mutex-debug.c             |  13 -
 kernel/locking/mutex-debug.h             |  10 -
 kernel/locking/mutex.c                   | 569 ++++++++++++++++++-------------
 kernel/locking/mutex.h                   |  26 --
 kernel/sched/core.c                      |   2 +-
 48 files changed, 364 insertions(+), 1403 deletions(-)
author maintainer
#1 lore raw

PATCH 1/8 locking/drm: Kill mutex trickery

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

Cc Daniel Vetter <[email protected]>
Cc Chris Wilson <[email protected]>
Cc Rob Clark <[email protected]>
---
 drivers/gpu/drm/i915/i915_gem_shrinker.c |   26 +++-----------------------
 drivers/gpu/drm/msm/msm_gem_shrinker.c   |   23 +++--------------------
 2 files changed, 6 insertions(+), 43 deletions(-)
drivers/gpu/drm/i915/i915_gem_shrinker.c +3 -23
@@ -35,19 +35,6 @@
 #include "i915_drv.h"
 #include "i915_trace.h"
 
-static bool mutex_is_locked_by(struct mutex *mutex, struct task_struct *task)
-{
-	if (!mutex_is_locked(mutex))
-		return false;
-
-#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER)
-	return mutex->owner == task;
-#else
-	/* Since UP may be pre-empted, we cannot assume that we own the lock */
-	return false;
-#endif
-}
-
 static int num_vma_bound(struct drm_i915_gem_object *obj)
 {
 	struct i915_vma *vma;
@@ -238,17 +225,10 @@ unsigned long i915_gem_shrink_all(struct
 
 static bool i915_gem_shrinker_lock(struct drm_device *dev, bool *unlock)
 {
-	if (!mutex_trylock(&dev->struct_mutex)) {
-		if (!mutex_is_locked_by(&dev->struct_mutex, current))
-			return false;
-
-		if (to_i915(dev)->mm.shrinker_no_lock_stealing)
-			return false;
-
-		*unlock = false;
-	} else
-		*unlock = true;
+	if (!mutex_trylock(&dev->struct_mutex))
+		return false;
 
+	*unlock = true;
 	return true;
 }
 
drivers/gpu/drm/msm/msm_gem_shrinker.c +3 -20
@@ -18,29 +18,12 @@
 #include "msm_drv.h"
 #include "msm_gem.h"
 
-static bool mutex_is_locked_by(struct mutex *mutex, struct task_struct *task)
-{
-	if (!mutex_is_locked(mutex))
-		return false;
-
-#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_MUTEXES)
-	return mutex->owner == task;
-#else
-	/* Since UP may be pre-empted, we cannot assume that we own the lock */
-	return false;
-#endif
-}
-
 static bool msm_gem_shrinker_lock(struct drm_device *dev, bool *unlock)
 {
-	if (!mutex_trylock(&dev->struct_mutex)) {
-		if (!mutex_is_locked_by(&dev->struct_mutex, current))
-			return false;
-		*unlock = false;
-	} else {
-		*unlock = true;
-	}
+	if (!mutex_trylock(&dev->struct_mutex))
+		return false;
 
+	*unlock = true;
 	return true;
 }
 
author maintainer
#2 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 2 lines · Peter Zijlstra

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

So something like the below would serve as a replacement for your previous hacks. Is this API something acceptable to people? Ingo, Thomas?

---
 include/linux/mutex.h | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)
include/linux/mutex.h +25 -0
index 4d3bccabbea5..afcff2c85957 100644
@@ -189,4 +189,29 @@ extern void mutex_unlock(struct mutex *lock);
 
 extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);
 
+enum mutex_trylock_recursive_enum {
+	mutex_trylock_failed = 0,
+	mutex_trylock_success = 1,
+	mutex_trylock_recursive,
+};
+
+/**
+ * mutex_trylock_recursive - trylock variant that allows recursive locking
+ * @lock: mutex to be locked
+ *
+ *
+ * Returns:
+ *  mutex_trylock_failed    - trylock failed,
+ *  mutex_trylock_success   - lock acquired,
+ *  mutex_trylock_recursive - we already owned the lock.
+ */
+static inline enum mutex_trylock_recursive_enum
+mutex_trylock_recursive(struct mutex *lock)
+{
+	if (unlikely(__mutex_owner(lock) == current))
+		return mutex_trylock_recursive;
+
+	return mutex_trylock(lock);
+}
+
 #endif /* __LINUX_MUTEX_H */
#3 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 4 lines · Peter Zijlstra

So something like the below would serve as a replacement for your previous hacks. Is this API something acceptable to people? Ingo, Thomas?

Ugh. I think the concept is fine, but can we place make these enum's be all upper case or something to make them really stand out visually.

Also, it's a bit subtle how this depends on the success/failure enums to just have the same values as the return value of the non-try mutex. So a comment about why that particular numbering is important above the enum definition, please?

You _kind_ of imply that magic numbering requirementby explicitly using " = 0" and " = 1", but quite frankly, in many ways that's really nasty, because I can see somebody looking at that enum definition and saying "that makes no sense: it sets *two* of the three values explicitly, and the values it sets explicitly are the same that it would get implicitly _anyway_, so I'll just clean this up".

So I really think that enum needs a comment on the forced choice of values, and making them all caps would make the users more obvious, I think.

The other choice would be to just make the choices be negative (== recursive), zero (== failed) or positive (== got lock), which allows for the same value re-use for the non-recursive case, and you could avoid the enum entirely.

Linus

author maintainer
#4 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 2 lines · Linus Torvalds

Ugh. I think the concept is fine, but can we place make these enum's be all upper case or something to make them really stand out visually.

OK.

Quoted 4 lines

The other choice would be to just make the choices be negative (== recursive), zero (== failed) or positive (== got lock), which allows for the same value re-use for the non-recursive case, and you could avoid the enum entirely.

I thought about that, but liked the enum better for having to then spell it out.

I'll go make the enum shout and add comment as you suggest.

#5 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 9 lines · Peter Zijlstra
Quoted 4 lines · Linus Torvalds

The other choice would be to just make the choices be negative (== recursive), zero (== failed) or positive (== got lock), which allows for the same value re-use for the non-recursive case, and you could avoid the enum entirely.

I thought about that, but liked the enum better for having to then spell it out.

I'll go make the enum shout and add comment as you suggest.

I like the idea of having a tri-state returned value (<0, 0, >0). I don't mind having the enum, but just making mutex_trylock_recursive equal to -1 will be great.

Cheers,
Longman

#6 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

In reply to Peter Zijlstra
Quoted 18 lines · Peter Zijlstra
Quoted 2 lines · Peter Zijlstra

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

So something like the below would serve as a replacement for your previous hacks. Is this API something acceptable to people? Ingo, Thomas?

---
 include/linux/mutex.h | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)
include/linux/mutex.h +0 -0
index 4d3bccabbea5..afcff2c85957 100644

include/linux/mutex.h extern void mutex_unlock(struct mutex *lock); · line 195

Quoted 7 lines
include/linux/mutex.h +4 -0
@@ -189,4 +189,29 @@ extern void mutex_unlock(struct mutex *lock);
 
 extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);
 
+enum mutex_trylock_recursive_enum {
+	mutex_trylock_failed = 0,
+	mutex_trylock_success = 1,
+	mutex_trylock_recursive,

Upper case enum symbols please, if at all.

Quoted 10 lines
include/linux/mutex.h +10 -0
+};
+
+/**
+ * mutex_trylock_recursive - trylock variant that allows recursive locking
+ * @lock: mutex to be locked
+ *
+ *
+ * Returns:
+ *  mutex_trylock_failed    - trylock failed,
+ *  mutex_trylock_success   - lock acquired,

include/linux/mutex.h extern void mutex_unlock(struct mutex *lock); · line 215

Quoted 10 lines
include/linux/mutex.h +10 -0
+ *  mutex_trylock_recursive - we already owned the lock.
+ */
+static inline enum mutex_trylock_recursive_enum
+mutex_trylock_recursive(struct mutex *lock)
+{
+	if (unlikely(__mutex_owner(lock) == current))
+		return mutex_trylock_recursive;
+
+	return mutex_trylock(lock);
+}

Hmm. I'm not a great fan of this, because that requires an conditional unlock mechanism.

       res = trylock_recursive(lock);
       if (res == FAILED)
       	       goto out;
       .....

       if (res == SUCCESS)
       	       unlock(lock);

While if you actually keep track of recursion you can do:

      if (!trylock_recursive(lock))
		goto out;

      ....

      unlock_recursive(lock);

or even:

     lock_recursive(lock);

     unlock_recursive(lock);

That's making lock/trylock and unlock symetric, so its obvious in the source what's going on and the recursion tracking allows for better debugability.

Thanks,

tglx

author maintainer
#7 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 22 lines · Thomas Gleixner

Hmm. I'm not a great fan of this, because that requires an conditional unlock mechanism.

       res = trylock_recursive(lock);
       if (res == FAILED)
       	       goto out;
       .....

       if (res == SUCCESS)
       	       unlock(lock);

While if you actually keep track of recursion you can do:

      if (!trylock_recursive(lock))
		goto out;

      ....

      unlock_recursive(lock);

or even:

Quoted 7 lines
     lock_recursive(lock);

     unlock_recursive(lock);

That's making lock/trylock and unlock symetric, so its obvious in the source what's going on and the recursion tracking allows for better debugability.

Hurm,. so I thought that in general we disliked recursive locking because it quickly turns in to a horrible mess.

Adding such primitives makes it 'easy' to use recursive locking and then where does it stop?

#8 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 31 lines · Peter Zijlstra
Quoted 22 lines · Thomas Gleixner

Hmm. I'm not a great fan of this, because that requires an conditional unlock mechanism.

       res = trylock_recursive(lock);
       if (res == FAILED)
       	       goto out;
       .....

       if (res == SUCCESS)
       	       unlock(lock);

While if you actually keep track of recursion you can do:

      if (!trylock_recursive(lock))
		goto out;

      ....

      unlock_recursive(lock);

or even:

Quoted 7 lines
     lock_recursive(lock);

     unlock_recursive(lock);

That's making lock/trylock and unlock symetric, so its obvious in the source what's going on and the recursion tracking allows for better debugability.

Quoted 5 lines

Hurm,. so I thought that in general we disliked recursive locking because it quickly turns in to a horrible mess.

Adding such primitives makes it 'easy' to use recursive locking and then where does it stop?

Well, when you add just trylock_recursive then people are going to use it anyway no matter whether it is easy or not.

So if we decide to provide something which supports recursive locking for mutexes then we are better off doing it with a proper set of functions and not just a single undebugable wrapper.

Thanks,

tglx

author maintainer
#9 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 6 lines · Thomas Gleixner

Well, when you add just trylock_recursive then people are going to use it anyway no matter whether it is easy or not.

So if we decide to provide something which supports recursive locking for mutexes then we are better off doing it with a proper set of functions and not just a single undebugable wrapper.

So ideally I'd say, no recursive stuff at all. But that means the GEM people need to either do custom hacks (and you know that will spread), or need to be convinced to rework their locking somehow.

author maintainer
#10 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

In reply to Peter Zijlstra
Quoted 7 lines · Peter Zijlstra
Quoted 2 lines · Peter Zijlstra

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

So something like the below would serve as a replacement for your previous hacks. Is this API something acceptable to people?

Compile tested only.. Daniel reminded me in another thread.

---
Subject: locking/mutex,drm: Introduce mutex_trylock_recursive()
From: Peter Zijlstra <[email protected]>
Date: Fri, 7 Oct 2016 17:43:51 +0200

By popular DRM demand, introduce mutex_trylock_recursive() to fix up the two GEM users.

Without this it is very easy for these drivers to get stuck in low-memory situations and trigger OOM. Work is in progress to remove the need for this in at least i915.

Cc Daniel Vetter <[email protected]>
Cc Chris Wilson <[email protected]>
Cc Rob Clark <[email protected]>
---
 drivers/gpu/drm/i915/i915_gem_shrinker.c | 15 ++++++++++++---
 drivers/gpu/drm/msm/msm_gem_shrinker.c   | 16 ++++++++++++----
 include/linux/mutex.h                    | 31 +++++++++++++++++++++++++++++++
 scripts/checkpatch.pl                    |  6 ++++++
 4 files changed, 61 insertions(+), 7 deletions(-)
drivers/gpu/drm/i915/i915_gem_shrinker.c +12 -3
index e9bd2a81d03a..5543d993a50e 100644
@@ -227,11 +227,20 @@ unsigned long i915_gem_shrink_all(struct drm_i915_private *dev_priv)
 
 static bool i915_gem_shrinker_lock(struct drm_device *dev, bool *unlock)
 {
-	if (!mutex_trylock(&dev->struct_mutex))
+	switch (mutex_trylock_recursive(&dev->struct_mutex)) {
+	case MUTEX_TRYLOCK_FAILED:
 		return false;
 
-	*unlock = true;
-	return true;
+	case MUTEX_TRYLOCK_SUCCESS:
+		*unlock = true;
+		return true;
+
+	case MUTEX_TRYLOCK_RECURSIVE:
+		*unlock = false;
+		return true;
+	}
+
+	BUG();
 }
 
 static unsigned long
drivers/gpu/drm/msm/msm_gem_shrinker.c +12 -4
index 6d2e885bd58e..62b8cc653823 100644
@@ -20,13 +20,21 @@
 
 static bool msm_gem_shrinker_lock(struct drm_device *dev, bool *unlock)
 {
-	if (!mutex_trylock(&dev->struct_mutex))
+	switch (mutex_trylock_recursive(&dev->struct_mutex)) {
+	case MUTEX_TRYLOCK_FAILED:
 		return false;
 
-	*unlock = true;
-	return true;
-}
+	case MUTEX_TRYLOCK_SUCCESS:
+		*unlock = true;
+		return true;
 
+	case MUTEX_TRYLOCK_RECURSIVE:
+		*unlock = false;
+		return true;
+	}
+
+	BUG();
+}
 
 static unsigned long
 msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
include/linux/mutex.h +31 -0
index 4d3bccabbea5..6a902f0a2148 100644
@@ -189,4 +189,35 @@ extern void mutex_unlock(struct mutex *lock);
 
 extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);
 
+/*
+ * These values are chosen such that FAIL and SUCCESS match the
+ * values of the regular mutex_trylock().
+ */
+enum mutex_trylock_recursive_enum {
+	MUTEX_TRYLOCK_FAILED    = 0,
+	MUTEX_TRYLOCK_SUCCESS   = 1,
+	MUTEX_TRYLOCK_RECURSIVE,
+};
+
+/**
+ * mutex_trylock_recursive - trylock variant that allows recursive locking
+ * @lock: mutex to be locked
+ *
+ * This function should not be used, _ever_. It is purely for hysterical GEM
+ * raisins, and once those are gone this will be removed.
+ *
+ * Returns:
+ *  MUTEX_TRYLOCK_FAILED    - trylock failed,
+ *  MUTEX_TRYLOCK_SUCCESS   - lock acquired,
+ *  MUTEX_TRYLOCK_RECURSIVE - we already owned the lock.
+ */
+static inline __deprecated __must_check enum mutex_trylock_recursive_enum
+mutex_trylock_recursive(struct mutex *lock)
+{
+	if (unlikely(__mutex_owner(lock) == current))
+		return MUTEX_TRYLOCK_RECURSIVE;
+
+	return mutex_trylock(lock);
+}
+
 #endif /* __LINUX_MUTEX_H */
scripts/checkpatch.pl +6 -0
index a8368d1c4348..23f462f64a3f 100755
@@ -6076,6 +6076,12 @@ sub process {
 			}
 		}
 
+# check for mutex_trylock_recursive usage
+		if ($line =~ /mutex_trylock_recursive/) {
+			ERROR("LOCKING",
+			      "recursive locking is bad, do not use this ever.\n" . $herecurr);
+		}
+
 # check for lockdep_set_novalidate_class
 		if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ ||
 		    $line =~ /__lockdep_no_validate__\s*\)/ ) {
author maintainer
#11 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

In reply to Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

Cc Daniel Vetter <[email protected]>
Cc Chris Wilson <[email protected]>
Cc Rob Clark <[email protected]>
---
 drivers/gpu/drm/i915/i915_gem_shrinker.c |   26 +++-----------------------
 drivers/gpu/drm/msm/msm_gem_shrinker.c   |   23 +++--------------------
 2 files changed, 6 insertions(+), 43 deletions(-)

OK, so it appears that i915 changed their locking around and got rid of this thing entirely. Much appreciated Chris!!

Rob, is there any chance you can do the same for msm?

Not having to provide a replacement function and opening the door to recursive locking would be ever so good.

author maintainer
#12 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 13 lines · Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

Cc Daniel Vetter <[email protected]>
Cc Chris Wilson <[email protected]>
Cc Rob Clark <[email protected]>
---
 drivers/gpu/drm/i915/i915_gem_shrinker.c |   26 +++-----------------------
 drivers/gpu/drm/msm/msm_gem_shrinker.c   |   23 +++--------------------
 2 files changed, 6 insertions(+), 43 deletions(-)
Quoted 2 lines

OK, so it appears that i915 changed their locking around and got rid of this thing entirely. Much appreciated Chris!!

Hmm, I might have spoken too soon. My patch conflicted and I seem to have read too much in the Changelog of 3b4e896f14b1 ("drm/i915: Remove unused no-shrinker-steal").

#13 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 14 lines · Peter Zijlstra
Quoted 13 lines · Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

Cc Daniel Vetter <[email protected]>
Cc Chris Wilson <[email protected]>
Cc Rob Clark <[email protected]>
---
 drivers/gpu/drm/i915/i915_gem_shrinker.c |   26 +++-----------------------
 drivers/gpu/drm/msm/msm_gem_shrinker.c   |   23 +++--------------------
 2 files changed, 6 insertions(+), 43 deletions(-)
Quoted 6 lines
Quoted 2 lines

OK, so it appears that i915 changed their locking around and got rid of this thing entirely. Much appreciated Chris!!

Hmm, I might have spoken too soon. My patch conflicted and I seem to have read too much in the Changelog of 3b4e896f14b1 ("drm/i915: Remove unused no-shrinker-steal").

Once all your locking rework is assembled it might be good to have a topic branch I could pull in. Both for testing and to handle conflicts before it goes boom in the merge window ;-) Not necessary ofc, but I think it'd be useful.
-Daniel

Signature · 3 lines
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
author maintainer
#14 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 4 lines · Daniel Vetter

Once all your locking rework is assembled it might be good to have a topic branch I could pull in. Both for testing and to handle conflicts before it goes boom in the merge window ;-) Not necessary ofc, but I think it'd be useful.

Everything except the trylock_recursive is already in tip/locking/core.

Ingo, is that all there is in that branch?

#15 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 10 lines · Peter Zijlstra
Quoted 4 lines · Daniel Vetter

Once all your locking rework is assembled it might be good to have a topic branch I could pull in. Both for testing and to handle conflicts before it goes boom in the merge window ;-) Not necessary ofc, but I think it'd be useful.

Everything except the trylock_recursive is already in tip/locking/core.

Ingo, is that all there is in that branch?

It has other bits as well - but I can create a separate topic branch for this, which would be c7faee2109f9 plus trylock_recursive().

Could you send a final version of trylock_recursive() patch for me to apply?

Thanks,

Ingo

author maintainer
#16 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 1 line · Ingo Molnar

Could you send a final version of trylock_recursive() patch for me to apply?

The latest lives here:

lkml.kernel.org/r/[email protected]

But I would really like someone to actually test that before you stick it in.

#17 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

Quoted 10 lines · Peter Zijlstra
Quoted 1 line · Ingo Molnar

Could you send a final version of trylock_recursive() patch for me to apply?

The latest lives here:

lkml.kernel.org/r/[email protected]

But I would really like someone to actually test that before you stick it in.

Agreed ...

Thanks,

Ingo

#18 lore raw

Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery

In reply to Peter Zijlstra
Quoted 13 lines · Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

Poking at lock internals is not cool. Since I'm going to change the implementation this will break, take it out.

Cc Daniel Vetter <[email protected]>
Cc Chris Wilson <[email protected]>
Cc Rob Clark <[email protected]>
---
 drivers/gpu/drm/i915/i915_gem_shrinker.c |   26 +++-----------------------
 drivers/gpu/drm/msm/msm_gem_shrinker.c   |   23 +++--------------------
 2 files changed, 6 insertions(+), 43 deletions(-)
Quoted 2 lines

OK, so it appears that i915 changed their locking around and got rid of this thing entirely. Much appreciated Chris!!

It's not dead yet! Sorry.

It's close though, in the next cycle we may be at a point where we don't rely on recursion locking in the shrinker.
-Chris

Signature · 1 line
Chris Wilson, Intel Open Source Technology Centre
author maintainer
#19 lore raw

PATCH 2/8 locking/mutex: Rework mutex::owner

In reply to Peter Zijlstra

The current mutex implementation has an atomic lock word and a non-atomic owner field.

This disparity leads to a number of issues with the current mutex code as it means that we can have a locked mutex without an explicit owner (because the owner field has not been set, or already cleared).

This leads to a number of weird corner cases, esp. between the optimistic spinning and debug code. Where the optimistic spinning code needs the owner field updated inside the lock region, the debug code is more relaxed because the whole lock is serialized by the wait_lock.

Also, the spinning code itself has a few corner cases where we need to deal with a held lock without an owner field.

Furthermore, it becomes even more of a problem when trying to fix starvation cases in the current code. We end up stacking special case on special case.

To solve this rework the basic mutex implementation to be a single atomic word that contains the owner and uses the low bits for extra state.

This matches how PI futexes and rt_mutex already work. By having the owner an integral part of the lock state a lot of the problems dissapear and we get a better option to deal with starvation cases, direct owner handoff.

Changing the basic mutex does however invalidate all the arch specific mutex code; this patch leaves that unused in-place, a later patch will remove that.

---
 include/linux/mutex-debug.h  |   24 --
 include/linux/mutex.h        |   46 +++--
 kernel/locking/mutex-debug.c |   13 -
 kernel/locking/mutex-debug.h |   10 -
 kernel/locking/mutex.c       |  371 ++++++++++++++++++-------------------------
 kernel/locking/mutex.h       |   26 ---
 kernel/sched/core.c          |    2 
 7 files changed, 187 insertions(+), 305 deletions(-)
include/linux/mutex-debug.h deleted +0 -24
@@ -1,24 +0,0 @@
-#ifndef __LINUX_MUTEX_DEBUG_H
-#define __LINUX_MUTEX_DEBUG_H
-
-#include <linux/linkage.h>
-#include <linux/lockdep.h>
-#include <linux/debug_locks.h>
-
-/*
- * Mutexes - debugging helpers:
- */
-
-#define __DEBUG_MUTEX_INITIALIZER(lockname)				\
-	, .magic = &lockname
-
-#define mutex_init(mutex)						\
-do {									\
-	static struct lock_class_key __key;				\
-									\
-	__mutex_init((mutex), #mutex, &__key);				\
-} while (0)
-
-extern void mutex_destroy(struct mutex *lock);
-
-#endif
include/linux/mutex.h +30 -16

95 lines not on this page · raw message

kernel/locking/mutex-debug.c +0 -13
@@ -73,21 +73,8 @@ void debug_mutex_unlock(struct mutex *lo
 {
 	if (likely(debug_locks)) {
 		DEBUG_LOCKS_WARN_ON(lock->magic != lock);
-
-		if (!lock->owner)
-			DEBUG_LOCKS_WARN_ON(!lock->owner);
-		else
-			DEBUG_LOCKS_WARN_ON(lock->owner != current);
-
 		DEBUG_LOCKS_WARN_ON(!lock->wait_list.prev && !lock->wait_list.next);
 	}
-
-	/*
-	 * __mutex_slowpath_needs_to_unlock() is explicitly 0 for debug
-	 * mutexes so that we can do it here after we've verified state.
-	 */
-	mutex_clear_owner(lock);
-	atomic_set(&lock->count, 1);
 }
 
 void debug_mutex_init(struct mutex *lock, const char *name,
kernel/locking/mutex-debug.h +0 -10
@@ -27,16 +27,6 @@ extern void debug_mutex_unlock(struct mu
 extern void debug_mutex_init(struct mutex *lock, const char *name,
 			     struct lock_class_key *key);
 
-static inline void mutex_set_owner(struct mutex *lock)
-{
-	WRITE_ONCE(lock->owner, current);
-}
-
-static inline void mutex_clear_owner(struct mutex *lock)
-{
-	WRITE_ONCE(lock->owner, NULL);
-}
-
 #define spin_lock_mutex(lock, flags)			\
 	do {						\
 		struct mutex *l = container_of(lock, struct mutex, wait_lock); \
kernel/locking/mutex.c +156 -215

601 lines not on this page · raw message

kernel/locking/mutex.h +0 -26
@@ -16,32 +16,6 @@
 #define mutex_remove_waiter(lock, waiter, task) \
 		__list_del((waiter)->list.prev, (waiter)->list.next)
 
-#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
-/*
- * The mutex owner can get read and written to locklessly.
- * We should use WRITE_ONCE when writing the owner value to
- * avoid store tearing, otherwise, a thread could potentially
- * read a partially written and incomplete owner value.
- */
-static inline void mutex_set_owner(struct mutex *lock)
-{
-	WRITE_ONCE(lock->owner, current);
-}
-
-static inline void mutex_clear_owner(struct mutex *lock)
-{
-	WRITE_ONCE(lock->owner, NULL);
-}
-#else
-static inline void mutex_set_owner(struct mutex *lock)
-{
-}
-
-static inline void mutex_clear_owner(struct mutex *lock)
-{
-}
-#endif
-
 #define debug_mutex_wake_waiter(lock, waiter)		do { } while (0)
 #define debug_mutex_free_waiter(waiter)			do { } while (0)
 #define debug_mutex_add_waiter(lock, waiter, ti)	do { } while (0)
kernel/sched/core.c +1 -1
@@ -75,11 +75,11 @@
 #include <linux/compiler.h>
 #include <linux/frame.h>
 #include <linux/prefetch.h>
+#include <linux/mutex.h>
 
 #include <asm/switch_to.h>
 #include <asm/tlb.h>
 #include <asm/irq_regs.h>
-#include <asm/mutex.h>
 #ifdef CONFIG_PARAVIRT
 #include <asm/paravirt.h>
 #endif
#20 lore raw

Re: [PATCH -v4 2/8] locking/mutex: Rework mutex::owner

kernel/locking/mutex.c line 111

Quoted 10 lines · Peter Zijlstra
kernel/locking/mutex.c +10 -0
+/*
+ * Optimistic trylock that only works in the uncontended case. Make sure to
+ * follow with a __mutex_trylock() before failing.
+ */
+static __always_inline bool __mutex_trylock_fast(struct mutex *lock)
+{
+	unsigned long curr = (unsigned long)current;
+
+	if (!atomic_long_cmpxchg_acquire(&lock->owner, 0UL, curr))
+		return true;

Do we want to do a ccas check for !lock->owner? Although I can see a possible case of 'optimizing for the contended' reasons for nay.

Thanks,
Davidlohr

#21 lore raw

Re: [PATCH -v4 2/8] locking/mutex: Rework mutex::owner

Quoted 14 lines · Davidlohr Bueso

kernel/locking/mutex.c line 111

Quoted 10 lines · Peter Zijlstra
kernel/locking/mutex.c +10 -0
+/*
+ * Optimistic trylock that only works in the uncontended case. Make sure to
+ * follow with a __mutex_trylock() before failing.
+ */
+static __always_inline bool __mutex_trylock_fast(struct mutex *lock)
+{
+	unsigned long curr = (unsigned long)current;
+
+	if (!atomic_long_cmpxchg_acquire(&lock->owner, 0UL, curr))
+		return true;

Do we want to do a ccas check for !lock->owner? Although I can see a possible case of 'optimizing for the contended' reasons for nay.

Since this is the fast path version that gets used in mutex_lock(), ect..., I think it would make sense to keep it like it is so that we optimize it for the "common" case. This trylock function is more likely to succeed as it is used for the initial attempt to get the mutex, so I think we could avoid the ccas in the trylock_fast().

Jason

Reviewed-by on v1 2/8
#22 lore raw

Re: [PATCH -v4 2/8] locking/mutex: Rework mutex::owner

In reply to Peter Zijlstra
Quoted 35 lines · Peter Zijlstra

The current mutex implementation has an atomic lock word and a non-atomic owner field.

This disparity leads to a number of issues with the current mutex code as it means that we can have a locked mutex without an explicit owner (because the owner field has not been set, or already cleared).

This leads to a number of weird corner cases, esp. between the optimistic spinning and debug code. Where the optimistic spinning code needs the owner field updated inside the lock region, the debug code is more relaxed because the whole lock is serialized by the wait_lock.

Also, the spinning code itself has a few corner cases where we need to deal with a held lock without an owner field.

Furthermore, it becomes even more of a problem when trying to fix starvation cases in the current code. We end up stacking special case on special case.

To solve this rework the basic mutex implementation to be a single atomic word that contains the owner and uses the low bits for extra state.

This matches how PI futexes and rt_mutex already work. By having the owner an integral part of the lock state a lot of the problems dissapear and we get a better option to deal with starvation cases, direct owner handoff.

Changing the basic mutex does however invalidate all the arch specific mutex code; this patch leaves that unused in-place, a later patch will remove that.

Quoted 9 lines
---
 include/linux/mutex-debug.h  |   24 --
 include/linux/mutex.h        |   46 +++--
 kernel/locking/mutex-debug.c |   13 -
 kernel/locking/mutex-debug.h |   10 -
 kernel/locking/mutex.c       |  371 ++++++++++++++++++-------------------------
 kernel/locking/mutex.h       |   26 ---
 kernel/sched/core.c          |    2 
 7 files changed, 187 insertions(+), 305 deletions(-)

Looks good to me:

Will

author maintainer
#23 lore raw

PATCH 3/8 locking/mutex: Kill arch specific code

In reply to Peter Zijlstra

Its all generic atomic_long_t stuff now.

---
 arch/alpha/include/asm/mutex.h      |    9 --
 arch/arc/include/asm/mutex.h        |   18 ----
 arch/arm/include/asm/mutex.h        |   21 -----
 arch/arm64/include/asm/Kbuild       |    1 
 arch/avr32/include/asm/mutex.h      |    9 --
 arch/blackfin/include/asm/Kbuild    |    1 
 arch/c6x/include/asm/mutex.h        |    6 -
 arch/cris/include/asm/mutex.h       |    9 --
 arch/frv/include/asm/mutex.h        |    9 --
 arch/h8300/include/asm/mutex.h      |    9 --
 arch/hexagon/include/asm/mutex.h    |    8 --
 arch/ia64/include/asm/mutex.h       |   90 ------------------------
 arch/m32r/include/asm/mutex.h       |    9 --
 arch/m68k/include/asm/Kbuild        |    1 
 arch/metag/include/asm/Kbuild       |    1 
 arch/microblaze/include/asm/mutex.h |    1 
 arch/mips/include/asm/Kbuild        |    1 
 arch/mn10300/include/asm/mutex.h    |   16 ----
 arch/nios2/include/asm/mutex.h      |    1 
 arch/openrisc/include/asm/mutex.h   |   27 -------
 arch/parisc/include/asm/Kbuild      |    1 
 arch/powerpc/include/asm/mutex.h    |  132 ------------------------------------
 arch/s390/include/asm/mutex.h       |    9 --
 arch/score/include/asm/mutex.h      |    6 -
 arch/sh/include/asm/mutex-llsc.h    |  109 -----------------------------
 arch/sh/include/asm/mutex.h         |   12 ---
 arch/sparc/include/asm/Kbuild       |    1 
 arch/tile/include/asm/Kbuild        |    1 
 arch/um/include/asm/Kbuild          |    1 
 arch/unicore32/include/asm/mutex.h  |   20 -----
 arch/x86/include/asm/mutex.h        |    5 -
 arch/x86/include/asm/mutex_32.h     |  110 ------------------------------
 arch/x86/include/asm/mutex_64.h     |  127 ----------------------------------
 arch/xtensa/include/asm/mutex.h     |    9 --
 include/asm-generic/mutex-dec.h     |   88 ------------------------
 include/asm-generic/mutex-null.h    |   19 -----
 include/asm-generic/mutex-xchg.h    |  120 --------------------------------
 include/asm-generic/mutex.h         |    9 --
 38 files changed, 1026 deletions(-)
arch/alpha/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/arc/include/asm/mutex.h deleted +0 -18
@@ -1,18 +0,0 @@
-/*
- * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
-/*
- * xchg() based mutex fast path maintains a state of 0 or 1, as opposed to
- * atomic dec based which can "count" any number of lock contenders.
- * This ideally needs to be fixed in core, but for now switching to dec ver.
- */
-#if defined(CONFIG_SMP) && (CONFIG_NR_CPUS > 2)
-#include <asm-generic/mutex-dec.h>
-#else
-#include <asm-generic/mutex-xchg.h>
-#endif
arch/arm/include/asm/mutex.h deleted +0 -21
@@ -1,21 +0,0 @@
-/*
- * arch/arm/include/asm/mutex.h
- *
- * ARM optimized mutex locking primitives
- *
- * Please look into asm-generic/mutex-xchg.h for a formal definition.
- */
-#ifndef _ASM_MUTEX_H
-#define _ASM_MUTEX_H
-/*
- * On pre-ARMv6 hardware this results in a swp-based implementation,
- * which is the most efficient. For ARMv6+, we have exclusive memory
- * accessors and use atomic_dec to avoid the extra xchg operations
- * on the locking slowpaths.
- */
-#if __LINUX_ARM_ARCH__ < 6
-#include <asm-generic/mutex-xchg.h>
-#else
-#include <asm-generic/mutex-dec.h>
-#endif
-#endif	/* _ASM_MUTEX_H */
arch/arm64/include/asm/Kbuild +0 -1
@@ -26,7 +26,6 @@ generic-y += mm-arch-hooks.h
 generic-y += mman.h
 generic-y += msgbuf.h
 generic-y += msi.h
-generic-y += mutex.h
 generic-y += pci.h
 generic-y += poll.h
 generic-y += preempt.h
arch/avr32/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/blackfin/include/asm/Kbuild +0 -1
@@ -24,7 +24,6 @@ generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
 generic-y += mman.h
 generic-y += msgbuf.h
-generic-y += mutex.h
 generic-y += param.h
 generic-y += percpu.h
 generic-y += pgalloc.h
arch/c6x/include/asm/mutex.h deleted +0 -6
@@ -1,6 +0,0 @@
-#ifndef _ASM_C6X_MUTEX_H
-#define _ASM_C6X_MUTEX_H
-
-#include <asm-generic/mutex-null.h>
-
-#endif /* _ASM_C6X_MUTEX_H */
arch/cris/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/frv/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/h8300/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/hexagon/include/asm/mutex.h deleted +0 -8
@@ -1,8 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-#include <asm-generic/mutex-xchg.h>
arch/ia64/include/asm/mutex.h deleted +0 -90

90 lines not on this page · raw message

arch/m32r/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/m68k/include/asm/Kbuild +0 -1
@@ -20,7 +20,6 @@ generic-y += local64.h
 generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
 generic-y += mman.h
-generic-y += mutex.h
 generic-y += percpu.h
 generic-y += preempt.h
 generic-y += resource.h
arch/metag/include/asm/Kbuild +0 -1
@@ -27,7 +27,6 @@ generic-y += local64.h
 generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
 generic-y += msgbuf.h
-generic-y += mutex.h
 generic-y += param.h
 generic-y += pci.h
 generic-y += percpu.h
arch/microblaze/include/asm/mutex.h deleted +0 -1
@@ -1 +0,0 @@
-#include <asm-generic/mutex-dec.h>
arch/mips/include/asm/Kbuild +0 -1
@@ -9,7 +9,6 @@ generic-y += irq_work.h
 generic-y += local64.h
 generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
-generic-y += mutex.h
 generic-y += parport.h
 generic-y += percpu.h
 generic-y += preempt.h
arch/mn10300/include/asm/mutex.h deleted +0 -16
@@ -1,16 +0,0 @@
-/* MN10300 Mutex fastpath
- *
- * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
- * Written by David Howells ([email protected])
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public Licence
- * as published by the Free Software Foundation; either version
- * 2 of the Licence, or (at your option) any later version.
- *
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-#include <asm-generic/mutex-null.h>
arch/nios2/include/asm/mutex.h deleted +0 -1
@@ -1 +0,0 @@
-#include <asm-generic/mutex-dec.h>
arch/openrisc/include/asm/mutex.h deleted +0 -27
@@ -1,27 +0,0 @@
-/*
- * OpenRISC Linux
- *
- * Linux architectural port borrowing liberally from similar works of
- * others.  All original copyrights apply as per the original source
- * declaration.
- *
- * OpenRISC implementation:
- * Copyright (C) 2003 Matjaz Breskvar <[email protected]>
- * Copyright (C) 2010-2011 Jonas Bonn <[email protected]>
- * et al.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/parisc/include/asm/Kbuild +0 -1
@@ -16,7 +16,6 @@ generic-y += local.h
 generic-y += local64.h
 generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
-generic-y += mutex.h
 generic-y += param.h
 generic-y += percpu.h
 generic-y += poll.h
arch/powerpc/include/asm/mutex.h deleted +0 -132

132 lines not on this page · raw message

arch/s390/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
arch/score/include/asm/mutex.h deleted +0 -6
@@ -1,6 +0,0 @@
-#ifndef _ASM_SCORE_MUTEX_H
-#define _ASM_SCORE_MUTEX_H
-
-#include <asm-generic/mutex-dec.h>
-
-#endif /* _ASM_SCORE_MUTEX_H */
arch/sh/include/asm/mutex-llsc.h deleted +0 -109

109 lines not on this page · raw message

arch/sh/include/asm/mutex.h deleted +0 -12
@@ -1,12 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-#if defined(CONFIG_CPU_SH4A)
-#include <asm/mutex-llsc.h>
-#else
-#include <asm-generic/mutex-dec.h>
-#endif
arch/sparc/include/asm/Kbuild +0 -1
@@ -14,7 +14,6 @@ generic-y += local64.h
 generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
 generic-y += module.h
-generic-y += mutex.h
 generic-y += preempt.h
 generic-y += rwsem.h
 generic-y += serial.h
arch/tile/include/asm/Kbuild +0 -1
@@ -21,7 +21,6 @@ generic-y += local64.h
 generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
 generic-y += msgbuf.h
-generic-y += mutex.h
 generic-y += param.h
 generic-y += parport.h
 generic-y += poll.h
arch/um/include/asm/Kbuild +0 -1
@@ -17,7 +17,6 @@ generic-y += irq_work.h
 generic-y += kdebug.h
 generic-y += mcs_spinlock.h
 generic-y += mm-arch-hooks.h
-generic-y += mutex.h
 generic-y += param.h
 generic-y += pci.h
 generic-y += percpu.h
arch/unicore32/include/asm/mutex.h deleted +0 -20
@@ -1,20 +0,0 @@
-/*
- * linux/arch/unicore32/include/asm/mutex.h
- *
- * Code specific to PKUnity SoC and UniCore ISA
- *
- * Copyright (C) 2001-2010 GUAN Xue-tao
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * UniCore optimized mutex locking primitives
- *
- * Please look into asm-generic/mutex-xchg.h for a formal definition.
- */
-#ifndef __UNICORE_MUTEX_H__
-#define __UNICORE_MUTEX_H__
-
-# include <asm-generic/mutex-xchg.h>
-#endif
arch/x86/include/asm/mutex.h deleted +0 -5
@@ -1,5 +0,0 @@
-#ifdef CONFIG_X86_32
-# include <asm/mutex_32.h>
-#else
-# include <asm/mutex_64.h>
-#endif
arch/x86/include/asm/mutex_32.h deleted +0 -110

110 lines not on this page · raw message

arch/x86/include/asm/mutex_64.h deleted +0 -127

127 lines not on this page · raw message

arch/xtensa/include/asm/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-/*
- * Pull in the generic implementation for the mutex fastpath.
- *
- * TODO: implement optimized primitives instead, or leave the generic
- * implementation in place, or pick the atomic_xchg() based generic
- * implementation. (see asm-generic/mutex-xchg.h for details)
- */
-
-#include <asm-generic/mutex-dec.h>
include/asm-generic/mutex-dec.h deleted +0 -88

88 lines not on this page · raw message

include/asm-generic/mutex-null.h deleted +0 -19
@@ -1,19 +0,0 @@
-/*
- * include/asm-generic/mutex-null.h
- *
- * Generic implementation of the mutex fastpath, based on NOP :-)
- *
- * This is used by the mutex-debugging infrastructure, but it can also
- * be used by architectures that (for whatever reason) want to use the
- * spinlock based slowpath.
- */
-#ifndef _ASM_GENERIC_MUTEX_NULL_H
-#define _ASM_GENERIC_MUTEX_NULL_H
-
-#define __mutex_fastpath_lock(count, fail_fn)		fail_fn(count)
-#define __mutex_fastpath_lock_retval(count)		(-1)
-#define __mutex_fastpath_unlock(count, fail_fn)		fail_fn(count)
-#define __mutex_fastpath_trylock(count, fail_fn)	fail_fn(count)
-#define __mutex_slowpath_needs_to_unlock()		1
-
-#endif
include/asm-generic/mutex-xchg.h deleted +0 -120

120 lines not on this page · raw message

include/asm-generic/mutex.h deleted +0 -9
@@ -1,9 +0,0 @@
-#ifndef __ASM_GENERIC_MUTEX_H
-#define __ASM_GENERIC_MUTEX_H
-/*
- * Pull in the generic implementation for the mutex fastpath,
- * which is a reasonable default on many architectures.
- */
-
-#include <asm-generic/mutex-dec.h>
-#endif /* __ASM_GENERIC_MUTEX_H */
author maintainer
#24 lore raw

PATCH 4/8 locking/mutex: Allow MUTEX_SPIN_ON_OWNER when DEBUG_MUTEXES

In reply to Peter Zijlstra

Now that mutex::count and mutex::owner are the same field, we can allow SPIN_ON_OWNER while DEBUG_MUTEX.

---
 kernel/Kconfig.locks |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/Kconfig.locks +1 -1
@@ -225,7 +225,7 @@ config ARCH_SUPPORTS_ATOMIC_RMW
 
 config MUTEX_SPIN_ON_OWNER
 	def_bool y
-	depends on SMP && !DEBUG_MUTEXES && ARCH_SUPPORTS_ATOMIC_RMW
+	depends on SMP && ARCH_SUPPORTS_ATOMIC_RMW
 
 config RWSEM_SPIN_ON_OWNER
        def_bool y
author maintainer
#25 lore raw

PATCH 5/8 locking/mutex: Add lock handoff to avoid starvation

In reply to Peter Zijlstra

Implement lock handoff to avoid lock starvation.

Lock starvation is possible because mutex_lock() allows lock stealing, where a running (or optimistic spinning) task beats the woken waiter to the acquire.

Lock stealing is an important performance optimization because waiting for a waiter to wake up and get runtime can take a significant time, during which everyboy would stall on the lock.

The down-side is of course that it allows for starvation.

This patch has the waiter requesting a handoff if it fails to acquire the lock upon waking. This re-introduces some of the wait time, because once we do a handoff we have to wait for the waiter to wake up again.

A future patch will add a round of optimistic spinning to attempt to alleviate this penalty, but if that turns out to not be enough, we can add a counter and only request handoff after multiple failed wakeups.

There are a few tricky implementation details:

- accepting a handoff must only be done in the wait-loop. Since the handoff condition is owner == current, it can easily cause recursive locking trouble.

- accepting the handoff must be careful to provide the ACQUIRE semantics.

- having the HANDOFF bit set on unlock requires care, we must not clear the owner.

- we must be careful to not leave HANDOFF set after we've acquired the lock. The tricky scenario is setting the HANDOFF bit on an unlocked mutex.

---
 kernel/locking/mutex.c |  142 +++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 119 insertions(+), 23 deletions(-)
kernel/locking/mutex.c +119 -23
@@ -54,8 +54,10 @@ EXPORT_SYMBOL(__mutex_init);
  * bits to store extra state.
  *
  * Bit0 indicates a non-empty waiter list; unlock must issue a wakeup.
+ * Bit1 indicates unlock needs to hand the lock to the top-waiter
  */
 #define MUTEX_FLAG_WAITERS	0x01
+#define MUTEX_FLAG_HANDOFF	0x02
 
 #define MUTEX_FLAGS		0x03
 
@@ -71,20 +73,48 @@ static inline unsigned long __owner_flag
 
 /*
  * Actual trylock that will work on any unlocked state.
+ *
+ * When setting the owner field, we must preserve the low flag bits.
+ *
+ * Be careful with @handoff, only set that in a wait-loop (where you set
+ * HANDOFF) to avoid recursive lock attempts.
  */
-static inline bool __mutex_trylock(struct mutex *lock)
+static inline bool __mutex_trylock(struct mutex *lock, const bool handoff)
 {
 	unsigned long owner, curr = (unsigned long)current;
 
 	owner = atomic_long_read(&lock->owner);
 	for (;;) { /* must loop, can race against a flag */
-		unsigned long old;
+		unsigned long old, flags = __owner_flags(owner);
+
+		if (__owner_task(owner)) {
+			if (handoff && unlikely(__owner_task(owner) == current)) {
+				/*
+				 * Provide ACQUIRE semantics for the lock-handoff.
+				 *
+				 * We cannot easily use load-acquire here, since
+				 * the actual load is a failed cmpxchg, which
+				 * doesn't imply any barriers.
+				 *
+				 * Also, this is a fairly unlikely scenario, and
+				 * this contains the cost.
+				 */
+				smp_mb(); /* ACQUIRE */
+				return true;
+			}
 
-		if (__owner_task(owner))
 			return false;
+		}
 
-		old = atomic_long_cmpxchg_acquire(&lock->owner, owner,
-						  curr | __owner_flags(owner));
+		/*
+		 * We set the HANDOFF bit, we must make sure it doesn't live
+		 * past the point where we acquire it. This would be possible
+		 * if we (accidentally) set the bit on an unlocked mutex.
+		 */
+		if (handoff)
+			flags &= ~MUTEX_FLAG_HANDOFF;
+
+		old = atomic_long_cmpxchg_acquire(&lock->owner, owner, curr | flags);
 		if (old == owner)
 			return true;
 
@@ -134,6 +164,39 @@ static inline void __mutex_clear_flag(st
 	atomic_long_andnot(flag, &lock->owner);
 }
 
+static inline bool __mutex_waiter_is_first(struct mutex *lock, struct mutex_waiter *waiter)
+{
+	return list_first_entry(&lock->wait_list, struct mutex_waiter, list) == waiter;
+}
+
+/*
+ * Give up ownership to a specific task, when @task = NULL, this is equivalent
+ * to a regular unlock. Clears HANDOFF, preserves WAITERS. Provides RELEASE
+ * semantics like a regular unlock, the __mutex_trylock() provides matching
+ * ACQUIRE semantics for the handoff.
+ */
+static void __mutex_handoff(struct mutex *lock, struct task_struct *task)
+{
+	unsigned long owner = atomic_long_read(&lock->owner);
+
+	for (;;) {
+		unsigned long old, new;
+
+#ifdef CONFIG_DEBUG_MUTEXES
+		DEBUG_LOCKS_WARN_ON(__owner_task(owner) != current);
+#endif
+
+		new = (owner & MUTEX_FLAG_WAITERS);
+		new |= (unsigned long)task;
+
+		old = atomic_long_cmpxchg_release(&lock->owner, owner, new);
+		if (old == owner)
+			break;
+
+		owner = old;
+	}
+}
+
 #ifndef CONFIG_DEBUG_LOCK_ALLOC
 /*
  * We split the mutex lock/unlock logic into separate fastpath and
@@ -398,7 +461,7 @@ static bool mutex_optimistic_spin(struct
 			break;
 
 		/* Try to acquire the mutex if it is unlocked. */
-		if (__mutex_trylock(lock)) {
+		if (__mutex_trylock(lock, false)) {
 			osq_unlock(&lock->osq);
 			return true;
 		}
@@ -523,6 +586,7 @@ __mutex_lock_common(struct mutex *lock,
 	struct task_struct *task = current;
 	struct mutex_waiter waiter;
 	unsigned long flags;
+	bool first = false;
 	int ret;
 
 	if (use_ww_ctx) {
@@ -534,7 +598,8 @@ __mutex_lock_common(struct mutex *lock,
 	preempt_disable();
 	mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
 
-	if (__mutex_trylock(lock) || mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
+	if (__mutex_trylock(lock, false) ||
+	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
 		/* got the lock, yay! */
 		lock_acquired(&lock->dep_map, ip);
 		if (use_ww_ctx) {
@@ -551,7 +616,7 @@ __mutex_lock_common(struct mutex *lock,
 	/*
 	 * After waiting to acquire the wait_lock, try again.
 	 */
-	if (__mutex_trylock(lock))
+	if (__mutex_trylock(lock, false))
 		goto skip_wait;
 
 	debug_mutex_lock_common(lock, &waiter);
@@ -561,13 +626,13 @@ __mutex_lock_common(struct mutex *lock,
 	list_add_tail(&waiter.list, &lock->wait_list);
 	waiter.task = task;
 
-	if (list_first_entry(&lock->wait_list, struct mutex_waiter, list) == &waiter)
+	if (__mutex_waiter_is_first(lock, &waiter))
 		__mutex_set_flag(lock, MUTEX_FLAG_WAITERS);
 
 	lock_contended(&lock->dep_map, ip);
 
 	for (;;) {
-		if (__mutex_trylock(lock))
+		if (__mutex_trylock(lock, first))
 			break;
 
 		/*
@@ -586,17 +651,20 @@ __mutex_lock_common(struct mutex *lock,
 		}
 
 		__set_task_state(task, state);
-
-		/* didn't get the lock, go to sleep: */
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
 		spin_lock_mutex(&lock->wait_lock, flags);
+
+		if (!first && __mutex_waiter_is_first(lock, &waiter)) {
+			first = true;
+			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
+		}
 	}
 	__set_task_state(task, TASK_RUNNING);
 
 	mutex_remove_waiter(lock, &waiter, task);
 	if (likely(list_empty(&lock->wait_list)))
-		__mutex_clear_flag(lock, MUTEX_FLAG_WAITERS);
+		__mutex_clear_flag(lock, MUTEX_FLAGS);
 
 	debug_mutex_free_waiter(&waiter);
 
@@ -724,33 +792,61 @@ EXPORT_SYMBOL_GPL(__ww_mutex_lock_interr
  */
 static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip)
 {
+	struct task_struct *next = NULL;
 	unsigned long owner, flags;
 	WAKE_Q(wake_q);
 
 	mutex_release(&lock->dep_map, 1, ip);
 
 	/*
-	 * Release the lock before (potentially) taking the spinlock
-	 * such that other contenders can get on with things ASAP.
+	 * Release the lock before (potentially) taking the spinlock such that
+	 * other contenders can get on with things ASAP.
+	 *
+	 * Except when HANDOFF, in that case we must not clear the owner field,
+	 * but instead set it to the top waiter.
 	 */
-	owner = atomic_long_fetch_and_release(MUTEX_FLAGS, &lock->owner);
-	if (!__owner_flags(owner))
-		return;
+	owner = atomic_long_read(&lock->owner);
+	for (;;) {
+		unsigned long old;
+
+#ifdef CONFIG_DEBUG_MUTEXES
+		DEBUG_LOCKS_WARN_ON(__owner_task(owner) != current);
+#endif
+
+		if (owner & MUTEX_FLAG_HANDOFF)
+			break;
+
+		old = atomic_long_cmpxchg_release(&lock->owner, owner,
+						  __owner_flags(owner));
+		if (old == owner) {
+			if (owner & MUTEX_FLAG_WAITERS)
+				break;
+
+			return;
+		}
+
+		owner = old;
+	}
 
 	spin_lock_mutex(&lock->wait_lock, flags);
 	debug_mutex_unlock(lock);
-
 	if (!list_empty(&lock->wait_list)) {
 		/* get the first entry from the wait-list: */
 		struct mutex_waiter *waiter =
-				list_entry(lock->wait_list.next,
-					   struct mutex_waiter, list);
+			list_first_entry(&lock->wait_list,
+					 struct mutex_waiter, list);
+
+		next = waiter->task;
 
 		debug_mutex_wake_waiter(lock, waiter);
-		wake_q_add(&wake_q, waiter->task);
+		wake_q_add(&wake_q, next);
 	}
 
+	if (owner & MUTEX_FLAG_HANDOFF)
+		__mutex_handoff(lock, next);
+
 	spin_unlock_mutex(&lock->wait_lock, flags);
+
 	wake_up_q(&wake_q);
 }
 
@@ -853,7 +949,7 @@ __ww_mutex_lock_interruptible_slowpath(s
  */
 int __sched mutex_trylock(struct mutex *lock)
 {
-	bool locked = __mutex_trylock(lock);
+	bool locked = __mutex_trylock(lock, false);
 
 	if (locked)
 		mutex_acquire(&lock->dep_map, 0, 1, _RET_IP_);
#26 lore raw

Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation

Hi Peter,

Just one comment below.

Quoted 36 lines · Peter Zijlstra
kernel/locking/mutex.c +13 -2
@@ -54,8 +54,10 @@ EXPORT_SYMBOL(__mutex_init);
  * bits to store extra state.
  *
  * Bit0 indicates a non-empty waiter list; unlock must issue a wakeup.
+ * Bit1 indicates unlock needs to hand the lock to the top-waiter
  */
 #define MUTEX_FLAG_WAITERS	0x01
+#define MUTEX_FLAG_HANDOFF	0x02
 
 #define MUTEX_FLAGS		0x03
 
@@ -71,20 +73,48 @@ static inline unsigned long __owner_flag
 
 /*
  * Actual trylock that will work on any unlocked state.
+ *
+ * When setting the owner field, we must preserve the low flag bits.
+ *
+ * Be careful with @handoff, only set that in a wait-loop (where you set
+ * HANDOFF) to avoid recursive lock attempts.
  */
-static inline bool __mutex_trylock(struct mutex *lock)
+static inline bool __mutex_trylock(struct mutex *lock, const bool handoff)
 {
 	unsigned long owner, curr = (unsigned long)current;
 
 	owner = atomic_long_read(&lock->owner);
 	for (;;) { /* must loop, can race against a flag */
-		unsigned long old;
+		unsigned long old, flags = __owner_flags(owner);
+
+		if (__owner_task(owner)) {
+			if (handoff && unlikely(__owner_task(owner) == current)) {
+				/*

kernel/locking/mutex.c static inline unsigned long __owner_flag · line 102

Quoted 10 lines
kernel/locking/mutex.c +10 -0
+				 * Provide ACQUIRE semantics for the lock-handoff.
+				 *
+				 * We cannot easily use load-acquire here, since
+				 * the actual load is a failed cmpxchg, which
+				 * doesn't imply any barriers.
+				 *
+				 * Also, this is a fairly unlikely scenario, and
+				 * this contains the cost.
+				 */
+				smp_mb(); /* ACQUIRE */

As we discussed on another thread recently, a failed cmpxchg_acquire will always give you ACQUIRE semantics in practice. Maybe we should update the documentation to allow this? The only special case is the full-barrier version.

Will

author maintainer
#27 lore raw

Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation

Quoted 10 lines · Will Deacon
Quoted 10 lines
kernel/locking/mutex.c +10 -0
+		if (__owner_task(owner)) {
+			if (handoff && unlikely(__owner_task(owner) == current)) {
+				/*
+				 * Provide ACQUIRE semantics for the lock-handoff.
+				 *
+				 * We cannot easily use load-acquire here, since
+				 * the actual load is a failed cmpxchg, which
+				 * doesn't imply any barriers.
+				 *
+				 * Also, this is a fairly unlikely scenario, and
Quoted 8 lines

kernel/locking/mutex.c static inline unsigned long __owner_flag · line 102

Quoted 3 lines
kernel/locking/mutex.c +3 -0
+				 * this contains the cost.
+				 */
+				smp_mb(); /* ACQUIRE */

As we discussed on another thread recently, a failed cmpxchg_acquire will always give you ACQUIRE semantics in practice. Maybe we should update the documentation to allow this? The only special case is the full-barrier version.

So on PPC we do:

static __always_inline unsigned long __cmpxchg_u32_acquire(u32 *p, unsigned long old, unsigned long new)

{
        unsigned long prev;

        __asm__ __volatile__ (
"1:     lwarx   %0,0,%2         # __cmpxchg_u32_acquire\n"
"       cmpw    0,%0,%3\n"
"       bne-    2f\n"
        PPC405_ERR77(0, %2)
"       stwcx.  %4,0,%2\n"
"       bne-    1b\n"
        PPC_ACQUIRE_BARRIER
        "\n"
"2:"
        : "=&r" (prev), "+m" (*p)
        : "r" (p), "r" (old), "r" (new)
        : "cc", "memory");

        return prev;
}

which I read to skip over the ACQUIRE_BARRIER on fail.

Similarly, we _could_ make the generic version skip the barrier entirely (we currently do not it seems).

And while I agree that it makes semantic sense, in that we always issue the LOAD, and since we defined the ACQUIRE to apply to the LOADs only, and we always issue the LOAD, we should also always provide ACQUIRE semantics. I'm not entirely convinced we should go there just yet. It would make failed cmpxchg_acquire()'s more expensive, and this really is the only place we care about those.

So I would propose for now we keep these explicit barriers; both here and the other place you mentioned, but keep this in mind.

Also, I don't feel we need more complexity in this patch set just now.

Reviewed-by on v1 5/8
#28 lore raw

Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation

In reply to Peter Zijlstra
Quoted 20 lines · Peter Zijlstra

/*
* Actual trylock that will work on any unlocked state.
+ *
+ * When setting the owner field, we must preserve the low flag bits.
+ *
+ * Be careful with @handoff, only set that in a wait-loop (where you set
+ * HANDOFF) to avoid recursive lock attempts.
*/

kernel/locking/mutex.c +5 -2
-static inline bool __mutex_trylock(struct mutex *lock)
+static inline bool __mutex_trylock(struct mutex *lock, const bool handoff)
  {
  	unsigned long owner, curr = (unsigned long)current;
 
  	owner = atomic_long_read(&lock->owner);
  	for (;;) { /* must loop, can race against a flag */
-		unsigned long old;
+		unsigned long old, flags = __owner_flags(owner);
+
+		if (__owner_task(owner)) {
+			if (handoff&&  unlikely(__owner_task(owner) == current)) {

kernel/locking/mutex.c static inline unsigned long __owner_flag · line 101

Quoted 10 lines
kernel/locking/mutex.c +10 -0
+				/*
+				 * Provide ACQUIRE semantics for the lock-handoff.
+				 *
+				 * We cannot easily use load-acquire here, since
+				 * the actual load is a failed cmpxchg, which
+				 * doesn't imply any barriers.
+				 *
+				 * Also, this is a fairly unlikely scenario, and
+				 * this contains the cost.
+				 */

I am not so sure about your comment here. I guess you are referring to the atomic_long_cmpxchg_acquire below for the failed cmpxchg. However, it is also possible that the path can be triggered on the first round without cmpxchg. Maybe we can do a load_acquire on the owner again to satisfy this requirement without a smp_mb().

Quoted 13 lines
kernel/locking/mutex.c +5 -3
+				smp_mb(); /* ACQUIRE */
+				return true;
+			}
 
-		if (__owner_task(owner))
  			return false;
+		}
 
-		old = atomic_long_cmpxchg_acquire(&lock->owner, owner,
-						  curr | __owner_flags(owner));
+		/*

kernel/locking/mutex.c static inline unsigned long __owner_flag · line 119

Quoted 10 lines
kernel/locking/mutex.c +8 -0
+		 * We set the HANDOFF bit, we must make sure it doesn't live
+		 * past the point where we acquire it. This would be possible
+		 * if we (accidentally) set the bit on an unlocked mutex.
+		 */
+		if (handoff)
+			flags&= ~MUTEX_FLAG_HANDOFF;
+
+		old = atomic_long_cmpxchg_acquire(&lock->owner, owner, curr | flags);
  		if (old == owner)
  			return true;

Other than that, the code is fine.

#29 lore raw

Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation

Quoted 61 lines · Waiman Long
Quoted 22 lines · Peter Zijlstra

/*
* Actual trylock that will work on any unlocked state.
+ *
+ * When setting the owner field, we must preserve the low flag bits.
+ *
+ * Be careful with @handoff, only set that in a wait-loop (where you set
+ * HANDOFF) to avoid recursive lock attempts.
*/

(quoted diff) +1 -1
-static inline bool __mutex_trylock(struct mutex *lock)
+static inline bool __mutex_trylock(struct mutex *lock, const bool 

handoff)

  {
      unsigned long owner, curr = (unsigned long)current;

      owner = atomic_long_read(&lock->owner);
      for (;;) { /* must loop, can race against a flag */
kernel/locking/mutex.c +4 -1
-        unsigned long old;
+        unsigned long old, flags = __owner_flags(owner);
+
+        if (__owner_task(owner)) {
+            if (handoff&&  unlikely(__owner_task(owner) == current)) {

kernel/locking/mutex.c static inline unsigned long __owner_flag · line 101

Quoted 10 lines
kernel/locking/mutex.c +10 -0
+                /*
+                 * Provide ACQUIRE semantics for the lock-handoff.
+                 *
+                 * We cannot easily use load-acquire here, since
+                 * the actual load is a failed cmpxchg, which
+                 * doesn't imply any barriers.
+                 *
+                 * Also, this is a fairly unlikely scenario, and
+                 * this contains the cost.
+                 */

I am not so sure about your comment here. I guess you are referring to the atomic_long_cmpxchg_acquire below for the failed cmpxchg. However, it is also possible that the path can be triggered on the first round without cmpxchg. Maybe we can do a load_acquire on the owner again to satisfy this requirement without a smp_mb().

Quoted 19 lines
kernel/locking/mutex.c +13 -3
+                smp_mb(); /* ACQUIRE */
+                return true;
+            }
 
-        if (__owner_task(owner))
              return false;
+        }
 
-        old = atomic_long_cmpxchg_acquire(&lock->owner, owner,
-                          curr | __owner_flags(owner));
+        /*
+         * We set the HANDOFF bit, we must make sure it doesn't live
+         * past the point where we acquire it. This would be possible
+         * if we (accidentally) set the bit on an unlocked mutex.
+         */
+        if (handoff)
+            flags&= ~MUTEX_FLAG_HANDOFF;
+
+        old = atomic_long_cmpxchg_acquire(&lock->owner, owner, curr 
Quoted 5 lines
| flags);
          if (old == owner)
              return true;
Quoted 4 lines

Other than that, the code is fine.

One more thing, I think it may be worthwhile to add another comment about what happens when the HANDOFF bit was set while we take the error path (goto err). As the actual handoff is serialized by the wait_lock, the code will still do the right thing. Either the next one in the queue will be handed off or it will be unlocked if the queue is empty.

Cheers,
Longman

author maintainer
#30 lore raw

Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation

Quoted 5 lines · Waiman Long

One more thing, I think it may be worthwhile to add another comment about what happens when the HANDOFF bit was set while we take the error path (goto err). As the actual handoff is serialized by the wait_lock, the code will still do the right thing. Either the next one in the queue will be handed off or it will be unlocked if the queue is empty.

Doesn't the next patch add just such a comment?

author maintainer
#31 lore raw

Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation

In reply to Waiman Long
Quoted 29 lines · Waiman Long
Quoted 28 lines · Peter Zijlstra

/*
* Actual trylock that will work on any unlocked state.
+ *
+ * When setting the owner field, we must preserve the low flag bits.
+ *
+ * Be careful with @handoff, only set that in a wait-loop (where you set
+ * HANDOFF) to avoid recursive lock attempts.
*/

kernel/locking/mutex.c +13 -2
-static inline bool __mutex_trylock(struct mutex *lock)
+static inline bool __mutex_trylock(struct mutex *lock, const bool handoff)
 {
 	unsigned long owner, curr = (unsigned long)current;
 
 	owner = atomic_long_read(&lock->owner);
 	for (;;) { /* must loop, can race against a flag */
-		unsigned long old;
+		unsigned long old, flags = __owner_flags(owner);
+
+		if (__owner_task(owner)) {
+			if (handoff&&  unlikely(__owner_task(owner) == current)) {
+				/*
+				 * Provide ACQUIRE semantics for the lock-handoff.
+				 *
+				 * We cannot easily use load-acquire here, since
+				 * the actual load is a failed cmpxchg, which
+				 * doesn't imply any barriers.
+				 *
+				 * Also, this is a fairly unlikely scenario, and
Quoted 8 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+				 * this contains the cost.
+				 */

I am not so sure about your comment here. I guess you are referring to the atomic_long_cmpxchg_acquire below for the failed cmpxchg. However, it is also possible that the path can be triggered on the first round without cmpxchg. Maybe we can do a load_acquire on the owner again to satisfy this requirement without a smp_mb().

Yes, I refer to the atomic_long_cmpxchg_acquire() below. If that cmpxchg fails, no barriers are implied.

Yes we could fix that, but that would make all cmpxchg_acquire() loops more expensive. And only fixing the initial load doesn't help, since we still need to deal with the cmpxchg case failing.

We _could_ re-issue the load I suppose, because since if owner==current, nobody else is going to change it, but I feel slightly uneasy with that. Also, its a relative slow path, so for now, lets keep it simple and explicit like so.

Quoted 27 lines
Quoted 14 lines
kernel/locking/mutex.c +5 -3
+				smp_mb(); /* ACQUIRE */
+				return true;
+			}
 
-		if (__owner_task(owner))
 			return false;
+		}
 
-		old = atomic_long_cmpxchg_acquire(&lock->owner, owner,
-						  curr | __owner_flags(owner));
+		/*

kernel/locking/mutex.c static inline unsigned long __owner_flag · line 119

Quoted 10 lines
kernel/locking/mutex.c +8 -0
+		 * We set the HANDOFF bit, we must make sure it doesn't live
+		 * past the point where we acquire it. This would be possible
+		 * if we (accidentally) set the bit on an unlocked mutex.
+		 */
+		if (handoff)
+			flags&= ~MUTEX_FLAG_HANDOFF;
+
+		old = atomic_long_cmpxchg_acquire(&lock->owner, owner, curr | flags);
 		if (old == owner)
 			return true;

Other than that, the code is fine.

Quoted 1 line

Thanks!

#32 lore raw

Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation

In reply to Peter Zijlstra
Quoted 30 lines · Peter Zijlstra

Implement lock handoff to avoid lock starvation.

Lock starvation is possible because mutex_lock() allows lock stealing, where a running (or optimistic spinning) task beats the woken waiter to the acquire.

Lock stealing is an important performance optimization because waiting for a waiter to wake up and get runtime can take a significant time, during which everyboy would stall on the lock.

The down-side is of course that it allows for starvation.

This patch has the waiter requesting a handoff if it fails to acquire the lock upon waking. This re-introduces some of the wait time, because once we do a handoff we have to wait for the waiter to wake up again.

A future patch will add a round of optimistic spinning to attempt to alleviate this penalty, but if that turns out to not be enough, we can add a counter and only request handoff after multiple failed wakeups.

There are a few tricky implementation details:

- accepting a handoff must only be done in the wait-loop. Since the handoff condition is owner == current, it can easily cause recursive locking trouble.

- accepting the handoff must be careful to provide the ACQUIRE semantics.

Quoted 6 lines

- having the HANDOFF bit set on unlock requires care, we must not clear the owner.

- we must be careful to not leave HANDOFF set after we've acquired the lock. The tricky scenario is setting the HANDOFF bit on an unlocked mutex.

There's a hole along the interruptible path - we leave the HANDOFF bit set, even though the first waiter returns with -EINTR. The unlock then sees the HANDOFF, assigns it to the next waiter, but that waiter does a racy check to decide if it is first, decides it is not and so skips the trylock and also returns with -EINTR. (i.e. invalidating the

/*
* Here we order against unlock; we must either see it change
* state back to RUNNING and fall through the next schedule(),
* or we must see its unlock and acquire.
*/

as we may not reach the next schedule). Repeating the __mutex_waiter_is_first() after acquiring the wait_lock is sufficient, as is clearing the HANDOFF bit before -EINTR.

kernel/locking/mutex.c +2 -0
index 9b349619f431..6f7e3bf0d595 100644
@@ -684,6 +684,8 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
                 * against mutex_unlock() and wake-ups do not go missing.
                 */
                if (unlikely(signal_pending_state(state, task))) {
+                       if (first)
+                               __mutex_clear_flag(lock, MUTEX_FLAG_HANDOFF);
                        ret = -EINTR;
                        goto err;
                }

Though I expect you will be able to find a better solution. -Chris

Signature · 1 line
Chris Wilson, Intel Open Source Technology Centre
#33 lore raw

PATCH 1/1 locking/mutex: Clear mutex-handoff flag on interrupt

In reply to Peter Zijlstra

If we abort the mutex_lock() due to an interrupt, or other error from ww_mutex, we need to relinquish the handoff flag if we applied it. Otherwise, we may cause missed wakeups as the current owner may try to handoff to a new thread that is not expecting the handoff and so sleep thinking the lock is already claimed (and since the owner unlocked there may never be a new wakeup).

Fixes 9d659ae14b54 ("locking/mutex: Add lock handoff to avoid starvation")
Cc Peter Zijlstra <[email protected]>
Cc Waiman Long <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Linus Torvalds <[email protected]>
Cc Paul E. McKenney <[email protected]>
Cc Thomas Gleixner <[email protected]>
---
 kernel/locking/mutex.c | 2 ++
 1 file changed, 2 insertions(+)
kernel/locking/mutex.c +2 -0
index 9b349619f431..087bbc082ad7 100644
@@ -738,6 +738,8 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 err:
 	__set_task_state(task, TASK_RUNNING);
 	mutex_remove_waiter(lock, &waiter, task);
+	if (first)
+		__mutex_clear_flag(lock, MUTEX_FLAG_HANDOFF);
 	spin_unlock_mutex(&lock->wait_lock, flags);
 	debug_mutex_free_waiter(&waiter);
 	mutex_release(&lock->dep_map, 1, ip);
Signature · 1 line
2.11.0
author maintainer
#34 lore raw

Re: [PATCH] locking/mutex: Clear mutex-handoff flag on interrupt

Quoted 1 line · Chris Wilson

If we abort the mutex_lock() due to an interrupt, or other error from

s/interrupt/signal/, right?

Quoted 5 lines

ww_mutex, we need to relinquish the handoff flag if we applied it. Otherwise, we may cause missed wakeups as the current owner may try to handoff to a new thread that is not expecting the handoff and so sleep thinking the lock is already claimed (and since the owner unlocked there may never be a new wakeup).

Isn't that the exact same scenario as Nicolai fixed here:

http://lkml.kernel.org/r/[email protected]

Did you, like Nicolai, find this by inspection, or can you reproduce?

FWIW, I have the below patch that should also solve this problem afaict.

---
Subject: locking/mutex: Fix mutex handoff
From: Peter Zijlstra <[email protected]>
Date: Wed Jan 11 14:17:48 CET 2017

While reviewing the ww_mutex patches, I noticed that it was still possible to (incorrectly) succeed for (incorrect) code like:

	mutex_lock(&a);
	mutex_lock(&a);

This was possible if the second mutex_lock() would block (as expected) but then receive a spurious wakeup. At that point it would find itself at the front of the queue, request a handoff and instantly claim ownership and continue, since owner would point to itself.

Avoid this scenario and simplify the code by introducing a third low bit to signal handoff pickup. So once we request handoff, unlock clears the handoff bit and sets the pickup bit along with the new owner.

This also removes the need for the .handoff argument to __mutex_trylock(), since that becomes superfluous with PICKUP.

In order to guarantee enough low bits, ensure task_struct alignment is at least L1_CACHE_BYTES (which seems a good ideal regardless).

Fixes 9d659ae14b54 ("locking/mutex: Add lock handoff to avoid starvation")
---
 include/linux/mutex.h  |    2 
 kernel/fork.c          |    6 +-
 kernel/locking/mutex.c |  106 +++++++++++++++++++++++--------------------------
 3 files changed, 56 insertions(+), 58 deletions(-)
include/linux/mutex.h +1 -1
@@ -65,7 +65,7 @@ struct mutex {
 
 static inline struct task_struct *__mutex_owner(struct mutex *lock)
 {
-	return (struct task_struct *)(atomic_long_read(&lock->owner) & ~0x03);
+	return (struct task_struct *)(atomic_long_read(&lock->owner) & ~0x07);
 }
 
 /*
kernel/fork.c +4 -2
@@ -432,11 +432,13 @@ void __init fork_init(void)
 	int i;
 #ifndef CONFIG_ARCH_TASK_STRUCT_ALLOCATOR
 #ifndef ARCH_MIN_TASKALIGN
-#define ARCH_MIN_TASKALIGN	L1_CACHE_BYTES
+#define ARCH_MIN_TASKALIGN	0
 #endif
+	int align = min_t(int, L1_CACHE_BYTES, ARCH_MIN_TASKALIGN);
+
 	/* create a slab on which task_structs can be allocated */
 	task_struct_cachep = kmem_cache_create("task_struct",
-			arch_task_struct_size, ARCH_MIN_TASKALIGN,
+			arch_task_struct_size, align,
 			SLAB_PANIC|SLAB_NOTRACK|SLAB_ACCOUNT, NULL);
 #endif
 
kernel/locking/mutex.c +51 -55

213 lines not on this page · raw message

#35 lore raw

Re: [PATCH] locking/mutex: Clear mutex-handoff flag on interrupt

Quoted 4 lines · Peter Zijlstra
Quoted 1 line · Chris Wilson

If we abort the mutex_lock() due to an interrupt, or other error from

s/interrupt/signal/, right?

Yes. EINTR is ingrained.

Quoted 11 lines
Quoted 5 lines

ww_mutex, we need to relinquish the handoff flag if we applied it. Otherwise, we may cause missed wakeups as the current owner may try to handoff to a new thread that is not expecting the handoff and so sleep thinking the lock is already claimed (and since the owner unlocked there may never be a new wakeup).

Isn't that the exact same scenario as Nicolai fixed here:

http://lkml.kernel.org/r/[email protected]

Did you, like Nicolai, find this by inspection, or can you reproduce?

Looks like it should be. It takes about 20 minutes of running a stress test, but it is very reliably hit on the current kernel.

Quoted 1 line

FWIW, I have the below patch that should also solve this problem afaict.

Thanks, I shall see if makes my machines happy.
-Chris

Signature · 1 line
Chris Wilson, Intel Open Source Technology Centre
Tested-by
#36 lore raw

Re: [PATCH] locking/mutex: Clear mutex-handoff flag on interrupt

Quoted 4 lines · Chris Wilson
Quoted 1 line · Peter Zijlstra

FWIW, I have the below patch that should also solve this problem afaict.

Thanks, I shall see if makes my machines happy.

I have had a few machines run for over 24 hours on this patch.

-Chris

Signature · 1 line
Chris Wilson, Intel Open Source Technology Centre
author maintainer
#37 lore raw

PATCH 6/8 locking/mutex: Restructure wait loop

In reply to Peter Zijlstra

Doesn't really matter yet, but pull the HANDOFF and trylock out from under the wait_lock.

The intention is to add an optimistic spin loop here, which requires we do not hold the wait_lock, so shuffle code around in preparation.

Also clarify the purpose of taking the wait_lock in the wait loop, its tempting to want to avoid it altogether, but the cancellation cases need to to avoid losing wakeups.

---
 kernel/locking/mutex.c |   30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)
kernel/locking/mutex.c +25 -5
@@ -631,13 +631,21 @@ __mutex_lock_common(struct mutex *lock,
 
 	lock_contended(&lock->dep_map, ip);
 
+	set_task_state(task, state);
 	for (;;) {
+		/*
+		 * Once we hold wait_lock, we're serialized against
+		 * mutex_unlock() handing the lock off to us, do a trylock
+		 * before testing the error conditions to make sure we pick up
+		 * the handoff.
+		 */
 		if (__mutex_trylock(lock, first))
-			break;
+			goto acquired;
 
 		/*
-		 * got a signal? (This code gets eliminated in the
-		 * TASK_UNINTERRUPTIBLE case.)
+		 * Check for signals and wound conditions while holding
+		 * wait_lock. This ensures the lock cancellation is ordered
+		 * against mutex_unlock() and wake-ups do not go missing.
 		 */
 		if (unlikely(signal_pending_state(state, task))) {
 			ret = -EINTR;
@@ -650,16 +658,27 @@ __mutex_lock_common(struct mutex *lock,
 				goto err;
 		}
 
-		__set_task_state(task, state);
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
-		spin_lock_mutex(&lock->wait_lock, flags);
 
 		if (!first && __mutex_waiter_is_first(lock, &waiter)) {
 			first = true;
 			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
 		}
+
+		set_task_state(task, state);
+		/*
+		 * Here we order against unlock; we must either see it change
+		 * state back to RUNNING and fall through the next schedule(),
+		 * or we must see its unlock and acquire.
+		 */
+		if (__mutex_trylock(lock, first))
+			break;
+
+		spin_lock_mutex(&lock->wait_lock, flags);
 	}
+	spin_lock_mutex(&lock->wait_lock, flags);
+acquired:
 	__set_task_state(task, TASK_RUNNING);
 
 	mutex_remove_waiter(lock, &waiter, task);
@@ -682,6 +701,7 @@ __mutex_lock_common(struct mutex *lock,
 	return 0;
 
 err:
+	__set_task_state(task, TASK_RUNNING);
 	mutex_remove_waiter(lock, &waiter, task);
 	spin_unlock_mutex(&lock->wait_lock, flags);
 	debug_mutex_free_waiter(&waiter);
#38 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

Hi Peter,

I'm struggling to get my head around the handoff code after this change...

Quoted 32 lines · Peter Zijlstra
kernel/locking/mutex.c +11 -4
@@ -631,13 +631,21 @@ __mutex_lock_common(struct mutex *lock,
 
 	lock_contended(&lock->dep_map, ip);
 
+	set_task_state(task, state);
 	for (;;) {
+		/*
+		 * Once we hold wait_lock, we're serialized against
+		 * mutex_unlock() handing the lock off to us, do a trylock
+		 * before testing the error conditions to make sure we pick up
+		 * the handoff.
+		 */
 		if (__mutex_trylock(lock, first))
-			break;
+			goto acquired;
 
 		/*
-		 * got a signal? (This code gets eliminated in the
-		 * TASK_UNINTERRUPTIBLE case.)
+		 * Check for signals and wound conditions while holding
+		 * wait_lock. This ensures the lock cancellation is ordered
+		 * against mutex_unlock() and wake-ups do not go missing.
 		 */
 		if (unlikely(signal_pending_state(state, task))) {
 			ret = -EINTR;
@@ -650,16 +658,27 @@ __mutex_lock_common(struct mutex *lock,
 				goto err;
 		}
 
-		__set_task_state(task, state);

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 669

Quoted 10 lines
kernel/locking/mutex.c +2 -1
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
-		spin_lock_mutex(&lock->wait_lock, flags);
 
 		if (!first && __mutex_waiter_is_first(lock, &waiter)) {
 			first = true;
 			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
 		}
+
+		set_task_state(task, state);

With this change, we no longer hold the lock wit_hen we set the task state, and it's ordered strictly *after* setting the HANDOFF flag. Doesn't that mean that the unlock code can see the HANDOFF flag, issue the wakeup, but then we come in and overwrite the task state?

I'm struggling to work out whether that's an issue, but it certainly feels odd and is a change from the previous behaviour.

Will

author maintainer
#39 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

Quoted 53 lines · Will Deacon

Hi Peter,

I'm struggling to get my head around the handoff code after this change...

Quoted 32 lines · Peter Zijlstra
kernel/locking/mutex.c +11 -4
@@ -631,13 +631,21 @@ __mutex_lock_common(struct mutex *lock,
 
 	lock_contended(&lock->dep_map, ip);
 
+	set_task_state(task, state);
 	for (;;) {
+		/*
+		 * Once we hold wait_lock, we're serialized against
+		 * mutex_unlock() handing the lock off to us, do a trylock
+		 * before testing the error conditions to make sure we pick up
+		 * the handoff.
+		 */
 		if (__mutex_trylock(lock, first))
-			break;
+			goto acquired;
 
 		/*
-		 * got a signal? (This code gets eliminated in the
-		 * TASK_UNINTERRUPTIBLE case.)
+		 * Check for signals and wound conditions while holding
+		 * wait_lock. This ensures the lock cancellation is ordered
+		 * against mutex_unlock() and wake-ups do not go missing.
 		 */
 		if (unlikely(signal_pending_state(state, task))) {
 			ret = -EINTR;
@@ -650,16 +658,27 @@ __mutex_lock_common(struct mutex *lock,
 				goto err;
 		}
 
-		__set_task_state(task, state);

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 669

Quoted 10 lines
kernel/locking/mutex.c +2 -1
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
-		spin_lock_mutex(&lock->wait_lock, flags);
 
 		if (!first && __mutex_waiter_is_first(lock, &waiter)) {
 			first = true;
 			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
 		}
+
+		set_task_state(task, state);

With this change, we no longer hold the lock wit_hen we set the task state, and it's ordered strictly *after* setting the HANDOFF flag. Doesn't that mean that the unlock code can see the HANDOFF flag, issue the wakeup, but then we come in and overwrite the task state?

Quoted 2 lines

I'm struggling to work out whether that's an issue, but it certainly feels odd and is a change from the previous behaviour.

Right, so I think the code is fine, since in that case the __mutex_trylock() must see the handoff and we'll break the loop and (re)set the state to RUNNING.

But you're right in that its slightly odd. I'll reorder them and put the set_task_state() above the !first thing.

author maintainer
#40 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

Quoted 57 lines · Peter Zijlstra
Quoted 53 lines · Will Deacon

Hi Peter,

I'm struggling to get my head around the handoff code after this change...

Quoted 32 lines · Peter Zijlstra
kernel/locking/mutex.c +11 -4
@@ -631,13 +631,21 @@ __mutex_lock_common(struct mutex *lock,
 
 	lock_contended(&lock->dep_map, ip);
 
+	set_task_state(task, state);
 	for (;;) {
+		/*
+		 * Once we hold wait_lock, we're serialized against
+		 * mutex_unlock() handing the lock off to us, do a trylock
+		 * before testing the error conditions to make sure we pick up
+		 * the handoff.
+		 */
 		if (__mutex_trylock(lock, first))
-			break;
+			goto acquired;
 
 		/*
-		 * got a signal? (This code gets eliminated in the
-		 * TASK_UNINTERRUPTIBLE case.)
+		 * Check for signals and wound conditions while holding
+		 * wait_lock. This ensures the lock cancellation is ordered
+		 * against mutex_unlock() and wake-ups do not go missing.
 		 */
 		if (unlikely(signal_pending_state(state, task))) {
 			ret = -EINTR;
@@ -650,16 +658,27 @@ __mutex_lock_common(struct mutex *lock,
 				goto err;
 		}
 
-		__set_task_state(task, state);

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 669

Quoted 10 lines
kernel/locking/mutex.c +2 -1
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
-		spin_lock_mutex(&lock->wait_lock, flags);
 
 		if (!first && __mutex_waiter_is_first(lock, &waiter)) {
 			first = true;
 			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
 		}
+
+		set_task_state(task, state);

With this change, we no longer hold the lock wit_hen we set the task state, and it's ordered strictly *after* setting the HANDOFF flag. Doesn't that mean that the unlock code can see the HANDOFF flag, issue the wakeup, but then we come in and overwrite the task state?

Quoted 2 lines

I'm struggling to work out whether that's an issue, but it certainly feels odd and is a change from the previous behaviour.

Quoted 6 lines

Right, so I think the code is fine, since in that case the __mutex_trylock() must see the handoff and we'll break the loop and (re)set the state to RUNNING.

But you're right in that its slightly odd. I'll reorder them and put the set_task_state() above the !first thing.

Humm,.. we might actually rely on this order, since the MB implied by set_task_state() is the only thing that separates the store of __mutex_set_flag() from the load of __mutex_trylock(), and those should be ordered I think.

Argh, completely messed up my brain. I'll not touch it and think on this again tomorrow.

#41 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

Quoted 62 lines · Peter Zijlstra
Quoted 61 lines · Peter Zijlstra
Quoted 53 lines · Will Deacon

Hi Peter,

I'm struggling to get my head around the handoff code after this change...

Quoted 32 lines · Peter Zijlstra
kernel/locking/mutex.c +11 -4
@@ -631,13 +631,21 @@ __mutex_lock_common(struct mutex *lock,
 
 	lock_contended(&lock->dep_map, ip);
 
+	set_task_state(task, state);
 	for (;;) {
+		/*
+		 * Once we hold wait_lock, we're serialized against
+		 * mutex_unlock() handing the lock off to us, do a trylock
+		 * before testing the error conditions to make sure we pick up
+		 * the handoff.
+		 */
 		if (__mutex_trylock(lock, first))
-			break;
+			goto acquired;
 
 		/*
-		 * got a signal? (This code gets eliminated in the
-		 * TASK_UNINTERRUPTIBLE case.)
+		 * Check for signals and wound conditions while holding
+		 * wait_lock. This ensures the lock cancellation is ordered
+		 * against mutex_unlock() and wake-ups do not go missing.
 		 */
 		if (unlikely(signal_pending_state(state, task))) {
 			ret = -EINTR;
@@ -650,16 +658,27 @@ __mutex_lock_common(struct mutex *lock,
 				goto err;
 		}
 
-		__set_task_state(task, state);

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 669

Quoted 10 lines
kernel/locking/mutex.c +2 -1
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
-		spin_lock_mutex(&lock->wait_lock, flags);
 
 		if (!first && __mutex_waiter_is_first(lock, &waiter)) {
 			first = true;
 			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
 		}
+
+		set_task_state(task, state);

With this change, we no longer hold the lock wit_hen we set the task state, and it's ordered strictly *after* setting the HANDOFF flag. Doesn't that mean that the unlock code can see the HANDOFF flag, issue the wakeup, but then we come in and overwrite the task state?

Quoted 2 lines

I'm struggling to work out whether that's an issue, but it certainly feels odd and is a change from the previous behaviour.

Right, so I think the code is fine, since in that case the __mutex_trylock() must see the handoff and we'll break the loop and (re)set the state to RUNNING.

Quoted 9 lines
Quoted 2 lines

But you're right in that its slightly odd. I'll reorder them and put the set_task_state() above the !first thing.

Humm,.. we might actually rely on this order, since the MB implied by set_task_state() is the only thing that separates the store of __mutex_set_flag() from the load of __mutex_trylock(), and those should be ordered I think.

But __mutex_set_flag() and __mutex_trylock() actually touch the same atomic word? So we don't need extra things to order them?

Regards,
Boqun

Quoted 2 lines

Argh, completely messed up my brain. I'll not touch it and think on this again tomorrow.

author maintainer
#42 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

Quoted 2 lines · Boqun Feng

But __mutex_set_flag() and __mutex_trylock() actually touch the same atomic word? So we don't need extra things to order them?

Right.. in any case brain is confused. I'll look again at it later.

author maintainer
#43 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

In reply to Will Deacon
Quoted 12 lines · Will Deacon

Review

Quoted 6 lines
(quoted diff) +2 -0
 		if (!first && __mutex_waiter_is_first(lock, &waiter)) {
 			first = true;
 			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
 		}
+
+		set_task_state(task, state);

With this change, we no longer hold the lock wit_hen we set the task state, and it's ordered strictly *after* setting the HANDOFF flag. Doesn't that mean that the unlock code can see the HANDOFF flag, issue the wakeup, but then we come in and overwrite the task state?

Quoted 2 lines

I'm struggling to work out whether that's an issue, but it certainly feels odd and is a change from the previous behaviour.

OK, so after a discussion on IRC the problem appears to have been unfamiliarity with the basic sleep/wakeup scheme. Mutex used to be the odd duck out for being fully serialized by wait_lock.

The below adds a few words on how the 'normal' sleep/wakeup scheme works.

---
Subject: sched: Better explain sleep/wakeup
From: Peter Zijlstra <[email protected]>
Date: Wed Oct 19 15:45:27 CEST 2016

There were a few questions wrt how sleep-wakeup works. Try and explain it more.

---
 include/linux/sched.h |   52 ++++++++++++++++++++++++++++++++++----------------
 kernel/sched/core.c   |   15 +++++++-------
 2 files changed, 44 insertions(+), 23 deletions(-)
include/linux/sched.h +36 -16
@@ -262,20 +262,9 @@ extern char ___assert_task_state[1 - 2*!
 #define set_task_state(tsk, state_value)			\
 	do {							\
 		(tsk)->task_state_change = _THIS_IP_;		\
-		smp_store_mb((tsk)->state, (state_value));		\
+		smp_store_mb((tsk)->state, (state_value));	\
 	} while (0)
 
-/*
- * set_current_state() includes a barrier so that the write of current->state
- * is correctly serialised wrt the caller's subsequent test of whether to
- * actually sleep:
- *
- *	set_current_state(TASK_UNINTERRUPTIBLE);
- *	if (do_i_need_to_sleep())
- *		schedule();
- *
- * If the caller does not need such serialisation then use __set_current_state()
- */
 #define __set_current_state(state_value)			\
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
@@ -284,11 +273,19 @@ extern char ___assert_task_state[1 - 2*!
 #define set_current_state(state_value)				\
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
-		smp_store_mb(current->state, (state_value));		\
+		smp_store_mb(current->state, (state_value));	\
 	} while (0)
 
 #else
 
+/*
+ * @tsk had better be current, or you get to keep the pieces.
+ *
+ * The only reason is that computing current can be more expensive than
+ * using a pointer that's already available.
+ *
+ * Therefore, see set_current_state().
+ */
 #define __set_task_state(tsk, state_value)		\
 	do { (tsk)->state = (state_value); } while (0)
 #define set_task_state(tsk, state_value)		\
@@ -299,11 +296,34 @@ extern char ___assert_task_state[1 - 2*!
  * is correctly serialised wrt the caller's subsequent test of whether to
  * actually sleep:
  *
+ *   for (;;) {
  *	set_current_state(TASK_UNINTERRUPTIBLE);
- *	if (do_i_need_to_sleep())
- *		schedule();
+ *	if (!need_sleep)
+ *		break;
+ *
+ *	schedule();
+ *   }
+ *   __set_current_state(TASK_RUNNING);
+ *
+ * If the caller does not need such serialisation (because, for instance, the
+ * condition test and condition change and wakeup are under the same lock) then
+ * use __set_current_state().
+ *
+ * The above is typically ordered against the wakeup, which does:
+ *
+ *	need_sleep = false;
+ *	wake_up_state(p, TASK_UNINTERRUPTIBLE);
+ *
+ * Where wake_up_state() (and all other wakeup primitives) imply enough
+ * barriers to order the store of the variable against wakeup.
+ *
+ * Wakeup will do: if (@state & p->state) p->state = TASK_RUNNING, that is,
+ * once it observes the TASK_UNINTERRUPTIBLE store the waking CPU can issue a
+ * TASK_RUNNING store which can collide with __set_current_state(TASK_RUNNING).
+ *
+ * This is obviously fine, since they both store the exact same value.
  *
- * If the caller does not need such serialisation then use __set_current_state()
+ * Also see the comments of try_to_wake_up().
  */
 #define __set_current_state(state_value)		\
 	do { current->state = (state_value); } while (0)
kernel/sched/core.c +8 -7
@@ -2000,14 +2000,15 @@ static void ttwu_queue(struct task_struc
  * @state: the mask of task states that can be woken
  * @wake_flags: wake modifier flags (WF_*)
  *
- * Put it on the run-queue if it's not already there. The "current"
- * thread is always on the run-queue (except when the actual
- * re-schedule is in progress), and as such you're allowed to do
- * the simpler "current->state = TASK_RUNNING" to mark yourself
- * runnable without the overhead of this.
+ * If (@state & @p->state) @p->state = TASK_RUNNING.
  *
- * Return: %true if @p was woken up, %false if it was already running.
- * or @state didn't match @p's state.
+ * If the task was not queued/runnable, also place it back on a runqueue.
+ *
+ * Atomic against schedule() which would dequeue a task, also see
+ * set_current_state().
+ *
+ * Return: %true if @p->state changes (an actual wakeup was done),
+ *	   %false otherwise.
  */
 static int
 try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
#44 lore raw

ciao set_task_state() (was Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop)

Quoted 40 lines · Peter Zijlstra

Subject: sched: Better explain sleep/wakeup
From: Peter Zijlstra <[email protected]>
Date: Wed Oct 19 15:45:27 CEST 2016

There were a few questions wrt how sleep-wakeup works. Try and explain it more.

---

include/linux/sched.h |   52 ++++++++++++++++++++++++++++++++++----------------
kernel/sched/core.c   |   15 +++++++-------
2 files changed, 44 insertions(+), 23 deletions(-)
include/linux/sched.h +1 -12
@@ -262,20 +262,9 @@ extern char ___assert_task_state[1 - 2*!
 #define set_task_state(tsk, state_value)			\
 	do {							\
 		(tsk)->task_state_change = _THIS_IP_;		\
-		smp_store_mb((tsk)->state, (state_value));		\
+		smp_store_mb((tsk)->state, (state_value));	\
 	} while (0)
 
-/*
- * set_current_state() includes a barrier so that the write of current->state
- * is correctly serialised wrt the caller's subsequent test of whether to
- * actually sleep:
- *
- *	set_current_state(TASK_UNINTERRUPTIBLE);
- *	if (do_i_need_to_sleep())
- *		schedule();
- *
- * If the caller does not need such serialisation then use __set_current_state()
- */
 #define __set_current_state(state_value)			\
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
@@ -284,11 +273,19 @@ extern char ___assert_task_state[1 - 2*!
 #define set_current_state(state_value)				\

include/linux/sched.h extern char ___assert_task_state[1 - 2*! · line 282

Quoted 10 lines
include/linux/sched.h +3 -1
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
-		smp_store_mb(current->state, (state_value));		\
+		smp_store_mb(current->state, (state_value));	\
 	} while (0)
 
 #else
 
+/*
+ * @tsk had better be current, or you get to keep the pieces.

That reminds me we were getting rid of the set_task_state() calls. Bcache was pending, being only user in the kernel that doesn't actually use current; but instead breaks newly (yet blocked/uninterruptible) created garbage collection kthread. I cannot figure out why this is done (ie purposely accounting the load avg. Furthermore gc kicks in in very specific scenarios obviously, such as as by the allocator task, so I don't see why bcache gc should want to be interruptible.

Kent, Jens, can we get rid of this?

drivers/md/bcache/btree.c +0 -1
index 76f7534d1dd1..6e3c358b5759 100644
@@ -1798,7 +1798,6 @@ int bch_gc_thread_start(struct cache_set *c)
       if (IS_ERR(c->gc_thread))
               return PTR_ERR(c->gc_thread);
 
-       set_task_state(c->gc_thread, TASK_INTERRUPTIBLE);
       return 0;

}

Thanks,
Davidlohr

Quoted 72 lines
include/linux/sched.h +32 -3
+ *
+ * The only reason is that computing current can be more expensive than
+ * using a pointer that's already available.
+ *
+ * Therefore, see set_current_state().
+ */
 #define __set_task_state(tsk, state_value)		\
 	do { (tsk)->state = (state_value); } while (0)
 #define set_task_state(tsk, state_value)		\
@@ -299,11 +296,34 @@ extern char ___assert_task_state[1 - 2*!
 * is correctly serialised wrt the caller's subsequent test of whether to
 * actually sleep:
 *
+ *   for (;;) {
 *	set_current_state(TASK_UNINTERRUPTIBLE);
- *	if (do_i_need_to_sleep())
- *		schedule();
+ *	if (!need_sleep)
+ *		break;
+ *
+ *	schedule();
+ *   }
+ *   __set_current_state(TASK_RUNNING);
+ *
+ * If the caller does not need such serialisation (because, for instance, the
+ * condition test and condition change and wakeup are under the same lock) then
+ * use __set_current_state().
+ *
+ * The above is typically ordered against the wakeup, which does:
+ *
+ *	need_sleep = false;
+ *	wake_up_state(p, TASK_UNINTERRUPTIBLE);
+ *
+ * Where wake_up_state() (and all other wakeup primitives) imply enough
+ * barriers to order the store of the variable against wakeup.
+ *
+ * Wakeup will do: if (@state & p->state) p->state = TASK_RUNNING, that is,
+ * once it observes the TASK_UNINTERRUPTIBLE store the waking CPU can issue a
+ * TASK_RUNNING store which can collide with __set_current_state(TASK_RUNNING).
+ *
+ * This is obviously fine, since they both store the exact same value.
 *
- * If the caller does not need such serialisation then use __set_current_state()
+ * Also see the comments of try_to_wake_up().
 */
 #define __set_current_state(state_value)		\
 	do { current->state = (state_value); } while (0)
kernel/sched/core.c +8 -7
@@ -2000,14 +2000,15 @@ static void ttwu_queue(struct task_struc
 * @state: the mask of task states that can be woken
 * @wake_flags: wake modifier flags (WF_*)
 *
- * Put it on the run-queue if it's not already there. The "current"
- * thread is always on the run-queue (except when the actual
- * re-schedule is in progress), and as such you're allowed to do
- * the simpler "current->state = TASK_RUNNING" to mark yourself
- * runnable without the overhead of this.
+ * If (@state & @p->state) @p->state = TASK_RUNNING.
 *
- * Return: %true if @p was woken up, %false if it was already running.
- * or @state didn't match @p's state.
+ * If the task was not queued/runnable, also place it back on a runqueue.
+ *
+ * Atomic against schedule() which would dequeue a task, also see
+ * set_current_state().
+ *
+ * Return: %true if @p->state changes (an actual wakeup was done),
+ *	   %false otherwise.
 */

static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)

#45 lore raw

Re: ciao set_task_state() (was Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop)

Quoted 68 lines · Davidlohr Bueso
Quoted 40 lines · Peter Zijlstra

Subject: sched: Better explain sleep/wakeup
From: Peter Zijlstra <[email protected]>
Date: Wed Oct 19 15:45:27 CEST 2016

There were a few questions wrt how sleep-wakeup works. Try and explain it more.

---

include/linux/sched.h |   52 ++++++++++++++++++++++++++++++++++----------------
kernel/sched/core.c   |   15 +++++++-------
2 files changed, 44 insertions(+), 23 deletions(-)
include/linux/sched.h +1 -12
@@ -262,20 +262,9 @@ extern char ___assert_task_state[1 - 2*!
 #define set_task_state(tsk, state_value)			\
 	do {							\
 		(tsk)->task_state_change = _THIS_IP_;		\
-		smp_store_mb((tsk)->state, (state_value));		\
+		smp_store_mb((tsk)->state, (state_value));	\
 	} while (0)
 
-/*
- * set_current_state() includes a barrier so that the write of current->state
- * is correctly serialised wrt the caller's subsequent test of whether to
- * actually sleep:
- *
- *	set_current_state(TASK_UNINTERRUPTIBLE);
- *	if (do_i_need_to_sleep())
- *		schedule();
- *
- * If the caller does not need such serialisation then use __set_current_state()
- */
 #define __set_current_state(state_value)			\
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
@@ -284,11 +273,19 @@ extern char ___assert_task_state[1 - 2*!
 #define set_current_state(state_value)				\

include/linux/sched.h extern char ___assert_task_state[1 - 2*! · line 282

Quoted 10 lines
include/linux/sched.h +3 -1
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
-		smp_store_mb(current->state, (state_value));		\
+		smp_store_mb(current->state, (state_value));	\
 	} while (0)
 
 #else
 
+/*
+ * @tsk had better be current, or you get to keep the pieces.

That reminds me we were getting rid of the set_task_state() calls. Bcache was pending, being only user in the kernel that doesn't actually use current; but instead breaks newly (yet blocked/uninterruptible) created garbage collection kthread. I cannot figure out why this is done (ie purposely accounting the load avg. Furthermore gc kicks in in very specific scenarios obviously, such as as by the allocator task, so I don't see why bcache gc should want to be interruptible.

Kent, Jens, can we get rid of this?

drivers/md/bcache/btree.c +0 -0
index 76f7534d1dd1..6e3c358b5759 100644

drivers/md/bcache/btree.c int bch_gc_thread_start(struct cache_set *c) · line 1802

Quoted 6 lines
drivers/md/bcache/btree.c +0 -1
@@ -1798,7 +1798,6 @@ int bch_gc_thread_start(struct cache_set *c)
       if (IS_ERR(c->gc_thread))
               return PTR_ERR(c->gc_thread);
 
-       set_task_state(c->gc_thread, TASK_INTERRUPTIBLE);
       return 0;
 }

Actually, that code looks broken, or at least stupid. Let me do a proper fix...

#46 lore raw

Re: ciao set_task_state() (was Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop)

In reply to Davidlohr Bueso
Quoted 61 lines · Davidlohr Bueso
Quoted 40 lines · Peter Zijlstra

Subject: sched: Better explain sleep/wakeup
From: Peter Zijlstra <[email protected]>
Date: Wed Oct 19 15:45:27 CEST 2016

There were a few questions wrt how sleep-wakeup works. Try and explain it more.

---

include/linux/sched.h |   52 ++++++++++++++++++++++++++++++++++----------------
kernel/sched/core.c   |   15 +++++++-------
2 files changed, 44 insertions(+), 23 deletions(-)
include/linux/sched.h +1 -12
@@ -262,20 +262,9 @@ extern char ___assert_task_state[1 - 2*!
 #define set_task_state(tsk, state_value)			\
 	do {							\
 		(tsk)->task_state_change = _THIS_IP_;		\
-		smp_store_mb((tsk)->state, (state_value));		\
+		smp_store_mb((tsk)->state, (state_value));	\
 	} while (0)
 
-/*
- * set_current_state() includes a barrier so that the write of current->state
- * is correctly serialised wrt the caller's subsequent test of whether to
- * actually sleep:
- *
- *	set_current_state(TASK_UNINTERRUPTIBLE);
- *	if (do_i_need_to_sleep())
- *		schedule();
- *
- * If the caller does not need such serialisation then use __set_current_state()
- */
 #define __set_current_state(state_value)			\
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
@@ -284,11 +273,19 @@ extern char ___assert_task_state[1 - 2*!
 #define set_current_state(state_value)				\

include/linux/sched.h extern char ___assert_task_state[1 - 2*! · line 282

Quoted 10 lines
include/linux/sched.h +3 -1
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
-		smp_store_mb(current->state, (state_value));		\
+		smp_store_mb(current->state, (state_value));	\
 	} while (0)
 
 #else
 
+/*
+ * @tsk had better be current, or you get to keep the pieces.

That reminds me we were getting rid of the set_task_state() calls. Bcache was pending, being only user in the kernel that doesn't actually use current; but instead breaks newly (yet blocked/uninterruptible) created garbage collection kthread. I cannot figure out why this is done (ie purposely accounting the load avg. Furthermore gc kicks in in very specific scenarios obviously, such as as by the allocator task, so I don't see why bcache gc should want to be interruptible.

Quoted 1 line

Kent, Jens, can we get rid of this?

Here's a patch that just fixes the way gc gets woken up. Eric, you want to take this?

-- >8 --
Subject: [PATCH] bcache: Make gc wakeup saner

This lets us ditch a set_task_state() call.

---
 drivers/md/bcache/bcache.h  |  4 ++--
 drivers/md/bcache/btree.c   | 39 ++++++++++++++++++++-------------------
 drivers/md/bcache/btree.h   |  3 +--
 drivers/md/bcache/request.c |  4 +---
 drivers/md/bcache/super.c   |  2 ++
 5 files changed, 26 insertions(+), 26 deletions(-)
drivers/md/bcache/bcache.h +2 -2
index 6b420a55c7..c3ea03c9a1 100644
@@ -425,7 +425,7 @@ struct cache {
 	 * until a gc finishes - otherwise we could pointlessly burn a ton of
 	 * cpu
 	 */
-	unsigned		invalidate_needs_gc:1;
+	unsigned		invalidate_needs_gc;
 
 	bool			discard; /* Get rid of? */
 
@@ -593,8 +593,8 @@ struct cache_set {
 
 	/* Counts how many sectors bio_insert has added to the cache */
 	atomic_t		sectors_to_gc;
+	wait_queue_head_t	gc_wait;
 
-	wait_queue_head_t	moving_gc_wait;
 	struct keybuf		moving_gc_keys;
 	/* Number of moving GC bios in flight */
 	struct semaphore	moving_in_flight;
drivers/md/bcache/btree.c +20 -19
index 81d3db40cd..2efdce0724 100644
@@ -1757,32 +1757,34 @@ static void bch_btree_gc(struct cache_set *c)
 	bch_moving_gc(c);
 }
 
-static int bch_gc_thread(void *arg)
+static bool gc_should_run(struct cache_set *c)
 {
-	struct cache_set *c = arg;
 	struct cache *ca;
 	unsigned i;
 
-	while (1) {
-again:
-		bch_btree_gc(c);
+	for_each_cache(ca, c, i)
+		if (ca->invalidate_needs_gc)
+			return true;
 
-		set_current_state(TASK_INTERRUPTIBLE);
-		if (kthread_should_stop())
-			break;
+	if (atomic_read(&c->sectors_to_gc) < 0)
+		return true;
 
-		mutex_lock(&c->bucket_lock);
+	return false;
+}
 
-		for_each_cache(ca, c, i)
-			if (ca->invalidate_needs_gc) {
-				mutex_unlock(&c->bucket_lock);
-				set_current_state(TASK_RUNNING);
-				goto again;
-			}
+static int bch_gc_thread(void *arg)
+{
+	struct cache_set *c = arg;
 
-		mutex_unlock(&c->bucket_lock);
+	while (1) {
+		wait_event_interruptible(c->gc_wait,
+			   kthread_should_stop() || gc_should_run(c));
 
-		schedule();
+		if (kthread_should_stop())
+			break;
+
+		set_gc_sectors(c);
+		bch_btree_gc(c);
 	}
 
 	return 0;
@@ -1790,11 +1792,10 @@ static int bch_gc_thread(void *arg)
 
 int bch_gc_thread_start(struct cache_set *c)
 {
-	c->gc_thread = kthread_create(bch_gc_thread, c, "bcache_gc");
+	c->gc_thread = kthread_run(bch_gc_thread, c, "bcache_gc");
 	if (IS_ERR(c->gc_thread))
 		return PTR_ERR(c->gc_thread);
 
-	set_task_state(c->gc_thread, TASK_INTERRUPTIBLE);
 	return 0;
 }
 
drivers/md/bcache/btree.h +1 -2
index 5c391fa01b..9b80417cd5 100644
@@ -260,8 +260,7 @@ void bch_initial_mark_key(struct cache_set *, int, struct bkey *);
 
 static inline void wake_up_gc(struct cache_set *c)
 {
-	if (c->gc_thread)
-		wake_up_process(c->gc_thread);
+	wake_up(&c->gc_wait);
 }
 
 #define MAP_DONE	0
drivers/md/bcache/request.c +1 -3
index 40ffe5e424..a37c1776f2 100644
@@ -196,10 +196,8 @@ static void bch_data_insert_start(struct closure *cl)
 	struct data_insert_op *op = container_of(cl, struct data_insert_op, cl);
 	struct bio *bio = op->bio, *n;
 
-	if (atomic_sub_return(bio_sectors(bio), &op->c->sectors_to_gc) < 0) {
-		set_gc_sectors(op->c);
+	if (atomic_sub_return(bio_sectors(bio), &op->c->sectors_to_gc) < 0)
 		wake_up_gc(op->c);
-	}
 
 	if (op->bypass)
 		return bch_data_invalidate(cl);
drivers/md/bcache/super.c +2 -0
index 849ad441cd..66669c8f41 100644
@@ -1491,6 +1491,7 @@ struct cache_set *bch_cache_set_alloc(struct cache_sb *sb)
 	mutex_init(&c->bucket_lock);
 	init_waitqueue_head(&c->btree_cache_wait);
 	init_waitqueue_head(&c->bucket_wait);
+	init_waitqueue_head(&c->gc_wait);
 	sema_init(&c->uuid_write_mutex, 1);
 
 	spin_lock_init(&c->btree_gc_time.lock);
@@ -1550,6 +1551,7 @@ static void run_cache_set(struct cache_set *c)
 
 	for_each_cache(ca, c, i)
 		c->nbuckets += ca->sb.nbuckets;
+	set_gc_sectors(c);
 
 	if (CACHE_SYNC(&c->sb)) {
 		LIST_HEAD(journal);
Signature · 1 line
2.9.3
#47 lore raw

Re: ciao set_task_state() (was Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop)

Quoted 62 lines · Kent Overstreet
Quoted 61 lines · Davidlohr Bueso
Quoted 40 lines · Peter Zijlstra

Subject: sched: Better explain sleep/wakeup
From: Peter Zijlstra <[email protected]>
Date: Wed Oct 19 15:45:27 CEST 2016

There were a few questions wrt how sleep-wakeup works. Try and explain it more.

---

include/linux/sched.h |   52 ++++++++++++++++++++++++++++++++++----------------
kernel/sched/core.c   |   15 +++++++-------
2 files changed, 44 insertions(+), 23 deletions(-)
include/linux/sched.h +1 -12
@@ -262,20 +262,9 @@ extern char ___assert_task_state[1 - 2*!
 #define set_task_state(tsk, state_value)			\
 	do {							\
 		(tsk)->task_state_change = _THIS_IP_;		\
-		smp_store_mb((tsk)->state, (state_value));		\
+		smp_store_mb((tsk)->state, (state_value));	\
 	} while (0)
 
-/*
- * set_current_state() includes a barrier so that the write of current->state
- * is correctly serialised wrt the caller's subsequent test of whether to
- * actually sleep:
- *
- *	set_current_state(TASK_UNINTERRUPTIBLE);
- *	if (do_i_need_to_sleep())
- *		schedule();
- *
- * If the caller does not need such serialisation then use __set_current_state()
- */
 #define __set_current_state(state_value)			\
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
@@ -284,11 +273,19 @@ extern char ___assert_task_state[1 - 2*!
 #define set_current_state(state_value)				\

include/linux/sched.h extern char ___assert_task_state[1 - 2*! · line 282

Quoted 10 lines
include/linux/sched.h +3 -1
 	do {							\
 		current->task_state_change = _THIS_IP_;		\
-		smp_store_mb(current->state, (state_value));		\
+		smp_store_mb(current->state, (state_value));	\
 	} while (0)
 
 #else
 
+/*
+ * @tsk had better be current, or you get to keep the pieces.

That reminds me we were getting rid of the set_task_state() calls. Bcache was pending, being only user in the kernel that doesn't actually use current; but instead breaks newly (yet blocked/uninterruptible) created garbage collection kthread. I cannot figure out why this is done (ie purposely accounting the load avg. Furthermore gc kicks in in very specific scenarios obviously, such as as by the allocator task, so I don't see why bcache gc should want to be interruptible.

Quoted 4 lines
Quoted 1 line

Kent, Jens, can we get rid of this?

Here's a patch that just fixes the way gc gets woken up. Eric, you want to take this?

Sure, I'll put it up with my -rc2 pull request to Jens.

A couple of sanity checks (for my understanding at least):

Why does bch_data_insert_start() no longer need to call set_gc_sectors(op->c) now that bch_cache_set_alloc and bch_gc_thread do?

Does bch_cache_set_alloc() even need to call set_gc_sectors since bch_gc_thread() does before calling bch_btree_gc?

Also I'm curious, why change invalidate_needs_gc from a bitfield?

-Eric

Quoted 165 lines

-- >8 --
Subject: [PATCH] bcache: Make gc wakeup saner

This lets us ditch a set_task_state() call.

---
 drivers/md/bcache/bcache.h  |  4 ++--
 drivers/md/bcache/btree.c   | 39 ++++++++++++++++++++-------------------
 drivers/md/bcache/btree.h   |  3 +--
 drivers/md/bcache/request.c |  4 +---
 drivers/md/bcache/super.c   |  2 ++
 5 files changed, 26 insertions(+), 26 deletions(-)
drivers/md/bcache/bcache.h +2 -2
index 6b420a55c7..c3ea03c9a1 100644
@@ -425,7 +425,7 @@ struct cache {
 	 * until a gc finishes - otherwise we could pointlessly burn a ton of
 	 * cpu
 	 */
-	unsigned		invalidate_needs_gc:1;
+	unsigned		invalidate_needs_gc;
 
 	bool			discard; /* Get rid of? */
 
@@ -593,8 +593,8 @@ struct cache_set {
 
 	/* Counts how many sectors bio_insert has added to the cache */
 	atomic_t		sectors_to_gc;
+	wait_queue_head_t	gc_wait;
 
-	wait_queue_head_t	moving_gc_wait;
 	struct keybuf		moving_gc_keys;
 	/* Number of moving GC bios in flight */
 	struct semaphore	moving_in_flight;
drivers/md/bcache/btree.c +20 -19
index 81d3db40cd..2efdce0724 100644
@@ -1757,32 +1757,34 @@ static void bch_btree_gc(struct cache_set *c)
 	bch_moving_gc(c);
 }
 
-static int bch_gc_thread(void *arg)
+static bool gc_should_run(struct cache_set *c)
 {
-	struct cache_set *c = arg;
 	struct cache *ca;
 	unsigned i;
 
-	while (1) {
-again:
-		bch_btree_gc(c);
+	for_each_cache(ca, c, i)
+		if (ca->invalidate_needs_gc)
+			return true;
 
-		set_current_state(TASK_INTERRUPTIBLE);
-		if (kthread_should_stop())
-			break;
+	if (atomic_read(&c->sectors_to_gc) < 0)
+		return true;
 
-		mutex_lock(&c->bucket_lock);
+	return false;
+}
 
-		for_each_cache(ca, c, i)
-			if (ca->invalidate_needs_gc) {
-				mutex_unlock(&c->bucket_lock);
-				set_current_state(TASK_RUNNING);
-				goto again;
-			}
+static int bch_gc_thread(void *arg)
+{
+	struct cache_set *c = arg;
 
-		mutex_unlock(&c->bucket_lock);
+	while (1) {
+		wait_event_interruptible(c->gc_wait,
+			   kthread_should_stop() || gc_should_run(c));
 
-		schedule();
+		if (kthread_should_stop())
+			break;
+
+		set_gc_sectors(c);
+		bch_btree_gc(c);
 	}
 
 	return 0;
@@ -1790,11 +1792,10 @@ static int bch_gc_thread(void *arg)
 
 int bch_gc_thread_start(struct cache_set *c)
 {
-	c->gc_thread = kthread_create(bch_gc_thread, c, "bcache_gc");
+	c->gc_thread = kthread_run(bch_gc_thread, c, "bcache_gc");
 	if (IS_ERR(c->gc_thread))
 		return PTR_ERR(c->gc_thread);
 
-	set_task_state(c->gc_thread, TASK_INTERRUPTIBLE);
 	return 0;
 }
 
drivers/md/bcache/btree.h +1 -2
index 5c391fa01b..9b80417cd5 100644
@@ -260,8 +260,7 @@ void bch_initial_mark_key(struct cache_set *, int, struct bkey *);
 
 static inline void wake_up_gc(struct cache_set *c)
 {
-	if (c->gc_thread)
-		wake_up_process(c->gc_thread);
+	wake_up(&c->gc_wait);
 }
 
 #define MAP_DONE	0
drivers/md/bcache/request.c +1 -3
index 40ffe5e424..a37c1776f2 100644
@@ -196,10 +196,8 @@ static void bch_data_insert_start(struct closure *cl)
 	struct data_insert_op *op = container_of(cl, struct data_insert_op, cl);
 	struct bio *bio = op->bio, *n;
 
-	if (atomic_sub_return(bio_sectors(bio), &op->c->sectors_to_gc) < 0) {
-		set_gc_sectors(op->c);
+	if (atomic_sub_return(bio_sectors(bio), &op->c->sectors_to_gc) < 0)
 		wake_up_gc(op->c);
-	}
 
 	if (op->bypass)
 		return bch_data_invalidate(cl);
drivers/md/bcache/super.c +2 -0
index 849ad441cd..66669c8f41 100644
@@ -1491,6 +1491,7 @@ struct cache_set *bch_cache_set_alloc(struct cache_sb *sb)
 	mutex_init(&c->bucket_lock);
 	init_waitqueue_head(&c->btree_cache_wait);
 	init_waitqueue_head(&c->bucket_wait);
+	init_waitqueue_head(&c->gc_wait);
 	sema_init(&c->uuid_write_mutex, 1);
 
 	spin_lock_init(&c->btree_gc_time.lock);
@@ -1550,6 +1551,7 @@ static void run_cache_set(struct cache_set *c)
 
 	for_each_cache(ca, c, i)
 		c->nbuckets += ca->sb.nbuckets;
+	set_gc_sectors(c);
 
 	if (CACHE_SYNC(&c->sb)) {
 		LIST_HEAD(journal);
Signature · 6 lines
2.9.3

--
To unsubscribe from this list: send the line "unsubscribe linux-bcache" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#48 lore raw

Re: ciao set_task_state() (was Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop)

Quoted 6 lines · Eric Wheeler

Sure, I'll put it up with my -rc2 pull request to Jens.

A couple of sanity checks (for my understanding at least):

Why does bch_data_insert_start() no longer need to call set_gc_sectors(op->c) now that bch_cache_set_alloc and bch_gc_thread do?

Before, the gc thread wasn't being explicitly signalled that it was time to run
- it was just being woken up, meaning that a spurious wakeup would cause gc to run. At best it was sketchy and fragile, for multiple reasons - wait_event() is a much better mechanism.

So with wait_event() gc is explicitly checking if it's time to run - the wakeup doesn't really make anything happen by itself, it just pokes the gc thread so it's able to notice that it should run.

When you're signalling a thread this way - we're in effect setting a global variable that says "gc should run now", then kicking the gc thread so it can check the "gc should run" variable. But wakeups aren't synchronous - our call to wake_up() doesn't make the gc thread check that variable before it returns, all we know when the wake_up() call returns is that the gc thread is going to check that variable some point in the future. So we can't set the "gc should run" varible, wake up the gc thread, and then set it back to "gc shouldn't run yet" - what'll happen most of the time is that the gc thread won't run before we set the variable back to "gc shouldn't run yet", it'll never see that it was supposed to run and it'll go back to sleep.

So the way you make this work is you have the gc thread has to set the variable back to "gc shouldn't run yet" _after_ it's seen it and decided to run.

Quoted 2 lines

Does bch_cache_set_alloc() even need to call set_gc_sectors since bch_gc_thread() does before calling bch_btree_gc?

Yes, because the gc thread only resets the counter when it's decided to run - we don't want it to run right away at startup.

Quoted 1 line

Also I'm curious, why change invalidate_needs_gc from a bitfield?

Bitfields are particularly unsafe for multiple threads to access - the compiler has to emit instructions to do read/modify/write, which will clobber adjacent data. A bare int is also not in _general_ safe for multiple threads to access without a lock, but for what it's doing here it's fine.

#49 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

In reply to Peter Zijlstra
Quoted 19 lines · Peter Zijlstra

Doesn't really matter yet, but pull the HANDOFF and trylock out from under the wait_lock.

The intention is to add an optimistic spin loop here, which requires we do not hold the wait_lock, so shuffle code around in preparation.

Also clarify the purpose of taking the wait_lock in the wait loop, its tempting to want to avoid it altogether, but the cancellation cases need to to avoid losing wakeups.

---
  kernel/locking/mutex.c |   30 +++++++++++++++++++++++++-----
  1 file changed, 25 insertions(+), 5 deletions(-)

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 634

Quoted 4 lines
kernel/locking/mutex.c +1 -0
@@ -631,13 +631,21 @@ __mutex_lock_common(struct mutex *lock,
 
  	lock_contended(&lock->dep_map, ip);
 
+	set_task_state(task, state);

Do we want to set the state here? I am not sure if it is OK to set the task state without ever calling schedule().

Quoted 25 lines
kernel/locking/mutex.c +10 -4
  	for (;;) {
+		/*
+		 * Once we hold wait_lock, we're serialized against
+		 * mutex_unlock() handing the lock off to us, do a trylock
+		 * before testing the error conditions to make sure we pick up
+		 * the handoff.
+		 */
  		if (__mutex_trylock(lock, first))
-			break;
+			goto acquired;
 
  		/*
-		 * got a signal? (This code gets eliminated in the
-		 * TASK_UNINTERRUPTIBLE case.)
+		 * Check for signals and wound conditions while holding
+		 * wait_lock. This ensures the lock cancellation is ordered
+		 * against mutex_unlock() and wake-ups do not go missing.
  		 */
  		if (unlikely(signal_pending_state(state, task))) {
  			ret = -EINTR;
@@ -650,16 +658,27 @@ __mutex_lock_common(struct mutex *lock,
  				goto err;
  		}
 
-		__set_task_state(task, state);

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 669

Quoted 10 lines
kernel/locking/mutex.c +2 -1
  		spin_unlock_mutex(&lock->wait_lock, flags);
  		schedule_preempt_disabled();
-		spin_lock_mutex(&lock->wait_lock, flags);
 
  		if (!first&&  __mutex_waiter_is_first(lock,&waiter)) {
  			first = true;
  			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
  		}
+
+		set_task_state(task, state);

I would suggest keep the __set_task_state() above and change set_task_state(task, state) to set_task_state(task, TASK_RUNNING) to provide the memory barrier. Then we don't need adding __set_task_state() calls below.

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 677

Quoted 8 lines
kernel/locking/mutex.c +8 -0
+		/*
+		 * Here we order against unlock; we must either see it change
+		 * state back to RUNNING and fall through the next schedule(),
+		 * or we must see its unlock and acquire.
+		 */
+		if (__mutex_trylock(lock, first))
+			break;
+

I don't think we need a trylock here since we are going to do it at the top of the loop within wait_lock anyway.

Quoted 10 lines
kernel/locking/mutex.c +3 -0
+		spin_lock_mutex(&lock->wait_lock, flags);
  	}
+	spin_lock_mutex(&lock->wait_lock, flags);
+acquired:
  	__set_task_state(task, TASK_RUNNING);
 
  	mutex_remove_waiter(lock,&waiter, task);

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 707

Quoted 7 lines
kernel/locking/mutex.c +1 -0
@@ -682,6 +701,7 @@ __mutex_lock_common(struct mutex *lock,
  	return 0;
 
  err:
+	__set_task_state(task, TASK_RUNNING);
  	mutex_remove_waiter(lock,&waiter, task);
  	spin_unlock_mutex(&lock->wait_lock, flags);
  	debug_mutex_free_waiter(&waiter);

Cheers,
Longman

author maintainer
#50 lore raw

Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop

Quoted 9 lines · Waiman Long

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 634

Quoted 6 lines

+++ b/kernel/locking/mutex.c

kernel/locking/mutex.c +1 -0
@@ -631,13 +631,21 @@ __mutex_lock_common(struct mutex *lock,
 
 	lock_contended(&lock->dep_map, ip);
 
+	set_task_state(task, state);

Do we want to set the state here? I am not sure if it is OK to set the task state without ever calling schedule().

That's entirely fine, note how we'll set it back to RUNNING at the end.

Quoted 29 lines
Quoted 29 lines
kernel/locking/mutex.c +10 -5
 	for (;;) {
+		/*
+		 * Once we hold wait_lock, we're serialized against
+		 * mutex_unlock() handing the lock off to us, do a trylock
+		 * before testing the error conditions to make sure we pick up
+		 * the handoff.
+		 */
 		if (__mutex_trylock(lock, first))
-			break;
+			goto acquired;
 
 		/*
-		 * got a signal? (This code gets eliminated in the
-		 * TASK_UNINTERRUPTIBLE case.)
+		 * Check for signals and wound conditions while holding
+		 * wait_lock. This ensures the lock cancellation is ordered
+		 * against mutex_unlock() and wake-ups do not go missing.
 		 */
 		if (unlikely(signal_pending_state(state, task))) {
 			ret = -EINTR;
@@ -650,16 +658,27 @@ __mutex_lock_common(struct mutex *lock,
 				goto err;
 		}
 
-		__set_task_state(task, state);
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
-		spin_lock_mutex(&lock->wait_lock, flags);
 
Quoted 11 lines

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 669

Quoted 6 lines
kernel/locking/mutex.c +2 -0
 		if (!first&&  __mutex_waiter_is_first(lock,&waiter)) {
 			first = true;
 			__mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
 		}
+
+		set_task_state(task, state);

I would suggest keep the __set_task_state() above and change set_task_state(task, state) to set_task_state(task, TASK_RUNNING) to provide the memory barrier. Then we don't need adding __set_task_state() calls below.

set_task_state(RUNNING) doesn't make sense, ever.

See the comment near set_task_state() for the reason it has a barrier.

We need it here because when we do that trylock (or optimistic spin) we need to have set the state and done a barrier, otherwise we can miss a wakeup and get stuck.

Quoted 11 lines

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 677

Quoted 8 lines
kernel/locking/mutex.c +8 -0
+		/*
+		 * Here we order against unlock; we must either see it change
+		 * state back to RUNNING and fall through the next schedule(),
+		 * or we must see its unlock and acquire.
+		 */
+		if (__mutex_trylock(lock, first))
+			break;
+

I don't think we need a trylock here since we are going to do it at the top of the loop within wait_lock anyway.

The idea was to avoid the wait-time of that lock acquire, also, this is a place-holder for the optimistic spin site for the next patch.

author maintainer
#51 lore raw

PATCH 7/8 locking/mutex: Simplify some ww_mutex code in __mutex_lock_common()

In reply to Peter Zijlstra

From: Waiman Long <[email protected]>

This patch removes some of the redundant ww_mutex code in __mutex_lock_common().

Cc Ingo Molnar <[email protected]>
Cc Linus Torvalds <[email protected]>
Cc Tim Chen <[email protected]>
Cc Thomas Gleixner <[email protected]>
Cc Imre Deak <[email protected]>
Cc Jason Low <[email protected]>
Cc "Paul E. McKenney" <[email protected]>
Cc Ding Tianhong <[email protected]>
Cc Davidlohr Bueso <[email protected]>
Cc Will Deacon <[email protected]>
---
 kernel/locking/mutex.c |   13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)
kernel/locking/mutex.c +4 -9
@@ -580,10 +580,11 @@ __mutex_lock_common(struct mutex *lock,
 	struct mutex_waiter waiter;
 	unsigned long flags;
 	bool first = false;
+	struct ww_mutex *ww;
 	int ret;
 
 	if (use_ww_ctx) {
-		struct ww_mutex *ww = container_of(lock, struct ww_mutex, base);
+		ww = container_of(lock, struct ww_mutex, base);
 		if (unlikely(ww_ctx == READ_ONCE(ww->ctx)))
 			return -EALREADY;
 	}
@@ -595,12 +596,8 @@ __mutex_lock_common(struct mutex *lock,
 	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
 		/* got the lock, yay! */
 		lock_acquired(&lock->dep_map, ip);
-		if (use_ww_ctx) {
-			struct ww_mutex *ww;
-			ww = container_of(lock, struct ww_mutex, base);
-
+		if (use_ww_ctx)
 			ww_mutex_set_context_fastpath(ww, ww_ctx);
-		}
 		preempt_enable();
 		return 0;
 	}
@@ -680,10 +677,8 @@ __mutex_lock_common(struct mutex *lock,
 	/* got the lock - cleanup and rejoice! */
 	lock_acquired(&lock->dep_map, ip);
 
-	if (use_ww_ctx) {
-		struct ww_mutex *ww = container_of(lock, struct ww_mutex, base);
+	if (use_ww_ctx)
 		ww_mutex_set_context_slowpath(ww, ww_ctx);
-	}
 
 	spin_unlock_mutex(&lock->wait_lock, flags);
 	preempt_enable();
author maintainer
#52 lore raw

PATCH 8/8 locking/mutex: Enable optimistic spinning of woken waiter

In reply to Peter Zijlstra

From: Waiman Long <[email protected]>

This patch makes the waiter that sets the HANDOFF flag start spinning instead of sleeping until the handoff is complete or the owner sleeps. Otherwise, the handoff will cause the optimistic spinners to abort spinning as the handed-off owner may not be running.

Cc Ingo Molnar <[email protected]>
Cc Linus Torvalds <[email protected]>
Cc Tim Chen <[email protected]>
Cc Thomas Gleixner <[email protected]>
Cc Imre Deak <[email protected]>
Cc Jason Low <[email protected]>
Cc "Paul E. McKenney" <[email protected]>
Cc Ding Tianhong <[email protected]>
Cc Davidlohr Bueso <[email protected]>
Cc Will Deacon <[email protected]>
---
 kernel/locking/mutex.c |   77 ++++++++++++++++++++++++++++++++++---------------
 1 file changed, 54 insertions(+), 23 deletions(-)
kernel/locking/mutex.c +54 -23
@@ -416,24 +416,39 @@ static inline int mutex_can_spin_on_owne
  *
  * Returns true when the lock was taken, otherwise false, indicating
  * that we need to jump to the slowpath and sleep.
+ *
+ * The waiter flag is set to true if the spinner is a waiter in the wait
+ * queue. The waiter-spinner will spin on the lock directly and concurrently
+ * with the spinner at the head of the OSQ, if present, until the owner is
+ * changed to itself.
  */
 static bool mutex_optimistic_spin(struct mutex *lock,
-				  struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)
+				  struct ww_acquire_ctx *ww_ctx,
+				  const bool use_ww_ctx, const bool waiter)
 {
 	struct task_struct *task = current;
 
-	if (!mutex_can_spin_on_owner(lock))
-		goto done;
+	if (!waiter) {
+		/*
+		 * The purpose of the mutex_can_spin_on_owner() function is
+		 * to eliminate the overhead of osq_lock() and osq_unlock()
+		 * in case spinning isn't possible. As a waiter-spinner
+		 * is not going to take OSQ lock anyway, there is no need
+		 * to call mutex_can_spin_on_owner().
+		 */
+		if (!mutex_can_spin_on_owner(lock))
+			goto fail;
 
-	/*
-	 * In order to avoid a stampede of mutex spinners trying to
-	 * acquire the mutex all at once, the spinners need to take a
-	 * MCS (queued) lock first before spinning on the owner field.
-	 */
-	if (!osq_lock(&lock->osq))
-		goto done;
+		/*
+		 * In order to avoid a stampede of mutex spinners trying to
+		 * acquire the mutex all at once, the spinners need to take a
+		 * MCS (queued) lock first before spinning on the owner field.
+		 */
+		if (!osq_lock(&lock->osq))
+			goto fail;
+	}
 
-	while (true) {
+	for (;;) {
 		struct task_struct *owner;
 
 		if (use_ww_ctx && ww_ctx->acquired > 0) {
@@ -449,7 +464,7 @@ static bool mutex_optimistic_spin(struct
 			 * performed the optimistic spinning cannot be done.
 			 */
 			if (READ_ONCE(ww->ctx))
-				break;
+				goto fail_unlock;
 		}
 
 		/*
@@ -457,15 +472,20 @@ static bool mutex_optimistic_spin(struct
 		 * release the lock or go to sleep.
 		 */
 		owner = __mutex_owner(lock);
-		if (owner && !mutex_spin_on_owner(lock, owner))
-			break;
+		if (owner) {
+			if (waiter && owner == task) {
+				smp_mb(); /* ACQUIRE */
+				break;
+			}
 
-		/* Try to acquire the mutex if it is unlocked. */
-		if (__mutex_trylock(lock, false)) {
-			osq_unlock(&lock->osq);
-			return true;
+			if (!mutex_spin_on_owner(lock, owner))
+				goto fail_unlock;
 		}
 
+		/* Try to acquire the mutex if it is unlocked. */
+		if (__mutex_trylock(lock, waiter))
+			break;
+
 		/*
 		 * The cpu_relax() call is a compiler barrier which forces
 		 * everything in this loop to be re-loaded. We don't need
@@ -475,8 +495,17 @@ static bool mutex_optimistic_spin(struct
 		cpu_relax_lowlatency();
 	}
 
-	osq_unlock(&lock->osq);
-done:
+	if (!waiter)
+		osq_unlock(&lock->osq);
+
+	return true;
+
+
+fail_unlock:
+	if (!waiter)
+		osq_unlock(&lock->osq);
+
+fail:
 	/*
 	 * If we fell out of the spin path because of need_resched(),
 	 * reschedule now, before we try-lock the mutex. This avoids getting
@@ -495,7 +524,8 @@ static bool mutex_optimistic_spin(struct
 }
 #else
 static bool mutex_optimistic_spin(struct mutex *lock,
-				  struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)
+				  struct ww_acquire_ctx *ww_ctx,
+				  const bool use_ww_ctx, const bool waiter)
 {
 	return false;
 }
@@ -600,7 +630,7 @@ __mutex_lock_common(struct mutex *lock,
 	mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
 
 	if (__mutex_trylock(lock, false) ||
-	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
+	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, false)) {
 		/* got the lock, yay! */
 		lock_acquired(&lock->dep_map, ip);
 		if (use_ww_ctx)
@@ -669,7 +699,8 @@ __mutex_lock_common(struct mutex *lock,
 		 * state back to RUNNING and fall through the next schedule(),
 		 * or we must see its unlock and acquire.
 		 */
-		if (__mutex_trylock(lock, first))
+		if ((first && mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, true)) ||
+		     __mutex_trylock(lock, first))
 			break;
 
 		spin_lock_mutex(&lock->wait_lock, flags);
#53 lore raw

Re: [PATCH -v4 8/8] locking/mutex: Enable optimistic spinning of woken waiter

Quoted 86 lines · Peter Zijlstra

From: Waiman Long <[email protected]>

This patch makes the waiter that sets the HANDOFF flag start spinning instead of sleeping until the handoff is complete or the owner sleeps. Otherwise, the handoff will cause the optimistic spinners to abort spinning as the handed-off owner may not be running.

Cc Ingo Molnar <[email protected]>
Cc Linus Torvalds <[email protected]>
Cc Tim Chen <[email protected]>
Cc Thomas Gleixner <[email protected]>
Cc Imre Deak <[email protected]>
Cc Jason Low <[email protected]>
Cc "Paul E. McKenney" <[email protected]>
Cc Ding Tianhong <[email protected]>
Cc Davidlohr Bueso <[email protected]>
Cc Will Deacon <[email protected]>
---
 kernel/locking/mutex.c |   77 ++++++++++++++++++++++++++++++++++---------------
 1 file changed, 54 insertions(+), 23 deletions(-)
kernel/locking/mutex.c +27 -12
@@ -416,24 +416,39 @@ static inline int mutex_can_spin_on_owne
  *
  * Returns true when the lock was taken, otherwise false, indicating
  * that we need to jump to the slowpath and sleep.
+ *
+ * The waiter flag is set to true if the spinner is a waiter in the wait
+ * queue. The waiter-spinner will spin on the lock directly and concurrently
+ * with the spinner at the head of the OSQ, if present, until the owner is
+ * changed to itself.
  */
 static bool mutex_optimistic_spin(struct mutex *lock,
-				  struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)
+				  struct ww_acquire_ctx *ww_ctx,
+				  const bool use_ww_ctx, const bool waiter)
 {
 	struct task_struct *task = current;
 
-	if (!mutex_can_spin_on_owner(lock))
-		goto done;
+	if (!waiter) {
+		/*
+		 * The purpose of the mutex_can_spin_on_owner() function is
+		 * to eliminate the overhead of osq_lock() and osq_unlock()
+		 * in case spinning isn't possible. As a waiter-spinner
+		 * is not going to take OSQ lock anyway, there is no need
+		 * to call mutex_can_spin_on_owner().
+		 */
+		if (!mutex_can_spin_on_owner(lock))
+			goto fail;
 
-	/*
-	 * In order to avoid a stampede of mutex spinners trying to
-	 * acquire the mutex all at once, the spinners need to take a
-	 * MCS (queued) lock first before spinning on the owner field.
-	 */
-	if (!osq_lock(&lock->osq))
-		goto done;
+		/*
+		 * In order to avoid a stampede of mutex spinners trying to
+		 * acquire the mutex all at once, the spinners need to take a
+		 * MCS (queued) lock first before spinning on the owner field.
+		 */
+		if (!osq_lock(&lock->osq))
+			goto fail;
+	}
 
-	while (true) {
+	for (;;) {
 		struct task_struct *owner;
 
 		if (use_ww_ctx && ww_ctx->acquired > 0) {
@@ -449,7 +464,7 @@ static bool mutex_optimistic_spin(struct
 			 * performed the optimistic spinning cannot be done.
 			 */
 			if (READ_ONCE(ww->ctx))
-				break;
+				goto fail_unlock;
 		}
 
 		/*

kernel/locking/mutex.c static bool mutex_optimistic_spin(struct · line 477

Quoted 8 lines
kernel/locking/mutex.c +3 -2
@@ -457,15 +472,20 @@ static bool mutex_optimistic_spin(struct
 		 * release the lock or go to sleep.
 		 */
 		owner = __mutex_owner(lock);
-		if (owner && !mutex_spin_on_owner(lock, owner))
-			break;
+		if (owner) {
+			if (waiter && owner == task) {
+				smp_mb(); /* ACQUIRE */

Hmm, is this barrier actually needed? This only happens on the handoff path, and we take the wait_lock immediately after this succeeds anyway. That control dependency, coupled with the acquire semantics of the spin_lock, should be sufficient, no?

Will

author maintainer
#54 lore raw

Re: [PATCH -v4 8/8] locking/mutex: Enable optimistic spinning of woken waiter

Quoted 16 lines · Will Deacon

kernel/locking/mutex.c static bool mutex_optimistic_spin(struct · line 477

Quoted 9 lines · Peter Zijlstra
kernel/locking/mutex.c +3 -2
@@ -457,15 +472,20 @@ static bool mutex_optimistic_spin(struct
 		 * release the lock or go to sleep.
 		 */
 		owner = __mutex_owner(lock);
-		if (owner && !mutex_spin_on_owner(lock, owner))
-			break;
+		if (owner) {
+			if (waiter && owner == task) {
+				smp_mb(); /* ACQUIRE */

Hmm, is this barrier actually needed? This only happens on the handoff path, and we take the wait_lock immediately after this succeeds anyway. That control dependency, coupled with the acquire semantics of the spin_lock, should be sufficient, no?

Yes, I think you're right. But like said in that earlier email, I'd like to keep this for now.

Once this code has settled we can reconsider this.

#55 lore raw

Re: [PATCH -v4 8/8] locking/mutex: Enable optimistic spinning of woken waiter

In reply to Peter Zijlstra
Quoted 10 lines · Peter Zijlstra
kernel/locking/mutex.c +1 -1
@@ -600,7 +630,7 @@ __mutex_lock_common(struct mutex *lock,
  	mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
 
  	if (__mutex_trylock(lock, false) ||
-	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
+	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, false)) {
  		/* got the lock, yay! */
  		lock_acquired(&lock->dep_map, ip);
  		if (use_ww_ctx)

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 703

Quoted 6 lines
kernel/locking/mutex.c +2 -1
@@ -669,7 +699,8 @@ __mutex_lock_common(struct mutex *lock,
  		 * state back to RUNNING and fall through the next schedule(),
  		 * or we must see its unlock and acquire.
  		 */
-		if (__mutex_trylock(lock, first))
+		if ((first&&  mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, true)) ||
+		     __mutex_trylock(lock, first))

Do we need a __mutex_trylock() here? mutex_optimistic_spin() will do the trylock and we have one at the top of the loop.

Cheers,
Longman

author maintainer
#56 lore raw

Re: [PATCH -v4 8/8] locking/mutex: Enable optimistic spinning of woken waiter

Quoted 4 lines · Waiman Long
Quoted 3 lines · Peter Zijlstra
kernel/locking/mutex.c +0 -0
@@ -600,7 +630,7 @@ __mutex_lock_common(struct mutex *lock,
 	mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
 
Quoted 16 lines

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, · line 703

Quoted 13 lines
kernel/locking/mutex.c +3 -2
 	if (__mutex_trylock(lock, false) ||
-	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
+	    mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, false)) {
 		/* got the lock, yay! */
 		lock_acquired(&lock->dep_map, ip);
 		if (use_ww_ctx)
@@ -669,7 +699,8 @@ __mutex_lock_common(struct mutex *lock,
 		 * state back to RUNNING and fall through the next schedule(),
 		 * or we must see its unlock and acquire.
 		 */
-		if (__mutex_trylock(lock, first))
+		if ((first&&  mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, true)) ||
+		     __mutex_trylock(lock, first))

Do we need a __mutex_trylock() here? mutex_optimistic_spin() will do the trylock and we have one at the top of the loop.

Yes, mutex_optimistic_spin() can be a no-op.

#57 lore raw
In reply to Peter Zijlstra
Quoted 3 lines · Peter Zijlstra

Since you all should not be sending patches during the merge window, I figured I should to keep you all occupied with something.

Thanks.. I guess.

No real review of the code itself from me (I'll leave that to Waiman and the usual gang), but I have to say that I like how it looks from 30000 ft. Getting rid of the arch-specific code is lovely.

Linus

Tested-by on the v1 series
#58 lore raw
In reply to Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

Hi all,

Since you all should not be sending patches during the merge window, I figured I should to keep you all occupied with something.

Please review, test and otherwise try to break these here patches.

I would like to get these patches into -tip (and -next) once the merge window closes, so please spend these quiet days staring at this stuff.

Small changes only, mostly the handoff logic as suggested by Waiman last time.

I tested these new patches on the 8 socket system with the high_systime workload which stresses mutexes.

The average throughput with and without the patches were very similar at lower levels of contention. At high contention, the throughput with the patches were slightly lower, but only by a small amount, which I think is expected due to the additional waiter wait time, ect...

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

|  users      | avg throughput | avg throughput |
              | without patch  | with patch     |

-------------------------------------------------
| 10 - 90 | 13,989 JPM | 13,920 JPM |
-------------------------------------------------
| 100 - 900 | 76,362 JPM | 76,298 JPM |
-------------------------------------------------
| 1000 - 1900 | 77,146 JPM | 76,061 JPM |
-------------------------------------------------