aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Zijlstra <peterz@infradead.org>2014-02-12 04:49:30 -0500
committerThomas Gleixner <tglx@linutronix.de>2014-02-21 15:43:18 -0500
commit3f1d2a318171bf61850d4e5a72031271e5aada76 (patch)
tree58b0fe5519ddeaac88983ff2ba9818f8695db726
parent6e83125c6b151afa139c8852c099d6d92954fe3b (diff)
sched: Fix hotplug task migration
Dan Carpenter reported: > kernel/sched/rt.c:1347 pick_next_task_rt() warn: variable dereferenced before check 'prev' (see line 1338) > kernel/sched/deadline.c:1011 pick_next_task_dl() warn: variable dereferenced before check 'prev' (see line 1005) Kirill also spotted that migrate_tasks() will have an instant NULL deref because pick_next_task() will immediately deref prev. Instead of fixing all the corner cases because migrate_tasks() can pass in a NULL prev task in the unlikely case of hot-un-plug, provide a fake task such that we can remove all the NULL checks from the far more common paths. A further problem; not previously spotted; is that because we pushed pre_schedule() and idle_balance() into pick_next_task() we now need to avoid those getting called and pulling more tasks on our dying CPU. We avoid pull_{dl,rt}_task() by setting fake_task.prio to MAX_PRIO+1. We also note that since we call pick_next_task() exactly the amount of times we have runnable tasks present, we should never land in idle_balance(). Fixes: 38033c37faab ("sched: Push down pre_schedule() and idle_balance()") Cc: Juri Lelli <juri.lelli@gmail.com> Cc: Ingo Molnar <mingo@kernel.org> Cc: Steven Rostedt <rostedt@goodmis.org> Reported-by: Kirill Tkhai <tkhai@yandex.ru> Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Peter Zijlstra <peterz@infradead.org> Link: http://lkml.kernel.org/r/20140212094930.GB3545@laptop.programming.kicks-ass.net Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r--kernel/sched/core.c18
-rw-r--r--kernel/sched/deadline.c3
-rw-r--r--kernel/sched/fair.c5
-rw-r--r--kernel/sched/idle_task.c3
-rw-r--r--kernel/sched/rt.c3
-rw-r--r--kernel/sched/sched.h5
-rw-r--r--kernel/sched/stop_task.c3
7 files changed, 28 insertions, 12 deletions
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index fb9764fbc537..49db434a35d0 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4681,6 +4681,22 @@ static void calc_load_migrate(struct rq *rq)
4681 atomic_long_add(delta, &calc_load_tasks); 4681 atomic_long_add(delta, &calc_load_tasks);
4682} 4682}
4683 4683
4684static void put_prev_task_fake(struct rq *rq, struct task_struct *prev)
4685{
4686}
4687
4688static const struct sched_class fake_sched_class = {
4689 .put_prev_task = put_prev_task_fake,
4690};
4691
4692static struct task_struct fake_task = {
4693 /*
4694 * Avoid pull_{rt,dl}_task()
4695 */
4696 .prio = MAX_PRIO + 1,
4697 .sched_class = &fake_sched_class,
4698};
4699
4684/* 4700/*
4685 * Migrate all tasks from the rq, sleeping tasks will be migrated by 4701 * Migrate all tasks from the rq, sleeping tasks will be migrated by
4686 * try_to_wake_up()->select_task_rq(). 4702 * try_to_wake_up()->select_task_rq().
@@ -4721,7 +4737,7 @@ static void migrate_tasks(unsigned int dead_cpu)
4721 if (rq->nr_running == 1) 4737 if (rq->nr_running == 1)
4722 break; 4738 break;
4723 4739
4724 next = pick_next_task(rq, NULL); 4740 next = pick_next_task(rq, &fake_task);
4725 BUG_ON(!next); 4741 BUG_ON(!next);
4726 next->sched_class->put_prev_task(rq, next); 4742 next->sched_class->put_prev_task(rq, next);
4727 4743
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index ed31ef66ab9d..bfeb84ecc32b 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1008,8 +1008,7 @@ struct task_struct *pick_next_task_dl(struct rq *rq, struct task_struct *prev)
1008 if (unlikely(!dl_rq->dl_nr_running)) 1008 if (unlikely(!dl_rq->dl_nr_running))
1009 return NULL; 1009 return NULL;
1010 1010
1011 if (prev) 1011 put_prev_task(rq, prev);
1012 prev->sched_class->put_prev_task(rq, prev);
1013 1012
1014 dl_se = pick_next_dl_entity(rq, dl_rq); 1013 dl_se = pick_next_dl_entity(rq, dl_rq);
1015 BUG_ON(!dl_se); 1014 BUG_ON(!dl_se);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 40c758bbdd57..e884e45982af 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4690,7 +4690,7 @@ again:
4690 if (!cfs_rq->nr_running) 4690 if (!cfs_rq->nr_running)
4691 goto idle; 4691 goto idle;
4692 4692
4693 if (!prev || prev->sched_class != &fair_sched_class) 4693 if (prev->sched_class != &fair_sched_class)
4694 goto simple; 4694 goto simple;
4695 4695
4696 /* 4696 /*
@@ -4766,8 +4766,7 @@ simple:
4766 if (!cfs_rq->nr_running) 4766 if (!cfs_rq->nr_running)
4767 goto idle; 4767 goto idle;
4768 4768
4769 if (prev) 4769 put_prev_task(rq, prev);
4770 prev->sched_class->put_prev_task(rq, prev);
4771 4770
4772 do { 4771 do {
4773 se = pick_next_entity(cfs_rq, NULL); 4772 se = pick_next_entity(cfs_rq, NULL);
diff --git a/kernel/sched/idle_task.c b/kernel/sched/idle_task.c
index f7d03af79a5b..53ff9e7c76d2 100644
--- a/kernel/sched/idle_task.c
+++ b/kernel/sched/idle_task.c
@@ -26,8 +26,7 @@ static void check_preempt_curr_idle(struct rq *rq, struct task_struct *p, int fl
26static struct task_struct * 26static struct task_struct *
27pick_next_task_idle(struct rq *rq, struct task_struct *prev) 27pick_next_task_idle(struct rq *rq, struct task_struct *prev)
28{ 28{
29 if (prev) 29 put_prev_task(rq, prev);
30 prev->sched_class->put_prev_task(rq, prev);
31 30
32 schedstat_inc(rq, sched_goidle); 31 schedstat_inc(rq, sched_goidle);
33#ifdef CONFIG_SMP 32#ifdef CONFIG_SMP
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index 72f9ec759972..65c2d6881ac3 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -1344,8 +1344,7 @@ pick_next_task_rt(struct rq *rq, struct task_struct *prev)
1344 if (rt_rq_throttled(rt_rq)) 1344 if (rt_rq_throttled(rt_rq))
1345 return NULL; 1345 return NULL;
1346 1346
1347 if (prev) 1347 put_prev_task(rq, prev);
1348 prev->sched_class->put_prev_task(rq, prev);
1349 1348
1350 p = _pick_next_task_rt(rq); 1349 p = _pick_next_task_rt(rq);
1351 1350
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 92018f9821e8..d276147ba5e4 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -1147,6 +1147,11 @@ struct sched_class {
1147#endif 1147#endif
1148}; 1148};
1149 1149
1150static inline void put_prev_task(struct rq *rq, struct task_struct *prev)
1151{
1152 prev->sched_class->put_prev_task(rq, prev);
1153}
1154
1150#define sched_class_highest (&stop_sched_class) 1155#define sched_class_highest (&stop_sched_class)
1151#define for_each_class(class) \ 1156#define for_each_class(class) \
1152 for (class = sched_class_highest; class; class = class->next) 1157 for (class = sched_class_highest; class; class = class->next)
diff --git a/kernel/sched/stop_task.c b/kernel/sched/stop_task.c
index a4147c9d2017..d6ce65dde541 100644
--- a/kernel/sched/stop_task.c
+++ b/kernel/sched/stop_task.c
@@ -31,8 +31,7 @@ pick_next_task_stop(struct rq *rq, struct task_struct *prev)
31 if (!stop || !stop->on_rq) 31 if (!stop || !stop->on_rq)
32 return NULL; 32 return NULL;
33 33
34 if (prev) 34 put_prev_task(rq, prev);
35 prev->sched_class->put_prev_task(rq, prev);
36 35
37 stop->se.exec_start = rq_clock_task(rq); 36 stop->se.exec_start = rq_clock_task(rq);
38 37