cleanup and remove some extra spinlocks from rtmutex
PATCH 1/1 cleanup and remove some extra spinlocks from rtmutex
Oleg brought up some interesting points about grabbing the pi_lock for some protections. In this discussion, I realized that there are some places that the pi_lock is being grabbed when it really wasn't necessary. Also this patch does a little bit of clean up.
This patch basically does three things:
1) renames the "boost" variable to "chain_walk". Since it is used in the debugging case when it isn't going to be boosted. It better describes what the test is going to do if it succeeds.
2) moves get_task_struct to just before the unlocking of the wait_lock. This removes duplicate code, and makes it a little easier to read. The owner wont go away while either the pi_lock or the wait_lock are held.
3) removes the pi_locking and owner blocked checking completely from the debugging case. This is because the grabbing the lock and doing the check, then releasing the lock is just so full of races. It's just as good to go ahead and call the pi_chain_walk function, since after releasing the lock the owner can then block anyway, and we would have missed that. For the debug case, we really do want to do the chain walk to test for deadlocks anyway.
-- Steve
kernel/rtmutex.c +22 -23
@@ -409,7 +409,7 @@ static int task_blocks_on_rt_mutex(strucstruct task_struct *owner =rt_mutex_owner (lock);struct rt_mutex_waiter *top_waiter = waiter;unsigned long flags; -int boost =0 , res; +int chain_walk =0 , res;spin_lock_irqsave (¤t->pi_lock, flags);__rt_mutex_adjust_prio (current);
@@ -433,25 +433,23 @@ static int task_blocks_on_rt_mutex(strucplist_add (&waiter->pi_list_entry, &owner->pi_waiters);__rt_mutex_adjust_prio (owner); -if (owner->pi_blocked_on) { - boost =1 ; -/* gets dropped in rt_mutex_adjust_prio_chain()! */ -get_task_struct (owner); - } +if (owner->pi_blocked_on) + chain_walk =1 ;spin_unlock_irqrestore (&owner->pi_lock, flags); } -else if (debug_rt_mutex_detect_deadlock (waiter, detect_deadlock)) { -spin_lock_irqsave (&owner->pi_lock, flags); -if (owner->pi_blocked_on) { - boost =1 ; -/* gets dropped in rt_mutex_adjust_prio_chain()! */ -get_task_struct (owner); - } -spin_unlock_irqrestore (&owner->pi_lock, flags); - } -if (!boost) +else if (debug_rt_mutex_detect_deadlock (waiter, detect_deadlock)) + chain_walk =1 ; + +if (!chain_walk)return 0 ; +/* +* The owner can't disappear while holding a lock, +* so the owner struct is protected by wait_lock. +* Gets dropped in rt_mutex_adjust_prio_chain()! +*/ +get_task_struct (owner); +spin_unlock (&lock->wait_lock); res = rt_mutex_adjust_prio_chain(owner, detect_deadlock, lock, waiter,
@@ -532,7 +530,7 @@ static void remove_waiter(struct rt_muteint first = (waiter ==rt_mutex_top_waiter (lock));struct task_struct *owner =rt_mutex_owner (lock);unsigned long flags; -int boost =0 ; +int chain_walk =0 ;spin_lock_irqsave (¤t->pi_lock, flags);plist_del (&waiter->list_entry, &lock->wait_list);
@@ -554,19 +552,20 @@ static void remove_waiter(struct rt_mute }__rt_mutex_adjust_prio (owner); -if (owner->pi_blocked_on) { - boost =1 ; -/* gets dropped in rt_mutex_adjust_prio_chain()! */ -get_task_struct (owner); - } +if (owner->pi_blocked_on) + chain_walk =1 ; +spin_unlock_irqrestore (&owner->pi_lock, flags); }WARN_ON (!plist_node_empty (&waiter->pi_list_entry)); -if (!boost) +if (!chain_walk)return ; +/* gets dropped in rt_mutex_adjust_prio_chain()! */ +get_task_struct (owner); +spin_unlock (&lock->wait_lock);rt_mutex_adjust_prio_chain (owner,0 , lock,NULL , current);
PATCH 1/1 rtmutex-clean-up-and-remove-some-extra-spinlocks-more
On top of Steven's rtmutex-clean-up-and-remove-some-extra-spinlocks.patch
There are still 2 get_task_struct()s under ->pi_lock. Imho, this is confusing. Move them outside of ->pi_lock protected section. The task can't go away, otherwise it was unsafe to take task->pi_lock.
kernel/rtmutex.c renamed from kernel/rtmutex.c~ +3 -3
@@ -249,6 +249,7 @@ static int rt_mutex_adjust_prio_chain(st/* Grab the next task */ task =rt_mutex_owner (lock); +get_task_struct (task);spin_lock_irqsave (&task->pi_lock, flags);if (waiter ==rt_mutex_top_waiter (lock)) {
@@ -267,7 +268,6 @@ static int rt_mutex_adjust_prio_chain(st__rt_mutex_adjust_prio (task); } -get_task_struct (task);spin_unlock_irqrestore (&task->pi_lock, flags); top_waiter =rt_mutex_top_waiter (lock);
@@ -589,10 +589,10 @@ void rt_mutex_adjust_pi(struct task_strureturn ; } -/* gets dropped in rt_mutex_adjust_prio_chain()! */ -get_task_struct (task);spin_unlock_irqrestore (&task->pi_lock, flags); +/* gets dropped in rt_mutex_adjust_prio_chain()! */ +get_task_struct (task);rt_mutex_adjust_prio_chain (task,0 ,NULL ,NULL , task); }
Another question: why should we take ->pi_lock to modify rt_mutex's ->wait_list? It looks confusing and unneeded to me, because we already hold ->wait_lock. For example, wakeup_next_waiter() takes current's ->pi_lock before plist_del(), which seems to be completely offtopic, since current->pi_blocked_on has nothing to do with that rt_mutex.
Note also that ->pi_blocked_on is always modified while also holding ->pi_blocked_on->lock->wait_lock, and things like rt_mutex_top_waiter() need ->wait_lock too, so I don't think we need ->pi_lock for ->wait_list.
In other words, could you please explain to me whether the patch below correct or not?
Thanks,
Oleg.
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +11 -11
@@ -236,6 +236,10 @@ static int rt_mutex_adjust_prio_chain(st goto out_unlock_pi; } +/* Release the task */ +spin_unlock_irqrestore (&task->pi_lock, flags); +put_task_struct (task); + top_waiter =rt_mutex_top_waiter (lock);/* Requeue the waiter */
@@ -243,10 +247,6 @@ static int rt_mutex_adjust_prio_chain(st waiter->list_entry.prio = task->prio;plist_add (&waiter->list_entry, &lock->wait_list); -/* Release the task */ -spin_unlock_irqrestore (&task->pi_lock, flags); -put_task_struct (task); -/* Grab the next task */ task =rt_mutex_owner (lock);get_task_struct (task);
@@ -416,15 +416,15 @@ static int task_blocks_on_rt_mutex(strucplist_node_init (&waiter->list_entry, current->prio);plist_node_init (&waiter->pi_list_entry, current->prio); + current->pi_blocked_on = waiter; + +spin_unlock_irqrestore (¤t->pi_lock, flags); +/* Get the top priority waiter on the lock */ if (rt_mutex_has_waiters (lock)) top_waiter =rt_mutex_top_waiter (lock);plist_add (&waiter->list_entry, &lock->wait_list); - current->pi_blocked_on = waiter; - -spin_unlock_irqrestore (¤t->pi_lock, flags); -if (waiter ==rt_mutex_top_waiter (lock)) {spin_lock_irqsave (&owner->pi_lock, flags);plist_del (&top_waiter->pi_list_entry, &owner->pi_waiters);
@@ -472,11 +472,10 @@ static void wakeup_next_waiter(struct rtstruct task_struct *pendowner;unsigned long flags; -spin_lock_irqsave (¤t->pi_lock, flags); - waiter =rt_mutex_top_waiter (lock);plist_del (&waiter->list_entry, &lock->wait_list); +spin_lock_irqsave (¤t->pi_lock, flags);/* * Remove it from current->pi_waiters. We do not adjust a * possible priority boost right now. We execute wakeup in the
@@ -530,8 +529,9 @@ static void remove_waiter(struct rt_muteunsigned long flags;int chain_walk =0 ; -spin_lock_irqsave (¤t->pi_lock, flags);plist_del (&waiter->list_entry, &lock->wait_list); + +spin_lock_irqsave (¤t->pi_lock, flags); waiter->task =NULL ; current->pi_blocked_on =NULL ;spin_unlock_irqrestore (¤t->pi_lock, flags);
Quoted 11 lines · Oleg Nesterov
Another question: why should we take ->pi_lock to modify rt_mutex's ->wait_list?
It looks confusing and unneeded to me, because we already hold ->wait_lock. For example,wakeup_next_waiter()takes current's ->pi_lock beforeplist_del(), which seems to be completely offtopic, sincecurrent->pi_blocked_onhas nothing to do with thatrt_mutex.Note also that ->pi_blocked_on is always modified while also holding ->pi_blocked_on->lock->wait_lock, and things like
rt_mutex_top_waiter()need ->wait_lock too, so I don't think we need ->pi_lock for ->wait_list.
Yes, that was the basic design:
lock->wait_list and related waiter->list_entry is protected by lock->wait_lock, while task->pi_waiters and related waiter->pi_list_entry.
Quoted 3 lines
In other words, could you please explain to me whether the patch below correct or not?
Well, we are talking about small optimizations now, moving only a few instructions outside the lock. Except for one of them it is correct, but it is worth risking stability for now?
Quoted 7 lines
Thanks,
Oleg.
kernel/rtmutex.c static int rt_mutex_adjust_prio_chain(st · line 242
Quoted 7 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +4
-0@@ -236,6 +236,10 @@ static int rt_mutex_adjust_prio_chain(st goto out_unlock_pi; } +/* Release the task */ +spin_unlock_irqrestore (&task->pi_lock, flags); +put_task_struct (task); +
So you want the task to go away here and use it below?
kernel/rtmutex.c static int rt_mutex_adjust_prio_chain(st · line 249
Quoted 11 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-4top_waiter =rt_mutex_top_waiter (lock);/* Requeue the waiter */ @@ -243,10 +247,6 @@ static int rt_mutex_adjust_prio_chain(st waiter->list_entry.prio = task->prio;plist_add (&waiter->list_entry, &lock->wait_list); -/* Release the task */ -spin_unlock_irqrestore (&task->pi_lock, flags); -put_task_struct (task); -
No! It is used in the line just above, so we better be sure it still exists!
Quoted 10 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +3
-0/* Grab the next task */ task =rt_mutex_owner (lock);get_task_struct (task);@@ -416,15 +416,15 @@ static int task_blocks_on_rt_mutex(strucplist_node_init (&waiter->list_entry, current->prio);plist_node_init (&waiter->pi_list_entry, current->prio); + current->pi_blocked_on = waiter; + +spin_unlock_irqrestore (¤t->pi_lock, flags);
kernel/rtmutex.c static int task_blocks_on_rt_mutex(struc · line 427
Quoted 10 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +1
-4+/* Get the top priority waiter on the lock */ if (rt_mutex_has_waiters (lock)) top_waiter =rt_mutex_top_waiter (lock);plist_add (&waiter->list_entry, &lock->wait_list); - current->pi_blocked_on = waiter; - -spin_unlock_irqrestore (¤t->pi_lock, flags); -
Ok, this change might work out.
kernel/rtmutex.c static void wakeup_next_waiter(struct rt · line 478
Quoted 13 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +1
-2if (waiter ==rt_mutex_top_waiter (lock)) {spin_lock_irqsave (&owner->pi_lock, flags);plist_del (&top_waiter->pi_list_entry, &owner->pi_waiters);@@ -472,11 +472,10 @@ static void wakeup_next_waiter(struct rtstruct task_struct *pendowner;unsigned long flags; -spin_lock_irqsave (¤t->pi_lock, flags); - waiter =rt_mutex_top_waiter (lock);plist_del (&waiter->list_entry, &lock->wait_list); +spin_lock_irqsave (¤t->pi_lock, flags);
This might be ok, too...
Quoted 5 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-0/* * Remove it from current->pi_waiters. We do not adjust a * possible priority boost right now. We execute wakeup in the
kernel/rtmutex.c static void remove_waiter(struct rt_mute · line 537
Quoted 10 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +2
-1@@ -530,8 +529,9 @@ static void remove_waiter(struct rt_muteunsigned long flags;int chain_walk =0 ; -spin_lock_irqsave (¤t->pi_lock, flags);plist_del (&waiter->list_entry, &lock->wait_list); + +spin_lock_irqsave (¤t->pi_lock, flags); waiter->task =NULL ; current->pi_blocked_on =NULL ;spin_unlock_irqrestore (¤t->pi_lock, flags);
And ok.
Quoted 4 lines · Esben Nielsen
Well, we are talking about small optimizations now, moving only a few instructions outside the lock. Except for one of them it is correct, but it is worth risking stability for now?
Yes, optimization is small, but I think this cleanups the code, which is (imho) more important. That said, I don't suggest this patch, it was a question. I stiil can't find a time to read the code hard and convince myself I can understand it :)
Also, I think such a change opens the possibility for further cleanups.
Quoted 6 lines
Quoted 6 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-0@@ -236,6 +236,10 @@ static int rt_mutex_adjust_prio_chain(st goto out_unlock_pi; }
Quoted 6 lines
kernel/rtmutex.c static int rt_mutex_adjust_prio_chain(st · line 242
Quoted 4 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +4
-0+/* Release the task */ +spin_unlock_irqrestore (&task->pi_lock, flags); +put_task_struct (task); +So you want the task to go away here and use it below?
task->pi_blocked_on != NULL, we hold task->pi_blocked_on->lock->wait_lock. Can it go away ?
Quoted 8 lines
Quoted 8 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-0top_waiter =rt_mutex_top_waiter (lock);/* Requeue the waiter */ @@ -243,10 +247,6 @@ static int rt_mutex_adjust_prio_chain(st waiter->list_entry.prio = task->prio;plist_add (&waiter->list_entry, &lock->wait_list);
Quoted 7 lines
kernel/rtmutex.c static int rt_mutex_adjust_prio_chain(st · line 249
Quoted 4 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-4-/* Release the task */ -spin_unlock_irqrestore (&task->pi_lock, flags); -put_task_struct (task); -No! It is used in the line just above, so we better be sure it still exists!
See above. If I am wrong, we can move this line
waiter->list_entry.prio = task->prio;
up, under ->pi_lock. plist_del() doesn't need a valid ->prio.
Thanks for your answer!
Oleg.
Quoted 25 lines · Oleg Nesterov
Quoted 4 lines · Esben Nielsen
Well, we are talking about small optimizations now, moving only a few instructions outside the lock. Except for one of them it is correct, but it is worth risking stability for now?
Yes, optimization is small, but I think this cleanups the code, which is (imho) more important. That said, I don't suggest this patch, it was a question. I stiil can't find a time to read the code hard and convince myself I can understand it :)
Also, I think such a change opens the possibility for further cleanups.
Quoted 6 lines
Quoted 6 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-0@@ -236,6 +236,10 @@ static int rt_mutex_adjust_prio_chain(st goto out_unlock_pi; }Quoted 6 lines
kernel/rtmutex.c static int rt_mutex_adjust_prio_chain(st · line 242
Quoted 4 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +4
-0+/* Release the task */ +spin_unlock_irqrestore (&task->pi_lock, flags); +put_task_struct (task); +So you want the task to go away here and use it below?
Quoted 2 lines
task->pi_blocked_on!= NULL, we holdtask->pi_blocked_on->lock->wait_lock. Can it go away ?
That is correct. But does it make the code more readable? When you read the code you shouldn't need to go into that kind of complicated arguments to see the correctness - unless the code can't be written otherwise.
Quoted 17 lines
Quoted 9 lines
Quoted 9 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-0top_waiter =rt_mutex_top_waiter (lock);/* Requeue the waiter */ @@ -243,10 +247,6 @@ static int rt_mutex_adjust_prio_chain(st waiter->list_entry.prio = task->prio;plist_add (&waiter->list_entry, &lock->wait_list);Quoted 7 lines
kernel/rtmutex.c static int rt_mutex_adjust_prio_chain(st · line 249
Quoted 4 lines
kernel/rtmutex.c renamed from kernel/rtmutex.c~2_rtm +0
-4-/* Release the task */ -spin_unlock_irqrestore (&task->pi_lock, flags); -put_task_struct (task); -No! It is used in the line just above, so we better be sure it still exists!
Quoted 6 lines
See above. If I am wrong, we can move this line
waiter->list_entry.prio = task->prio;up, under ->pi_lock.
plist_del()doesn't need a valid ->prio.
Correct.
Quoted 4 lines
Thanks for your answer!
Oleg.
Esben
Quoted 7 lines · Esben Nielsen
Quoted 3 lines · Oleg Nesterov
task->pi_blocked_on!= NULL, we holdtask->pi_blocked_on->lock->wait_lock. Can it go away ?That is correct. But does it make the code more readable?
Well, in my opinion - yes. But yes, it's only my personal feeling :)
Quoted 3 lines
When you read
the code you shouldn't need to go into that kind of complicated arguments to see the correctness - unless the code can't be written otherwise.
Sure, this needs a comment.
Thanks again,
Oleg.
Quoted 9 lines · Oleg Nesterov
Quoted 7 lines · Esben Nielsen
Quoted 3 lines · Oleg Nesterov
task->pi_blocked_on!= NULL, we holdtask->pi_blocked_on->lock->wait_lock. Can it go away ?That is correct. But does it make the code more readable?
Quoted 7 lines
Well, in my opinion - yes. But yes, it's only my personal feeling :)
Quoted 3 lines
When you read
the code you shouldn't need to go into that kind of complicated arguments to see the correctness - unless the code can't be written otherwise.Sure, this needs a comment.
It is even better if you can read the code without a comment. Good code doesn't need comments.
Esben
Quoted 5 lines
Thanks again,
Oleg.
Quoted 7 lines · Esben Nielsen
Quoted 2 lines · Oleg Nesterov
Sure, this needs a comment.
It is even better if you can read the code without a comment. Good code doesn't need comments.
Yes. But from my point of view, the current code needs a comment to explain why do we take ->pi_lock, this is hard to understand at least to me.
It is even better if we don't take ->pi_lock for ->wait_list manipulation.
Imho.
Oleg.