diff options
author | Peter Zijlstra <peterz@infradead.org> | 2015-08-13 17:09:29 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2015-09-18 03:17:50 -0400 |
commit | de9b8f5dcbd94bfb1d249907a635f1fb1968e19c (patch) | |
tree | 82744e9ef61c2cd985c7f9af1f6af287255464ac | |
parent | a7d5c189719846889c029c34e24e166a465368fa (diff) |
sched: Fix crash trying to dequeue/enqueue the idle thread
Sasha reports that his virtual machine tries to schedule the idle
thread since commit 6c37067e2786 ("sched: Change the
sched_class::set_cpus_allowed() calling context").
Hit trace shows this happening from idle_thread_get()->init_idle(),
which is the _second_ init_idle() invocation on that task_struct, the
first being done through idle_init()->fork_idle(). (this code is
insane...)
Because we call init_idle() twice in a row, its ->sched_class ==
&idle_sched_class and ->on_rq = TASK_ON_RQ_QUEUED. This means
do_set_cpus_allowed() think we're queued and will call dequeue_task(),
which is implemented with BUG() for the idle class, seeing how
dequeueing the idle task is a daft thing.
Aside of the whole insanity of calling init_idle() _twice_, change the
code to call set_cpus_allowed_common() instead as this is 'obviously'
before the idle task gets ran etc..
Reported-by: Sasha Levin <sasha.levin@oracle.com>
Tested-by: Sasha Levin <sasha.levin@oracle.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: 6c37067e2786 ("sched: Change the sched_class::set_cpus_allowed() calling context")
Signed-off-by: Ingo Molnar <mingo@kernel.org>
-rw-r--r-- | kernel/sched/core.c | 14 |
1 files changed, 11 insertions, 3 deletions
diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 97d276ff1edb..f0d043ec0182 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c | |||
@@ -4927,7 +4927,15 @@ void init_idle(struct task_struct *idle, int cpu) | |||
4927 | idle->state = TASK_RUNNING; | 4927 | idle->state = TASK_RUNNING; |
4928 | idle->se.exec_start = sched_clock(); | 4928 | idle->se.exec_start = sched_clock(); |
4929 | 4929 | ||
4930 | do_set_cpus_allowed(idle, cpumask_of(cpu)); | 4930 | #ifdef CONFIG_SMP |
4931 | /* | ||
4932 | * Its possible that init_idle() gets called multiple times on a task, | ||
4933 | * in that case do_set_cpus_allowed() will not do the right thing. | ||
4934 | * | ||
4935 | * And since this is boot we can forgo the serialization. | ||
4936 | */ | ||
4937 | set_cpus_allowed_common(idle, cpumask_of(cpu)); | ||
4938 | #endif | ||
4931 | /* | 4939 | /* |
4932 | * We're having a chicken and egg problem, even though we are | 4940 | * We're having a chicken and egg problem, even though we are |
4933 | * holding rq->lock, the cpu isn't yet set to this cpu so the | 4941 | * holding rq->lock, the cpu isn't yet set to this cpu so the |
@@ -4944,7 +4952,7 @@ void init_idle(struct task_struct *idle, int cpu) | |||
4944 | 4952 | ||
4945 | rq->curr = rq->idle = idle; | 4953 | rq->curr = rq->idle = idle; |
4946 | idle->on_rq = TASK_ON_RQ_QUEUED; | 4954 | idle->on_rq = TASK_ON_RQ_QUEUED; |
4947 | #if defined(CONFIG_SMP) | 4955 | #ifdef CONFIG_SMP |
4948 | idle->on_cpu = 1; | 4956 | idle->on_cpu = 1; |
4949 | #endif | 4957 | #endif |
4950 | raw_spin_unlock(&rq->lock); | 4958 | raw_spin_unlock(&rq->lock); |
@@ -4959,7 +4967,7 @@ void init_idle(struct task_struct *idle, int cpu) | |||
4959 | idle->sched_class = &idle_sched_class; | 4967 | idle->sched_class = &idle_sched_class; |
4960 | ftrace_graph_init_idle_task(idle, cpu); | 4968 | ftrace_graph_init_idle_task(idle, cpu); |
4961 | vtime_init_idle(idle, cpu); | 4969 | vtime_init_idle(idle, cpu); |
4962 | #if defined(CONFIG_SMP) | 4970 | #ifdef CONFIG_SMP |
4963 | sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu); | 4971 | sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu); |
4964 | #endif | 4972 | #endif |
4965 | } | 4973 | } |