locking/mutex: Rewrite basic mutex
Subsystems (28)
Peter Zijlstra
maintainercover 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(-)
Peter Zijlstra
maintainerPATCH 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.
--- 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(structstatic 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; }
Peter Zijlstra
maintainerRe: [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
@@ -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 */
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
Peter Zijlstra
maintainerRe: [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.
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
Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery
In reply to Peter ZijlstraQuoted 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
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
Peter Zijlstra
maintainerRe: [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?
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
Peter Zijlstra
maintainerRe: [PATCH -v4 1/8] locking/drm: Kill mutex trickery
Quoted 6 lines · Thomas Gleixner
Well, when you add just
trylock_recursivethen 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.
Peter Zijlstra
maintainerRe: [PATCH -v4 1/8] locking/drm: Kill mutex trickery
In reply to Peter ZijlstraQuoted 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.
--- 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
@@ -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
@@ -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
@@ -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
@@ -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*\)/ ) {
Peter Zijlstra
maintainerRe: [PATCH -v4 1/8] locking/drm: Kill mutex trickery
In reply to Peter ZijlstraQuoted 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]>Signed-off-by Peter Zijlstra (Intel) <[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.
Peter Zijlstra
maintainerRe: [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]>Signed-off-by Peter Zijlstra (Intel) <[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").
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]>Signed-off-by Peter Zijlstra (Intel) <[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
Peter Zijlstra
maintainerRe: [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?
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_recursiveis 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
Peter Zijlstra
maintainerRe: [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.
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
Re: [PATCH -v4 1/8] locking/drm: Kill mutex trickery
In reply to Peter ZijlstraQuoted 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]>Signed-off-by Peter Zijlstra (Intel) <[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
Peter Zijlstra
maintainerPATCH 2/8 locking/mutex: Rework mutex::owner
In reply to Peter ZijlstraThe 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 muextern 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
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
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
Re: [PATCH -v4 2/8] locking/mutex: Rework mutex::owner
In reply to Peter ZijlstraQuoted 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_mutexalready 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.
Signed-off-by Peter Zijlstra (Intel) <[email protected]>
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
Peter Zijlstra
maintainerPATCH 3/8 locking/mutex: Kill arch specific code
In reply to Peter ZijlstraIts 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 */
Peter Zijlstra
maintainerPATCH 4/8 locking/mutex: Allow MUTEX_SPIN_ON_OWNER when DEBUG_MUTEXES
In reply to Peter ZijlstraNow 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
Peter Zijlstra
maintainerPATCH 5/8 locking/mutex: Add lock handoff to avoid starvation
In reply to Peter ZijlstraImplement 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(statomic_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(structbreak ;/* 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_);
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
Peter Zijlstra
maintainerRe: [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_acquirewill 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.
Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation
In reply to Peter ZijlstraQuoted 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.
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 boolhandoff)
{ 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_acquirebelow 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 aload_acquireon the owner again to satisfy this requirement without asmp_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, currQuoted 5 lines
| flags); if (old == owner) return true;
Quoted 4 lines
Other than that, the code is fine.
Reviewed-by Waiman Long <[email protected]>
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
Peter Zijlstra
maintainerRe: [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?
Peter Zijlstra
maintainerRe: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation
In reply to Waiman LongQuoted 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_acquirebelow 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 aload_acquireon the owner again to satisfy this requirement without asmp_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
Reviewed-by Waiman Long <[email protected]>
Thanks!
Re: [PATCH -v4 5/8] locking/mutex: Add lock handoff to avoid starvation
In reply to Peter ZijlstraQuoted 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
@@ -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
PATCH 1/1 locking/mutex: Clear mutex-handoff flag on interrupt
In reply to Peter ZijlstraIf 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).
--- kernel/locking/mutex.c | 2 ++ 1 file changed, 2 insertions(+)
kernel/locking/mutex.c +2 -0
@@ -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
Peter Zijlstra
maintainerRe: [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).
--- 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
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 froms/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
Chris Wilson
Tested-byRe: [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
Peter Zijlstra
maintainerPATCH 6/8 locking/mutex: Restructure wait loop
In reply to Peter ZijlstraDoesn'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);
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
-1spin_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
Peter Zijlstra
maintainerRe: [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
-1spin_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_henwe 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.
Peter Zijlstra
maintainerRe: [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
-1spin_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_henwe 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.
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
-1spin_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_henwe 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.
Peter Zijlstra
maintainerRe: [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.
Peter Zijlstra
maintainerRe: [PATCH -v4 6/8] locking/mutex: Restructure wait loop
In reply to Will DeaconQuoted 12 lines · Will Deacon
Review
Quoted 6 lines
(quoted diff) +2
-0if (!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_henwe 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)
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 2016There were a few questions wrt how sleep-wakeup works. Try and explain it more.
Requested-by Will Deacon <[email protected]>Signed-off-by Peter Zijlstra (Intel) <[email protected]>---
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
-1do { \ 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
@@ -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)
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 2016There were a few questions wrt how sleep-wakeup works. Try and explain it more.
Requested-by Will Deacon <[email protected]>Signed-off-by Peter Zijlstra (Intel) <[email protected]>---
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
-1do { \ 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
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...
Re: ciao set_task_state() (was Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop)
In reply to Davidlohr BuesoQuoted 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 2016There were a few questions wrt how sleep-wakeup works. Try and explain it more.
Requested-by Will Deacon <[email protected]>Signed-off-by Peter Zijlstra (Intel) <[email protected]>---
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
-1do { \ 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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 2016There were a few questions wrt how sleep-wakeup works. Try and explain it more.
Requested-by Will Deacon <[email protected]>Signed-off-by Peter Zijlstra (Intel) <[email protected]>---
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
-1do { \ 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 sanerThis lets us ditch a
set_task_state()call.Signed-off-by Kent Overstreet <[email protected]>--- 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@@ -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@@ -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@@ -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 0drivers/md/bcache/request.c +1
-3@@ -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@@ -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
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 callset_gc_sectors(op->c)now thatbch_cache_set_allocandbch_gc_threaddo?
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 callset_gc_sectorssincebch_gc_thread()does before callingbch_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_gcfrom 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.
Re: [PATCH -v4 6/8] locking/mutex: Restructure wait loop
In reply to Peter ZijlstraQuoted 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_lockin the wait loop, its tempting to want to avoid it altogether, but the cancellation cases need to to avoid losing wakeups.Suggested-by Waiman Long<[email protected]>Signed-off-by Peter Zijlstra (Intel)<[email protected]>--- 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
-4for (;;) { +/* +* 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
-1spin_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
Peter Zijlstra
maintainerRe: [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.ckernel/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
-5for (;;) { +/* +* 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
-0if (!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 changeset_task_state(task, state)toset_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_lockanyway.
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.
Peter Zijlstra
maintainerPATCH 7/8 locking/mutex: Simplify some ww_mutex code in __mutex_lock_common()
In reply to Peter ZijlstraFrom: Waiman Long <[email protected]>
This patch removes some of the redundant ww_mutex code in __mutex_lock_common().
--- 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 ();
Peter Zijlstra
maintainerPATCH 8/8 locking/mutex: Enable optimistic spinning of woken waiter
In reply to Peter ZijlstraFrom: 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.
--- 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(structcpu_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);
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]>Signed-off-by Waiman Long <[email protected]>Signed-off-by Peter Zijlstra (Intel) <[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
Peter Zijlstra
maintainerRe: [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_lockimmediately after this succeeds anyway. That control dependency, coupled with the acquire semantics of thespin_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.
Re: [PATCH -v4 8/8] locking/mutex: Enable optimistic spinning of woken waiter
In reply to Peter ZijlstraQuoted 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
Peter Zijlstra
maintainerRe: [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
-2if (__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.
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
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 |
-------------------------------------------------