Skip to contentSkip to conversation

Generic Mutex Subsystem, mutex-core.patch

PATCH In review Ingo Molnar 4 participants 17 messages Dec 19, 2005 – Dec 21, 2005

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

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
Index: linux/include/linux/mutex.h
===================================================================
@@ -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
Index: linux/include/linux/preempt.h
===================================================================
@@ -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
Index: linux/include/linux/sched.h
===================================================================
@@ -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
Index: linux/kernel/Makefile
===================================================================
@@ -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
Index: linux/kernel/fork.c
===================================================================
@@ -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

#1 lore raw
Quoted 4 lines · Ingo Molnar

+ * 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:

Silly question: Is MUTEXESS a proper name, and what does it mean?

-- Steve

#2 lore raw
In reply to Ingo Molnar
Quoted 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 of lock->owner is 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

#3 lore raw
In reply to Ingo Molnar

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

#4 lore raw
In reply to Ingo Molnar
Quoted 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

#5 lore raw
In reply to Ingo Molnar

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

#6 lore raw
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_common is 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_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

-
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
#7 lore raw
In reply to Steven Rostedt
Quoted 4 lines · Steven Rostedt

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:

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

#8 lore raw
In reply to Ingo Molnar
Quoted 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
#9 lore raw
Quoted 11 lines · Steven Rostedt

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

Quoted 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

reviewer Acked-by on v1 5/15
#10 lore raw
In reply to Ingo Molnar
Quoted 7 lines · Ingo Molnar

mutex implementation, core files: just the basic subsystem, no users of it.

----

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

#11 lore raw
In reply to Ingo Molnar
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.

#12 lore raw
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() 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.

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

#13 lore raw
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() 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.

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.

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

hm, you are right - i've added this optimization.

Ingo

#15 lore raw
In reply to Oleg Nesterov
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 mutex

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

#16 lore raw
In reply to Oleg Nesterov
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