rt: Port of Lai Jiangshan's rt patch
Thomas,
I finally got Lai's patch to a point that I'm happy with it. The first 4 patches are clean ups to help in applying Lai's patch at the end.
I've tested this patch series by running dbench 100 in a loop and it crashes as much as mainline 2.6.33 does ;-) so I'm pretty happy with it.
The following patches are in:
git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-2.6-rt.git
branch: rt/tip/rt/devel
Lai Jiangshan (1):
rtmutex: Ensure only the top waiter or higher priority task can take the lock
Steven Rostedt (4):
rtmutex: Only save lock depth once in spin_slowlock
rtmutex: Try to take lock early in rt_spin_lock_slowlock()
rtmutex: Revert Optimize rt lock wakeup
rtmutex: Fix comment about why new_owner can be NULL in wake_futex_pi()
----
kernel/futex.c | 20 +- kernel/rt.c | 10 +- kernel/rtmutex-debug.c | 1 - kernel/rtmutex.c | 478 ++++++++++++++++++----------------------------- kernel/rtmutex_common.h | 18 +-- 5 files changed, 204 insertions(+), 323 deletions(-)
PATCH 1/5 rtmutex: Only save lock depth once in spin_slowlock
From: Steven Rostedt <[email protected]>
The task that enters the rt_spin_lock_slowlock() will not take or release the kernel lock again until it leaves the fuction. There's no reason that we need to save and restore the lock depth for each iteration of the try_lock loop.
Just save and reset the lock depth before entering the loop and restore it upon exit.
--- kernel/rtmutex.c | 16 +++++++++------- 1 files changed, 9 insertions(+), 7 deletions(-)
kernel/rtmutex.c +9 -7
@@ -821,6 +821,7 @@ rt_spin_lock_slowlock(struct rt_mutex *lock)struct rt_mutex_waiter waiter;unsigned long saved_state, flags;struct task_struct *orig_owner; +int saved_lock_depth;debug_rt_mutex_init_waiter (&waiter); waiter.task =NULL ;
@@ -843,8 +844,14 @@ rt_spin_lock_slowlock(struct rt_mutex *lock)*/ saved_state =rt_set_current_blocked_state (current->state); +/* +* Prevent schedule() to drop BKL, while waiting for +* the lock ! We restore lock_depth when we come back. +*/ + saved_lock_depth = current->lock_depth; + current->lock_depth =-1 ; +for (;;) { -int saved_lock_depth = current->lock_depth;/* Try to acquire the lock */ if (do_try_to_take_rt_mutex (lock,STEAL_LATERAL ))
@@ -863,11 +870,6 @@ rt_spin_lock_slowlock(struct rt_mutex *lock)continue ; } -/* -* Prevent schedule() to drop BKL, while waiting for -* the lock ! We restore lock_depth when we come back. -*/ - current->lock_depth =-1 ; orig_owner =rt_mutex_owner (lock);get_task_struct (orig_owner);raw_spin_unlock_irqrestore (&lock->wait_lock, flags);
@@ -883,9 +885,9 @@ rt_spin_lock_slowlock(struct rt_mutex *lock)put_task_struct (orig_owner);raw_spin_lock_irqsave (&lock->wait_lock, flags); - current->lock_depth = saved_lock_depth; saved_state =rt_set_current_blocked_state (saved_state); } + current->lock_depth = saved_lock_depth;rt_restore_current_state (saved_state);
Signature · 1 line
1.7.2.3
PATCH 2/5 rtmutex: Try to take lock early in rt_spin_lock_slowlock()
In reply to Steven RostedtFrom: Steven Rostedt <[email protected]>
Try to take the lock again as soon as we go into the rt_spin_lock_slowlock() code before doing the setup and wait loop. This makes the code closer to what the rt_mutex_slowlock() does, which will help in simplifying this code in later commits.
--- kernel/rtmutex.c | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-)
kernel/rtmutex.c +5 -0
@@ -829,6 +829,11 @@ rt_spin_lock_slowlock(struct rt_mutex *lock)raw_spin_lock_irqsave (&lock->wait_lock, flags);init_lists (lock); +if (do_try_to_take_rt_mutex (lock,STEAL_LATERAL )) { +raw_spin_unlock_irqrestore (&lock->wait_lock, flags); +return ; + } +BUG_ON (rt_mutex_owner (lock) == current);/*
Signature · 1 line
1.7.2.3
PATCH 3/5 rtmutex: Revert Optimize rt lock wakeup
In reply to Steven RostedtFrom: Steven Rostedt <[email protected]>
The commit: rtmutex: Optimize rt lock wakeup
Does not do what it was suppose to do.
This is because the adaptive waiter sets its state to TASK_(UN)INTERRUPTIBLE before going into the loop. Thus, the test in wakeup_next_waiter() will always fail on an adaptive waiter, as it only tests to see if the pending waiter never has its state set ot TASK_RUNNING unless something else had woke it up.
The smp_mb() added to make this test work is just as expensive as just calling wakeup. And since we we fail to wake up anyway, we are doing both a smp_mb() and wakeup as well.
I tested this with dbench and we run faster without this patch. I also tried a variant that instead fixed the loop, to change the state only if the spinner was to go to sleep, and that still did not show any improvement.
--- kernel/rtmutex.c | 29 ++--------------------------- 1 files changed, 2 insertions(+), 27 deletions(-)
kernel/rtmutex.c +2 -27
@@ -554,33 +554,8 @@ static void wakeup_next_waiter(struct rt_mutex *lock, int savestate)*/ if (!savestate)wake_up_process (pendowner); -else { -/* -* We can skip the actual (expensive) wakeup if the -* waiter is already running, but we have to be careful -* of race conditions because they may be about to sleep. -* -* The waiter-side protocol has the following pattern: -* 1: Set state != RUNNING -* 2: Conditionally sleep if waiter->task != NULL; -* -* And the owner-side has the following: -* A: Set waiter->task = NULL -* B: Conditionally wake if the state != RUNNING -* -* As long as we ensure 1->2 order, and A->B order, we -* will never miss a wakeup. -* -* Therefore, this barrier ensures that waiter->task = NULL -* is visible before we test the pendowner->state. The -* corresponding barrier is in the sleep logic. -*/ -smp_mb (); - -/* If !RUNNING && !RUNNING_MUTEX */ -if (pendowner->state & ~TASK_RUNNING_MUTEX ) -wake_up_process_mutex (pendowner); - } +else +wake_up_process_mutex (pendowner);rt_mutex_set_owner (lock, pendowner,RT_MUTEX_OWNER_PENDING );
Signature · 1 line
1.7.2.3
PATCH 4/5 rtmutex: Fix comment about why new_owner can be NULL in wake_futex_pi()
In reply to Steven RostedtFrom: Steven Rostedt <[email protected]>
The comment about why rt_mutex_next_owner() can return NULL in wake_futex_pi() is not the normal case.
Tracing the cause of why this occurs is more likely that waiter simply timedout. But because it originally caused contention with the futex, the owner will go into the kernel when it unlocks the lock. Then it will hit this code path and rt_mutex_next_owner() will return NULL.
--- kernel/futex.c | 7 +++---- 1 files changed, 3 insertions(+), 4 deletions(-)
kernel/futex.c +3 -4
@@ -777,10 +777,9 @@ static int wake_futex_pi(u32 __user *uaddr, u32 uval, struct futex_q *this) new_owner =rt_mutex_next_owner (&pi_state->pi_mutex);/* -* This happens when we have stolen the lock and the original -* pending owner did not enqueue itself back on the rt_mutex. -* Thats not a tragedy. We know that way, that a lock waiter -* is on the fly. We make the futex_q waiter the pending owner. +* It is possible that the next waiter (the one that brought +* this owner to the kernel) timed out and is no longer +* waiting on the lock. */ if (!new_owner) new_owner = this->task;
Signature · 1 line
1.7.2.3
PATCH 5/5 rtmutex: Ensure only the top waiter or higher priority task can take the lock
In reply to Steven RostedtFrom: Lai Jiangshan <[email protected]>
In current rtmutex, the pending owner may be boosted by the tasks in the rtmutex's waitlist when the pending owner is deboosted or a task in the waitlist is boosted. This boosting is unrelated, because the pending owner does not really take the rtmutex. It is not reasonable.
Example.
time1:
A(high prio) onwers the rtmutex.
B(mid prio) and C (low prio) in the waitlist.
time2
A release the lock, B becomes the pending owner
A(or other high prio task) continues to run. B's prio is lower than A, so B is just queued at the runqueue.
time3
A or other high prio task sleeps, but we have passed some time The B and C's prio are changed in the period (time2 ~ time3) due to boosting or deboosting. Now C has the priority higher than B. ***Is it reasonable that C has to boost B and help B to get the rtmutex?
NO!! I think, it is unrelated/unneed boosting before B really owns the rtmutex. We should give C a chance to beat B and win the rtmutex.
This is the motivation of this patch. This patch *ensures* only the top waiter or higher priority task can take the lock.
How?
1) we don't dequeue the top waiter when unlock, if the top waiter is changed, the old top waiter will fail and go to sleep again.
2) when requiring lock, it will get the lock when the lock is not taken and:
there is no waiter OR higher priority than waiters OR it is top waiter.
3) In any time, the top waiter is changed, the top waiter will be woken up.
The algorithm is much simpler than before, no pending owner, no boosting for pending owner.
Other advantage of this patch:
1) The states of a rtmutex are reduced a half, easier to read the code.
2) the codes become shorter.
3) top waiter is not dequeued until it really take the lock:
they will retain FIFO when it is stolen.
Not advantage nor disadvantage
1) Even we may wakeup multiple waiters(any time when top waiter changed), we hardly cause "thundering herd",
the number of wokenup task is likely 1 or very little.
2) two APIs are changed.
rt_mutex_owner() will not return pending owner, it will return NULL when
the top waiter is going to take the lock.
rt_mutex_next_owner() always return the top waiter.
will not return NULL if we have waiters because the top waiter is not dequeued.
I have fixed the code that use these APIs.
need updated after this patch is accepted
1) Document/*
2) the testcase scripts/rt-tester/t4-l2-pi-deboost.tst
[ Ported to the -rt patch set by Steven Rostedt ]
LKML-Reference: <[email protected]>
--- kernel/futex.c | 13 +- kernel/rt.c | 10 +- kernel/rtmutex-debug.c | 1 - kernel/rtmutex.c | 434 +++++++++++++++++++---------------------------- kernel/rtmutex_common.h | 18 +-- 5 files changed, 188 insertions(+), 288 deletions(-)
kernel/futex.c +7 -6
@@ -774,6 +774,7 @@ static int wake_futex_pi(u32 __user *uaddr, u32 uval, struct futex_q *this)return -EINVAL ;raw_spin_lock (&pi_state->pi_mutex.wait_lock); +/* set new owner to the highest priority waiter (top waiter). */ new_owner =rt_mutex_next_owner (&pi_state->pi_mutex);/*
@@ -1514,10 +1515,10 @@ static int fixup_pi_state_owner(u32 __user *uaddr, struct futex_q *q,/* * We are here either because we stole the rtmutex from the -* pending owner or we are the pending owner which failed to -* get the rtmutex. We have to replace the pending owner TID -* in the user space variable. This must be atomic as we have -* to preserve the owner died bit here. +* previous highest priority waiter or we are the highest priority +* waiter but failed to get the rtmutex the first time. +* We have to replace the newowner TID in the user space variable. +* This must be atomic as we have to preserve the owner died bit here. * * Note: We write the user space value _before_ changing the pi_state * because we can fault here. Imagine swapped out pages or a fork
@@ -1566,8 +1567,8 @@ retry:/* * To handle the page fault we need to drop the hash bucket -* lock here. That gives the other task (either the pending -* owner itself or the task which stole the rtmutex) the +* lock here. That gives the other task (either the highest priority +* waiter itself or the task which stole the rtmutex) the * chance to try the fixup of the pi_state. So once we are * back from handling the fault we need to check the pi_state * after reacquiring the hash bucket lock and before trying to
kernel/rt.c +5 -5
@@ -208,7 +208,7 @@ int __lockfunc rt_read_trylock(rwlock_t *rwlock)* but not when read_depth == 0 which means that the lock is * write locked. */ -if (rt_mutex_real_owner (lock) != current) +if (rt_mutex_owner (lock) != current) ret =rt_mutex_trylock (lock);else if (!rwlock->read_depth) ret =0 ;
@@ -238,7 +238,7 @@ void __lockfunc rt_read_lock(rwlock_t *rwlock)/* * recursive read locks succeed when current owns the lock */ -if (rt_mutex_real_owner (lock) != current) +if (rt_mutex_owner (lock) != current)__rt_spin_lock (lock); rwlock->read_depth++; }
@@ -318,7 +318,7 @@ EXPORT_SYMBOL(rt_up_read);*/ void rt_downgrade_write (struct rw_semaphore *rwsem) { -BUG_ON (rt_mutex_real_owner (&rwsem->lock) != current); +BUG_ON (rt_mutex_owner (&rwsem->lock) != current); rwsem->read_depth =1 ; }EXPORT_SYMBOL (rt_downgrade_write);
@@ -357,7 +357,7 @@ int rt_down_read_trylock(struct rw_semaphore *rwsem)* but not when read_depth == 0 which means that the rwsem is * write locked. */ -if (rt_mutex_real_owner (lock) != current) +if (rt_mutex_owner (lock) != current) ret =rt_mutex_trylock (&rwsem->lock);else if (!rwsem->read_depth) ret =0 ;
@@ -376,7 +376,7 @@ static void __rt_down_read(struct rw_semaphore *rwsem, int subclass)rwsem_acquire_read (&rwsem->dep_map, subclass,0 , _RET_IP_); -if (rt_mutex_real_owner (lock) != current) +if (rt_mutex_owner (lock) != current)rt_mutex_lock (&rwsem->lock); rwsem->read_depth++; }
kernel/rtmutex-debug.c +0 -1
@@ -161,7 +161,6 @@ void debug_rt_mutex_free_waiter(struct rt_mutex_waiter *waiter)put_pid (waiter->deadlock_task_pid);DEBUG_LOCKS_WARN_ON (!plist_node_empty (&waiter->list_entry));DEBUG_LOCKS_WARN_ON (!plist_node_empty (&waiter->pi_list_entry)); -DEBUG_LOCKS_WARN_ON (waiter->task);memset (waiter,0x22 ,sizeof (*waiter)); }
kernel/rtmutex.c +172 -262
699 lines not on this page · raw message
kernel/rtmutex_common.h +4 -14
@@ -43,6 +43,7 @@ extern void schedule_rt_mutex_test(struct rt_mutex *lock);* @list_entry: pi node to enqueue into the mutex waiters list * @pi_list_entry: pi node to enqueue into the mutex owner waiters list * @task: task reference to the blocked task +* @savestate: should the task be woken with wake_up_process_mutex? */ struct rt_mutex_waiter {struct plist_node list_entry;
@@ -54,6 +55,7 @@ struct rt_mutex_waiter {struct pid *deadlock_task_pid;struct rt_mutex *deadlock_lock;#endif +int savestate; };/*
@@ -91,9 +93,8 @@ task_top_pi_waiter(struct task_struct *p)/* * lock->owner state tracking: */ -#define RT_MUTEX_OWNER_PENDING 1UL -#define RT_MUTEX_HAS_WAITERS 2UL -#define RT_MUTEX_OWNER_MASKALL 3UL +#define RT_MUTEX_HAS_WAITERS 1UL +#define RT_MUTEX_OWNER_MASKALL 1ULstatic inline struct task_struct *rt_mutex_owner (struct rt_mutex *lock) {
@@ -101,17 +102,6 @@ static inline struct task_struct *rt_mutex_owner(struct rt_mutex *lock) ((unsigned long )lock->owner & ~RT_MUTEX_OWNER_MASKALL ); } -static inline struct task_struct *rt_mutex_real_owner (struct rt_mutex *lock) -{ -return (struct task_struct *) - ((unsigned long )lock->owner & ~RT_MUTEX_HAS_WAITERS ); -} - -static inline unsigned long rt_mutex_owner_pending (struct rt_mutex *lock) -{ -return (unsigned long )lock->owner &RT_MUTEX_OWNER_PENDING ; -} -/* * PI-futex support (proxy locking functions, etc.): */
Signature · 1 line
1.7.2.3
Quoted 9 lines · Steven Rostedt
Thomas,
I finally got Lai's patch to a point that I'm happy with it. The first 4 patches are clean ups to help in applying Lai's patch at the end.
I've tested this patch series by running dbench 100 in a loop and it crashes as much as mainline 2.6.33 does ;-) so I'm pretty happy with it.
Crashes in what way? I'm not so happy about stuff which crashes :)
Thanks,
tglx