sched: Fix wake up race with rt_mutex_setprio()

From: Thomas Gleixner
Date: Sun Feb 14 2010 - 14:46:07 EST


rt_mutex_setprio() can race with try_to_wake_up():

CPU 0 CPU 1
try_to_wake_up(p)
task_rq_lock(p)
p->state = TASK_WAKING;
task_rq_unlock() rt_mutex_setprio(p)
task_rq_lock(p) <- succeeds (old CPU)
newcpu = select_task_rq(p)
set_task_cpu(p)

task_rq_lock(p) <- succeeds (new CPU)

activate_task(p) change sched_class(p)

That way we end up with the task enqueued in the wrong sched class.

Solve this by waiting for p->state != TASK_WAKING in rt_mutex_setprio().

Debugged and tested in the preempt-rt tree.

Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
---
kernel/sched.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)

Index: linux-2.6/kernel/sched.c
===================================================================
--- linux-2.6.orig/kernel/sched.c
+++ linux-2.6/kernel/sched.c
@@ -6058,7 +6058,25 @@ void rt_mutex_setprio(struct task_struct

BUG_ON(prio < 0 || prio > MAX_PRIO);

+again:
rq = task_rq_lock(p, &flags);
+
+ /*
+ * Prevent a nasty race with ttwu(). ttwu() sets the task
+ * state to WAKING and drops the runqueue lock. So we can
+ * acquire the runqueue lock while ttwu() migrates the
+ * task. We need to wait until ttwu() set the target cpu and
+ * enqueued the task on whatever CPU it decided to select.
+ * Otherwise ttwu() might enqueue with the old class on
+ * another cpu while we are changing the class on the previous
+ * cpu.
+ */
+ if (unlikely(p->state == TASK_WAKING)) {
+ task_rq_unlock(rq, &flags);
+ cpu_relax();
+ goto again;
+ }
+
update_rq_clock(rq);

oldprio = p->prio;
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/