summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorRusty Russell <rusty@rustcorp.com.au>2004-08-26 20:30:43 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2004-08-26 20:30:43 -0700
commite271fc8510eb1b0213aede782d84369304e09127 (patch)
tree03d6437dc7662f3a752ef63e56e91ca886aba737 /kernel
parenta5212682cc2c0a2e3fb1ab50160861d5cca04fc2 (diff)
[PATCH] Hotplug CPU vs TASK_ZOMBIEs: The Sequel to Hotplug CPU vs TASK_DEAD
release_task can sleep. Sleeping allows a CPU to go down underneath you. release_task removes you from the tasklist, so you don't get migrated off the CPU: BUG() in sched.c. In last week's episode, our dashing hero (Ingo Molnar) solved this for self-reaping tasks by grabbing the hotplug cpu lock to prevent this. However, in an unexpected twist, the problem remains for tasks whose parents call release_task on them: the zombies are off the task list, and lurk on the dead CPU. Fortunately, the comedic sidekick (Rusty Russell) has an answer: let's make the hotplug callback walk the runqueue of the dead CPU as well, taking care of the zombies. 1) Restore exit.c to its former form. The comment is incorrect: sched.c checks PF_DEAD, not the state, to decide to do the final put_task_struct(), and it does it for all tasks, self-reaping or no. 2) Implement migrate_dead_tasks() in the sched.c hotplug CPU callback. 3) Rename migrate_all_tasks() to migrate_live_tasks(). Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/exit.c29
-rw-r--r--kernel/sched.c48
2 files changed, 50 insertions, 27 deletions
diff --git a/kernel/exit.c b/kernel/exit.c
index b21436b147f1..f0d1ff3c30f2 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -754,8 +754,8 @@ static void exit_notify(struct task_struct *tsk)
state = TASK_ZOMBIE;
if (tsk->exit_signal == -1 && tsk->ptrace == 0)
state = TASK_DEAD;
- else
- tsk->state = state;
+ tsk->state = state;
+
/*
* Clear these here so that update_process_times() won't try to deliver
* itimer, profile or rlimit signals to this task while it is in late exit.
@@ -764,14 +764,6 @@ static void exit_notify(struct task_struct *tsk)
tsk->it_prof_value = 0;
tsk->rlim[RLIMIT_CPU].rlim_cur = RLIM_INFINITY;
- /*
- * Get a reference to it so that we can set the state
- * as the last step. The state-setting only matters if the
- * current task is releasing itself, to trigger the final
- * put_task_struct() in finish_task_switch(). (thread self-reap)
- */
- get_task_struct(tsk);
-
write_unlock_irq(&tasklist_lock);
list_for_each_safe(_p, _n, &ptrace_dead) {
@@ -781,23 +773,12 @@ static void exit_notify(struct task_struct *tsk)
}
/* If the process is dead, release it - nobody will wait for it */
- if (state == TASK_DEAD) {
- lock_cpu_hotplug();
+ if (state == TASK_DEAD)
release_task(tsk);
- write_lock_irq(&tasklist_lock);
- /*
- * No preemption may happen from this point on,
- * or CPU hotplug (and task exit) breaks:
- */
- unlock_cpu_hotplug();
- tsk->state = state;
- _raw_write_unlock(&tasklist_lock);
- local_irq_enable();
- } else
- preempt_disable();
+ /* PF_DEAD causes final put_task_struct after we schedule. */
+ preempt_disable();
tsk->flags |= PF_DEAD;
- put_task_struct(tsk);
}
asmlinkage NORET_TYPE void do_exit(long code)
diff --git a/kernel/sched.c b/kernel/sched.c
index 732344a7be1b..4b44d5219c36 100644
--- a/kernel/sched.c
+++ b/kernel/sched.c
@@ -3990,8 +3990,8 @@ static void move_task_off_dead_cpu(int dead_cpu, struct task_struct *tsk)
__migrate_task(tsk, dead_cpu, dest_cpu);
}
-/* migrate_all_tasks - function to migrate all tasks from the dead cpu. */
-static void migrate_all_tasks(int src_cpu)
+/* Run through task list and migrate tasks from the dead cpu. */
+static void migrate_live_tasks(int src_cpu)
{
struct task_struct *tsk, *t;
@@ -4033,6 +4033,47 @@ void sched_idle_next(void)
spin_unlock_irqrestore(&rq->lock, flags);
}
+
+static void migrate_dead(unsigned int dead_cpu, task_t *tsk)
+{
+ struct runqueue *rq = cpu_rq(dead_cpu);
+
+ /* Must be exiting, otherwise would be on tasklist. */
+ BUG_ON(tsk->state != TASK_ZOMBIE && tsk->state != TASK_DEAD);
+
+ /* Cannot have done final schedule yet: would have vanished. */
+ BUG_ON(tsk->flags & PF_DEAD);
+
+ get_task_struct(tsk);
+
+ /*
+ * Drop lock around migration; if someone else moves it,
+ * that's OK. No task can be added to this CPU, so iteration is
+ * fine.
+ */
+ spin_unlock_irq(&rq->lock);
+ move_task_off_dead_cpu(dead_cpu, tsk);
+ spin_lock_irq(&rq->lock);
+
+ put_task_struct(tsk);
+}
+
+/* release_task() removes task from tasklist, so we won't find dead tasks. */
+static void migrate_dead_tasks(unsigned int dead_cpu)
+{
+ unsigned arr, i;
+ struct runqueue *rq = cpu_rq(dead_cpu);
+
+ for (arr = 0; arr < 2; arr++) {
+ for (i = 0; i < MAX_PRIO; i++) {
+ struct list_head *list = &rq->arrays[arr].queue[i];
+ while (!list_empty(list))
+ migrate_dead(dead_cpu,
+ list_entry(list->next, task_t,
+ run_list));
+ }
+ }
+}
#endif /* CONFIG_HOTPLUG_CPU */
/*
@@ -4072,7 +4113,7 @@ static int migration_call(struct notifier_block *nfb, unsigned long action,
cpu_rq(cpu)->migration_thread = NULL;
break;
case CPU_DEAD:
- migrate_all_tasks(cpu);
+ migrate_live_tasks(cpu);
rq = cpu_rq(cpu);
kthread_stop(rq->migration_thread);
rq->migration_thread = NULL;
@@ -4081,6 +4122,7 @@ static int migration_call(struct notifier_block *nfb, unsigned long action,
deactivate_task(rq->idle, rq);
rq->idle->static_prio = MAX_PRIO;
__setscheduler(rq->idle, SCHED_NORMAL, 0);
+ migrate_dead_tasks(cpu);
task_rq_unlock(rq, &flags);
BUG_ON(rq->nr_running != 0);