From 6b06d1ce233787655eb21b624ed924806768b36c Mon Sep 17 00:00:00 2001 From: "Bjoern B. Brandenburg" Date: Sun, 4 May 2008 18:07:35 -0400 Subject: LITMUS: avoid using the same stack on two CPUs in global schedulers This change fixes a race where a job could be executed on more than one CPU, which to random crashes. --- include/litmus/rt_param.h | 10 ++++++++++ kernel/sched.c | 6 +++++- litmus/sched_gsn_edf.c | 9 ++------- litmus/sched_litmus.c | 47 +++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/include/litmus/rt_param.h b/include/litmus/rt_param.h index 76be2fe4be..a5e939daa5 100644 --- a/include/litmus/rt_param.h +++ b/include/litmus/rt_param.h @@ -111,6 +111,16 @@ struct rt_param { */ volatile int scheduled_on; + /* Is the stack of the task currently in use? Currently, this + * is the responsibility of the plugin to update this field. + * Maybe become part of the LITMUS core some day. + * + * Used by GSN-EDF. + * + * Be careful to avoid deadlocks! + */ + volatile int stack_in_use; + /* This field can be used by plugins to store where the task * is currently linked. It is the responsibility of the plugin * to avoid race conditions. diff --git a/kernel/sched.c b/kernel/sched.c index 441996e08c..d9e876fea8 100644 --- a/kernel/sched.c +++ b/kernel/sched.c @@ -1897,6 +1897,7 @@ static void finish_task_switch(struct rq *rq, struct task_struct *prev) finish_arch_switch(prev); litmus->finish_switch(prev); finish_lock_switch(rq, prev); + prev->rt_param.stack_in_use = NO_CPU; fire_sched_in_preempt_notifiers(current); if (mm) mmdrop(mm); @@ -3679,6 +3680,7 @@ need_resched_nonpreemptible: rq->curr = next; ++*switch_count; + TRACE_TASK(next, "switched to\n"); context_switch(rq, prev, next); /* unlocks the rq */ } else spin_unlock_irq(&rq->lock); @@ -4391,8 +4393,10 @@ recheck: oldprio = p->prio; __setscheduler(rq, p, policy, param->sched_priority); - if (policy == SCHED_LITMUS) + if (policy == SCHED_LITMUS) { + p->rt_param.stack_in_use = running ? rq->cpu : NO_CPU; litmus->task_new(p, on_rq, running); + } if (on_rq) { if (running) diff --git a/litmus/sched_gsn_edf.c b/litmus/sched_gsn_edf.c index c988e91e6e..eb0f4c0b36 100644 --- a/litmus/sched_gsn_edf.c +++ b/litmus/sched_gsn_edf.c @@ -466,10 +466,8 @@ static struct task_struct* gsnedf_schedule(struct task_struct * prev) TRACE_TASK(next, "scheduled at %llu\n", litmus_clock()); else if (exists && !next) TRACE("becomes idle at %llu.\n", litmus_clock()); - /* don't race with a concurrent switch */ - if (next && prev != next) - while (next->rt_param.scheduled_on != NO_CPU) - cpu_relax(); + + return next; } @@ -481,9 +479,6 @@ static void gsnedf_finish_switch(struct task_struct *prev) cpu_entry_t* entry = &__get_cpu_var(gsnedf_cpu_entries); entry->scheduled = is_realtime(current) ? current : NULL; - - prev->rt_param.scheduled_on = NO_CPU; - current->rt_param.scheduled_on = smp_processor_id(); } diff --git a/litmus/sched_litmus.c b/litmus/sched_litmus.c index feb0159033..ab52ae9510 100644 --- a/litmus/sched_litmus.c +++ b/litmus/sched_litmus.c @@ -21,10 +21,13 @@ static void litmus_tick(struct rq *rq, struct task_struct *p) litmus->tick(p); } +#define NO_CPU -1 + static void litmus_schedule(struct rq *rq, struct task_struct *prev) { struct rq* other_rq; long prev_state; + lt_t _maybe_deadlock = 0; /* WARNING: rq is _not_ locked! */ if (is_realtime(prev)) update_time_litmus(rq, prev); @@ -43,11 +46,43 @@ static void litmus_schedule(struct rq *rq, struct task_struct *prev) */ prev_state = prev->state; spin_unlock(&rq->lock); + + /* Don't race with a concurrent switch. + * This could deadlock in the case of cross or circular migrations. + * It's the job of the plugin to make sure that doesn't happen. + */ + TRACE_TASK(rq->litmus_next, "stack_in_use=%d\n", + rq->litmus_next->rt_param.stack_in_use); + if (rq->litmus_next->rt_param.stack_in_use != NO_CPU) { + TRACE_TASK(rq->litmus_next, "waiting to deschedule\n"); + _maybe_deadlock = litmus_clock(); + } + while (rq->litmus_next->rt_param.stack_in_use != NO_CPU) { + cpu_relax(); + mb(); + if (rq->litmus_next->rt_param.stack_in_use == NO_CPU) + TRACE_TASK(rq->litmus_next, "descheduled. Proceeding.\n"); + if (lt_before(_maybe_deadlock + 10000000, litmus_clock())) { + /* We've been spinning for 10ms. + * Something can't be right! + * Let's abandon the task and bail out; at least + * we will have debug info instead of a hard deadlock. + */ + TRACE_TASK(rq->litmus_next, + "stack too long in use. Deadlock?\n"); + rq->litmus_next = NULL; + + /* bail out */ + spin_lock(&rq->lock); + return; + } + } + double_rq_lock(rq, other_rq); if (prev->state != prev_state) { TRACE_TASK(prev, "state changed while we dropped" - " the lock: now=%d, old=%d", + " the lock: now=%d, old=%d\n", prev->state, prev_state); if (prev_state && !prev->state) { /* prev task became unblocked @@ -61,7 +96,7 @@ static void litmus_schedule(struct rq *rq, struct task_struct *prev) set_task_cpu(rq->litmus_next, smp_processor_id()); - /* now that we have the lock we need to make sure a + /* DEBUG: now that we have the lock we need to make sure a * couple of things still hold: * - it is still a real-time task * - it is still runnable (could have been stopped) @@ -71,12 +106,16 @@ static void litmus_schedule(struct rq *rq, struct task_struct *prev) /* BAD BAD BAD */ TRACE_TASK(rq->litmus_next, "migration invariant FAILED: rt=%d running=%d\n", - is_realtime(rq->litmus_next), + is_realtime(rq->litmus_next), is_running(rq->litmus_next)); + /* drop the task */ + rq->litmus_next = NULL; } /* release the other CPU's runqueue, but keep ours */ spin_unlock(&other_rq->lock); - } + } + if (rq->litmus_next) + rq->litmus_next->rt_param.stack_in_use = rq->cpu; } static void enqueue_task_litmus(struct rq *rq, struct task_struct *p, int wakeup) -- cgit v1.2.2