Generic Mutex Subsystem, mutex-core.patch
Subsystems (4)
PATCH 5/15 Generic Mutex Subsystem, mutex-core.patch
mutex implementation, core files: just the basic subsystem, no users of it.
----
include/linux/mutex.h | 102 ++++++++ include/linux/preempt.h | 1 include/linux/sched.h | 5 kernel/Makefile | 2 kernel/fork.c | 4 kernel/mutex.c | 564 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 677 insertions(+), 1 deletion(-)
include/linux/mutex.h new file +102 -0
@@ -0,0 +1,102 @@ +#ifndef __LINUX_MUTEX_H +#define __LINUX_MUTEX_H + +/* +* Mutexes +* +* started by Ingo Molnar: +* +* Copyright (C) 2004, 2005 Red Hat, Inc., Ingo Molnar <[email protected]> +* +* This file contains the main data structure and API definitions. +*/ +#include <linux/config.h> +#include <asm/atomic.h> +#include <linux/list.h> +#include <linux/spinlock_types.h> + +/* +* Simple, straightforward mutexes with strict semantics: +* +* - only one task can hold the mutex at a time +* - only the owner can unlock the mutex +* - multiple unlocks are not permitted +* - recursive locking is not permitted +* - a mutex object must be initialized via the API +* - a mutex object must not be initialized via memset or copying +* - task may not exit with mutex held +* - memory areas where held locks reside must not be freed +* +* These semantics are fully enforced when DEBUG_MUTEXESS is +* enabled. Furthermore, besides enforcing the above rules, the mutex +* debugging code also implements a number of additional features +* that make lock debugging easier and faster: +* +* - uses symbolic names of mutexes, whenever they are printed in debug output +* - point-of-acquire tracking, symbolic lookup of function names +* - list of all locks held in the system, printout of them +* - owner tracking +* - detects self-recursing locks and prints out all relevant info +* - detects multi-task circular deadlocks and prints out all affected +* locks and tasks (and only those tasks) +*/ +struct mutex { +// 1: unlocked, 0: locked, negative: locked, possibly waiters +atomic_t count; +spinlock_t wait_lock; +struct list_head wait_list; +#ifdef CONFIG_DEBUG_MUTEXESS +struct thread_info *owner; +struct list_head held_list; +unsigned long acquire_ip; +char *name, *file; +int line; +void *magic; +#endif +}; + +/* +* This is the control structure for tasks blocked on mutex, +* which resides on the blocked task's kernel stack: +*/ +struct mutex_waiter { +struct mutex *lock; +struct list_head list; +struct thread_info *ti; +int woken; +#ifdef CONFIG_DEBUG_MUTEXESS +unsigned long ip; +void *magic; +#endif +}; + +#ifdef CONFIG_DEBUG_MUTEXESS +# define __MUTEX_DEADLOCK_DETECT_INITIALIZER (lockname) \ + , .held_list = LIST_HEAD_INIT(lockname.held_list), \ + .name = #lockname, .file = __FILE__, .line = __LINE__ , \ + .magic = &lockname +extern void FASTCALL (__mutex_init (struct mutex *lock,char *name, +char *file,int line)); +# define mutex_init (sem) __mutex_init(sem, NULL, __FILE__, __LINE__) +#else +# define __MUTEX_DEADLOCK_DETECT_INITIALIZER (lockname) +extern void FASTCALL (__mutex_init (struct mutex *lock)); +# define mutex_init (sem) __mutex_init(sem) +#endif + +#define __MUTEX_INITIALIZER (lockname) \ + { .count = ATOMIC_INIT(1) \ + , .wait_lock = SPIN_LOCK_UNLOCKED \ + , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \ + __MUTEX_DEADLOCK_DETECT_INITIALIZER(lockname) } + +#define DEFINE_MUTEX (mutexname) \ + struct mutex mutexname = __MUTEX_INITIALIZER(mutexname) + +extern void FASTCALL (mutex_lock (struct mutex *lock)); +extern int FASTCALL (mutex_lock_interruptible (struct mutex *lock)); +extern int FASTCALL (mutex_trylock (struct mutex *lock)); +extern void FASTCALL (mutex_unlock (struct mutex *lock)); +extern int FASTCALL (mutex_is_locked (struct mutex *lock)); + +#endif
include/linux/preempt.h +1 -0
@@ -22,6 +22,7 @@#define dec_preempt_count () sub_preempt_count(1)#define preempt_count () (current_thread_info()->preempt_count) +#define preempt_count_ti (ti) ((ti)->preempt_count)#ifdef CONFIG_PREEMPT
include/linux/sched.h +5 -0
@@ -820,6 +820,11 @@ struct task_struct {/* Protection of proc_dentry: nesting proc_lock, dcache_lock, write_lock_irq(&tasklist_lock); */ spinlock_t proc_lock; +#ifdef CONFIG_DEBUG_MUTEXESS +/* mutex deadlock detection */ +struct mutex_waiter *blocked_on; +#endif +/* journalling filesystem info */ void *journal_info;
kernel/Makefile +1 -1
@@ -7,7 +7,7 @@ obj-y = sched.o fork.o exec_domain.o sysctl.o capability.o ptrace.o timer.o user.o \ signal.o sys.o kmod.o workqueue.o pid.o \ rcupdate.o intermodule.o extable.o params.o posix-timers.o \ - kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o + kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o obj-$(CONFIG_FUTEX) += futex.o obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o
kernel/fork.c +4 -0
@@ -973,6 +973,10 @@ static task_t *copy_process(unsigned lon }#endif +#ifdef CONFIG_DEBUG_MUTEXESS + p->blocked_on =NULL ;/* not blocked yet */ +#endif + p->tgid = p->pid;if (clone_flags &CLONE_THREAD ) p->tgid = current->tgid;
kernel/mutex.c new file +564 -0
564 lines not on this page · raw message
Steven Rostedt
reviewerQuoted 4 lines · Ingo Molnar
+ * These semantics are fully enforced when
DEBUG_MUTEXESSis
+ * enabled. Furthermore, besides enforcing the above rules, the mutex
+ * debugging code also implements a number of additional features
+ * that make lock debugging easier and faster:
Silly question: Is MUTEXESS a proper name, and what does it mean?
-- Steve
Steven Rostedt
reviewerQuoted 7 lines · Ingo Molnar
+/*
+ * We can speed up the lock-acquire, if the architecture
+ * supports cmpxchg and if there's no debugging state
+ * to be set up (!DEBUG_MUTEXESS).
+ *
+ * trick: we can use cmpxchg on the release side too, if bit
+ * 0 oflock->owneris set if there is at least a single pending
I think you mean lock->count and not lock->owner (you're getting your patches mixed up ;)
kernel/mutex.c line 36
Quoted 7 lines
+ * task in the
wait_list. This way the release atomic-fastpath
+ * can be a mirror image of the acquire path:
+ */kernel/mutex.c new file +4
-0+#if defined(__HAVE_ARCH_CMPXCHG) && !defined(CONFIG_DEBUG_MUTEXESS ) +# define MUTEX_LOCKLESS_FASTPATH +#endif +
-- Steve
Steven Rostedt
reviewerkernel/mutex.c line 57
Quoted 4 lines · Ingo Molnar
kernel/mutex.c new file +4
-0+#ifdef CONFIG_DEBUG_MUTEXESS +# include "mutex-debug.c" +#else /* !CONFIG_DEBUG_MUTEXESS */ +
Note: patch 05 is dependent on patch 06. Well, if you want debugging.
-- Steve
Steven Rostedt
reviewerQuoted 12 lines · Ingo Molnar
kernel/mutex.c new file +12
-0+static inline void +__mutex_wakeup_waiter(struct mutex *lock __IP_DECL__) +{ +struct mutex_waiter *waiter; + +SMP_DEBUG_WARN_ON (!spin_is_locked (&lock->wait_lock)); +DEBUG_WARN_ON (list_empty (&lock->wait_list)); + +/* +* Get the first entry from the wait-list: +*/ + waiter = list_entry(lock->wait_list.next, struct mutex_waiter,
Quoted 2 lines
list);
+
Any thought about adding priorities to the queue here? Maybe another time we can add the plist? But maybe I'm getting ahead of myself.
-- Steve
Steven Rostedt
reviewerkernel/mutex.c line 187
Quoted 11 lines · Ingo Molnar
kernel/mutex.c new file +4
-0+static inline int +__mutex_lock_common (struct mutex *lock,struct mutex_waiter *waiter, +struct thread_info *ti,struct task_struct *task, + unsigned long *flags, unsigned long task_state
__IP_DECL__)kernel/mutex.c new file +6
-0+{ +unsigned int old_val; + +debug_lock_irqsave (&debug_lock, *flags, ti); +DEBUG_WARN_ON (lock->magic != lock); +
How expensive is the xchg? Since __mutex_lock_common is called even when it's going to wake up. Maybe it might be more efficient to add something like:
if (atomic_cmpxchg(&lock->count, 1, 0) {
debug_set_owner(lock, ti __IP__);
debug_unlock_irqrestore(&debug_lock, *flags, ti);
return 1;
}This way we save the overhead of grabbing another spinlock, adding the task to the wait_list and changing it's state.
Quoted 5 lines
kernel/mutex.c new file +5
-0+spin_lock (&lock->wait_lock); + __add_waiter(lock, waiter, ti, task __IP__); +set_task_state (task, task_state); + +/*
kernel/mutex.c line 202
Quoted 10 lines
kernel/mutex.c new file +10
-0+* Lets try to take the lock again - this is needed even if +* we get here for the first time (shortly after failing to +* acquire the lock), to make sure that we get a wakeup once +* it's unlocked. Later on this is the operation that gives +* us the lock. We need to xchg it to -1, so that when we +* release the lock, we properly wake up other waiters! +*/ + old_val =atomic_xchg (&lock->count,-1 ); + +if (old_val ==1 ) {
-- Steve
Quoted 16 lines · Steven Rostedt
kernel/mutex.c line 187
Quoted 11 lines · Ingo Molnar
kernel/mutex.c new file +4
-0+static inline int +__mutex_lock_common (struct mutex *lock,struct mutex_waiter *waiter, +struct thread_info *ti,struct task_struct *task, + unsigned long *flags, unsigned long task_state
__IP_DECL__)kernel/mutex.c new file +6
-0+{ +unsigned int old_val; + +debug_lock_irqsave (&debug_lock, *flags, ti); +DEBUG_WARN_ON (lock->magic != lock); +How expensive is the xchg? Since
__mutex_lock_commonis called even when it's going to wake up. Maybe it might be more efficient to add something like:
But add:
#ifdef MUTEX_LOCKLESS_FASTPATH
Quoted 6 lines
if (atomic_cmpxchg(&lock->count, 1, 0) {
debug_set_owner(lock, ti __IP__);
debug_unlock_irqrestore(&debug_lock, *flags, ti);
return 1;
}#endif
-- Steve
Quoted 29 lines
This way we save the overhead of grabbing another spinlock, adding the task to the
wait_listand changing it's state.Quoted 5 lines
kernel/mutex.c new file +5
-0+spin_lock (&lock->wait_lock); + __add_waiter(lock, waiter, ti, task __IP__); +set_task_state (task, task_state); + +/* kernel/mutex.c line 202
Quoted 10 lines
kernel/mutex.c new file +10
-0+* Lets try to take the lock again - this is needed even if +* we get here for the first time (shortly after failing to +* acquire the lock), to make sure that we get a wakeup once +* it's unlocked. Later on this is the operation that gives +* us the lock. We need to xchg it to -1, so that when we +* release the lock, we properly wake up other waiters! +*/ + old_val =atomic_xchg (&lock->count,-1 ); + +if (old_val ==1 ) {-- Steve
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Signature · 4 lines
Steven Rostedt Senior Programmer Kihon Technologies (607)786-4830
Quoted 4 lines · Steven Rostedt
How expensive is the xchg? Since
__mutex_lock_commonis called even when it's going to wake up. Maybe it might be more efficient to add something like:
Quoted 8 lines
if (atomic_cmpxchg(&lock->count, 1, 0) { debug_set_owner(lock, ti __IP__); debug_unlock_irqrestore(&debug_lock, *flags, ti); return 1; }This way we save the overhead of grabbing another spinlock, adding the task to the
wait_listand changing it's state.
in the first pass we definitely need to add ourselves to the list first
- hence have to grab the lock. Even after the schedule(), we have to xchg it to -1, not 0. This is crutial to 'not drop the ball' property of one-waiter-in-flight logic - we must not lose the -1 'there are more waiters pending' property. Plus, we have the grab the lock because we remove ourselves from the wait-list after the schedule(). So i'm not sure your suggested optimization is possible.
Ingo
Steven Rostedt
reviewerQuoted 9 lines · Ingo Molnar
+static inline void
__mutex_unlock_nonatomic(struct mutex *lock __IP_DECL__)kernel/mutex.c new file +7
-0+{ +struct thread_info *ti =current_thread_info (); +unsigned long flags; + +debug_lock_irqsave (&debug_lock, flags, ti); +spin_lock (&lock->wait_lock); +
kernel/mutex.c line 433
Quoted 10 lines
kernel/mutex.c new file +10
-0+#ifdef CONFIG_DEBUG_MUTEXESS +DEBUG_WARN_ON (lock->magic != lock); +DEBUG_WARN_ON (!lock->wait_list.prev && !lock->wait_list.next); +DEBUG_WARN_ON (lock->owner != ti); +if (debug_on) { +DEBUG_WARN_ON (list_empty (&lock->held_list)); +list_del_init (&lock->held_list); + } +#endif +
The unlikely below is only for the non MUTEX_LOCKLESS_FASTPATH case. Maybe have a define for the unlikely?
#ifdef MUTEX_LOCKLESS_FASTPATH
# define UNLIKELY_SLOW(x) x
#else
# define UNLIKELY_SLOW(x) unlikely(x)
#endif
-- Steve
Quoted 5 lines
kernel/mutex.c new file +5
-0+if (unlikely (!list_empty (&lock->wait_list))) + __mutex_wakeup_waiter(lock __IP__); +#ifdef CONFIG_DEBUG_MUTEXESS + lock->owner =NULL ; +#endif
Quoted 11 lines · Steven Rostedt
The unlikely below is only for the non
MUTEX_LOCKLESS_FASTPATHcase. Maybe have a define for the unlikely?#ifdef
MUTEX_LOCKLESS_FASTPATH
# defineUNLIKELY_SLOW(x)x
#else
# defineUNLIKELY_SLOW(x)unlikely(x)
#endifQuoted 2 lines
(quoted diff) +2
-0+ if (unlikely(!list_empty(&lock->wait_list))) + __mutex_wakeup_waiter(lock __IP__);
i'll rather eliminate the unlikely().
Ingo
Quoted 7 lines · Ingo Molnar
mutex implementation, core files: just the basic subsystem, no users of it.
Signed-off-by Ingo Molnar <[email protected]>----
Quoted 9 lines
include/linux/mutex.h | 102 ++++++++ include/linux/preempt.h | 1 include/linux/sched.h | 5 kernel/Makefile | 2 kernel/fork.c | 4 kernel/mutex.c | 564 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 677 insertions(+), 1 deletion(-)
OK, I went through patches 1-5 with a fine tooth comb. Time for bed.
I'll do the rest tomorrow (oh wait, damn that's today!)
Patches 1-5:
-- Steve
Quoted 2 lines · Ingo Molnar
mutex implementation, core files: just the basic subsystem, no users of it.
Ingo, could you explain to me ...
kernel/mutex.c line 190
Quoted 12 lines
kernel/mutex.c new file +12
-0+__mutex_lock_common(struct mutex *lock, struct mutex_waiter *waiter, + struct thread_info *ti, struct task_struct *task, + unsigned long *flags, unsigned long task_state __IP_DECL__) +{ +unsigned int old_val; + +debug_lock_irqsave (&debug_lock, *flags, ti); +DEBUG_WARN_ON (lock->magic != lock); + +spin_lock (&lock->wait_lock); + __add_waiter(lock, waiter, ti, task __IP__); +set_task_state (task, task_state);
I can't understand why __mutex_lock_common() does xchg() after adding the waiter to the ->wait_list. We are holding ->wait_lock, we can't race with __mutex_unlock_nonatomic() - it calls wake_up() and sets ->count under this spinlock.
So, I think it can be simplified:
int __mutex_lock_common(lock, waiter)
{
lock(&lock->wait_lock);
ret = 1;
if (xchg(&lock->count, -1) == 1)
goto out;
__add_waiter(lock, waiter);
task->state = state;
ret = 0;
out:
unlock(&lock->wait_lock);
return ret;
}No?
kernel/mutex.c line 275
Quoted 8 lines
kernel/mutex.c new file +7
-0+__mutex_wakeup_waiter(struct mutex *lock __IP_DECL__) +{ +struct mutex_waiter *waiter; +if (!waiter->woken) { + waiter->woken =1 ; +wake_up_process (waiter->ti->task); + }
Is it optimization? If yes - why? From mutex.h:
- only one task can hold the mutex at a time
- only the owner can unlock the mutex
So, how can this help?
kernel/mutex.c line 297
Quoted 10 lines
kernel/mutex.c new file +10
-0+start_mutex_timer(struct timer_list *timer, unsigned long time, + unsigned long *expire) +{ + *expire = time + jiffies; +init_timer (timer); + timer->expires = *expire; + timer->data = (unsigned long )current; + timer->function = process_timeout; +add_timer (timer); +}
How about
setup_timer(&timer, process_timeout, (unsigned long)current); __mod_timer(&timer, *expire);
?
kernel/mutex.c line 309
Quoted 10 lines
kernel/mutex.c new file +10
-0+stop_mutex_timer(struct timer_list *timer, unsigned long time, + unsigned long expire) +{ + int ret; + + ret = (int )(expire - jiffies); +if (!timer_pending (timer)) { +del_singleshot_timer_sync (timer); + ret = -ETIMEDOUT ; + }
Did you mean
if (!timer_pending(timer)) ret = -ETIMEDOUT; del_singleshot_timer_sync(timer);
?
kernel/mutex.c line 329
Quoted 13 lines
kernel/mutex.c new file +13
-0+__mutex_lock_interruptible(struct mutex *lock, unsigned long time __IP_DECL__) +{ + struct thread_info *ti = current_thread_info(); +struct task_struct *task = ti->task; +unsigned long expire =0 , flags; +struct mutex_waiter waiter; +struct timer_list timer; +int ret; + +repeat: +if (__mutex_lock_common (lock, &waiter, ti, task, &flags, + TASK_INTERRUPTIBLE __IP__)) +return 0 ;
I think this is wrong. We may have pending timer here if we were woken by signal.
Oleg.
Quoted 8 lines · Oleg Nesterov
kernel/mutex.c line 190
Quoted 3 lines
kernel/mutex.c new file +3
-0+spin_lock (&lock->wait_lock); + __add_waiter(lock, waiter, ti, task __IP__); +set_task_state (task, task_state);I can't understand why
__mutex_lock_common()doesxchg()after adding the waiter to the ->wait_list. We are holding ->wait_lock, we can't race with__mutex_unlock_nonatomic()- it callswake_up()and sets ->count under this spinlock.
we must make sure that the drop has not been dropped meanwhile, on the way in, between the fastpath-unlock atomic op, and the xchg() here.
Ingo
Quoted 7 lines · Ingo Molnar
Quoted 4 lines · Oleg Nesterov
Quoted 3 lines
kernel/mutex.c new file +3
-0+spin_lock (&lock->wait_lock); + __add_waiter(lock, waiter, ti, task __IP__); +set_task_state (task, task_state);
Quoted 7 lines
Quoted 4 lines
I can't understand why
__mutex_lock_common()doesxchg()after adding the waiter to the ->wait_list. We are holding ->wait_lock, we can't race with__mutex_unlock_nonatomic()- it callswake_up()and sets ->count under this spinlock.we must make sure that the drop has not been dropped meanwhile, on the way in, between the fastpath-unlock atomic op, and the
xchg()here.
Sorry for noise, probably I should just re-read your explanation tomorrow after some sleeping...
But why we can't add the waiter to ->wait_list _after_ xchg() ? What makes the difference? Fastpath atomic op can happen before or after xchg(), this is ok. Unlock path will look at ->wait_list only after taking spinlock, at this time we already added this waiter to the ->wait_list.
In other words: we are holding ->wait_lock, nobody can even look at ->wait_list. We can add the waiter to ->wait_list before or after atomic_xchg() - it does not matter.
Again no?
Oleg.
Quoted 9 lines · Oleg Nesterov
But why we can't add the waiter to ->wait_list _after_
xchg()? What makes the difference? Fastpath atomic op can happen before or afterxchg(), this is ok. Unlock path will look at ->wait_list only after taking spinlock, at this time we already added this waiter to the ->wait_list.In other words: we are holding ->wait_lock, nobody can even look at ->wait_list. We can add the waiter to ->wait_list before or after
atomic_xchg()- it does not matter.
hm, you are right - i've added this optimization.
Ingo
Quoted 11 lines · Oleg Nesterov
kernel/mutex.c line 275
Quoted 8 lines
kernel/mutex.c new file +7
-0+__mutex_wakeup_waiter(struct mutex *lock __IP_DECL__) +{ +struct mutex_waiter *waiter; +if (!waiter->woken) { + waiter->woken =1 ; +wake_up_process (waiter->ti->task); + }Is it optimization? If yes - why? From
mutex.h:
Quoted 4 lines
- only one task can hold the mutex at a time
- only the owner can unlock the mutexSo, how can this help?
yes, it's an optimization. I've removed it from the latest queue because it didnt trigger all that often, but the optimization is valid: while we have a 'waiter in flight', another (fast) task might grab the mutex, and might release it - in which case it could attempt to wake up the waiter again - which this flag optimizes.
Ingo
Quoted 12 lines · Oleg Nesterov
kernel/mutex.c line 297
Quoted 10 lines
kernel/mutex.c new file +10
-0+start_mutex_timer(struct timer_list *timer, unsigned long time, + unsigned long *expire) +{ + *expire = time + jiffies; +init_timer (timer); + timer->expires = *expire; + timer->data = (unsigned long )current; + timer->function = process_timeout; +add_timer (timer); +}How about
Quoted 3 lines
setup_timer(&timer, process_timeout, (unsigned long)current); __mod_timer(&timer, *expire);?
i've removed the timer code from the latest queue - because it's unused and we can add it back later. But you are right, it was racy (Arjan noticed the signal race too), because this codepath in -rt relied on guaranteed lock-passing. (while mutex.c does a trylock)
Ingo