Skip to contentSkip to conversation

cleanup and remove some extra spinlocks from rtmutex

PATCH In review Steven Rostedt 3 participants 9 messages Aug 1, 2006 – Aug 15, 2006

lore
Share & tools
lore
#0 lore raw

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
Index: linux-2.6.18-rc2/kernel/rtmutex.c
===================================================================
@@ -409,7 +409,7 @@ static int task_blocks_on_rt_mutex(struc
 	struct 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(&current->pi_lock, flags);
 	__rt_mutex_adjust_prio(current);
@@ -433,25 +433,23 @@ static int task_blocks_on_rt_mutex(struc
 		plist_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_mute
 	int 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(&current->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);
#1 lore raw

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_stru
 		return;
 	}
 
-	/* 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);
 }
 
#2 lore raw
In reply to Steven Rostedt

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(struc
 	plist_node_init(&waiter->list_entry, current->prio);
 	plist_node_init(&waiter->pi_list_entry, current->prio);
 
+	current->pi_blocked_on = waiter;
+
+	spin_unlock_irqrestore(&current->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(&current->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 rt
 	struct task_struct *pendowner;
 	unsigned long flags;
 
-	spin_lock_irqsave(&current->pi_lock, flags);
-
 	waiter = rt_mutex_top_waiter(lock);
 	plist_del(&waiter->list_entry, &lock->wait_list);
 
+	spin_lock_irqsave(&current->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_mute
 	unsigned long flags;
 	int chain_walk = 0;
 
-	spin_lock_irqsave(&current->pi_lock, flags);
 	plist_del(&waiter->list_entry, &lock->wait_list);
+
+	spin_lock_irqsave(&current->pi_lock, flags);
 	waiter->task = NULL;
 	current->pi_blocked_on = NULL;
 	spin_unlock_irqrestore(&current->pi_lock, flags);
#3 lore raw
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 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.

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 -4
 	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);
-

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(struc
 	plist_node_init(&waiter->list_entry, current->prio);
 	plist_node_init(&waiter->pi_list_entry, current->prio);
 
+	current->pi_blocked_on = waiter;
+
+	spin_unlock_irqrestore(&current->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(&current->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 -2
 	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 rt
 	struct task_struct *pendowner;
 	unsigned long flags;
 
-	spin_lock_irqsave(&current->pi_lock, flags);
-
 	waiter = rt_mutex_top_waiter(lock);
 	plist_del(&waiter->list_entry, &lock->wait_list);
 
+	spin_lock_irqsave(&current->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_mute
 	unsigned long flags;
 	int chain_walk = 0;
 
-	spin_lock_irqsave(&current->pi_lock, flags);
 	plist_del(&waiter->list_entry, &lock->wait_list);
+
+	spin_lock_irqsave(&current->pi_lock, flags);
 	waiter->task = NULL;
 	current->pi_blocked_on = NULL;
 	spin_unlock_irqrestore(&current->pi_lock, flags);

And ok.

#4 lore raw
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 -0
 	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);
 
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.

#5 lore raw
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 hold task->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 -0
 	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);
 
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

#6 lore raw
Quoted 7 lines · Esben Nielsen
Quoted 3 lines · Oleg Nesterov

task->pi_blocked_on != NULL, we hold task->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.

#7 lore raw
Quoted 9 lines · Oleg Nesterov
Quoted 7 lines · Esben Nielsen
Quoted 3 lines · Oleg Nesterov

task->pi_blocked_on != NULL, we hold task->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.

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