aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorPaul Mackerras <paulus@samba.org>2010-03-10 04:45:52 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2010-04-01 19:01:30 -0400
commit118c29f02194a7874cbbff605b2ec544b97d5610 (patch)
tree41713d4ba368faac87acaa0285aeaf8c45307d8d /kernel
parent9e5aa831f8aec46e8f64cfc720ead5a0dd1d6a15 (diff)
perf_event: Fix oops triggered by cpu offline/online
commit 220b140b52ab6cc133f674a7ffec8fa792054f25 upstream. Anton Blanchard found that he could reliably make the kernel hit a BUG_ON in the slab allocator by taking a cpu offline and then online while a system-wide perf record session was running. The reason is that when the cpu comes up, we completely reinitialize the ctx field of the struct perf_cpu_context for the cpu. If there is a system-wide perf record session running, then there will be a struct perf_event that has a reference to the context, so its refcount will be 2. (The perf_event has been removed from the context's group_entry and event_entry lists by perf_event_exit_cpu(), but that doesn't remove the perf_event's reference to the context and doesn't decrement the context's refcount.) When the cpu comes up, perf_event_init_cpu() gets called, and it calls __perf_event_init_context() on the cpu's context. That resets the refcount to 1. Then when the perf record session finishes and the perf_event is closed, the refcount gets decremented to 0 and the context gets kfreed after an RCU grace period. Since the context wasn't kmalloced -- it's part of a per-cpu variable -- bad things happen. In fact we don't need to completely reinitialize the context when the cpu comes up. It's sufficient to initialize the context once at boot, but we need to do it for all possible cpus. This moves the context initialization to happen at boot time. With this, we don't trash the refcount and the context never gets kfreed, and we don't hit the BUG_ON. Reported-by: Anton Blanchard <anton@samba.org> Signed-off-by: Paul Mackerras <paulus@samba.org> Tested-by: Anton Blanchard <anton@samba.org> Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Signed-off-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/perf_event.c13
1 files changed, 12 insertions, 1 deletions
diff --git a/kernel/perf_event.c b/kernel/perf_event.c
index ff8e468486d5..32d0ae230074 100644
--- a/kernel/perf_event.c
+++ b/kernel/perf_event.c
@@ -5246,12 +5246,22 @@ int perf_event_init_task(struct task_struct *child)
5246 return ret; 5246 return ret;
5247} 5247}
5248 5248
5249static void __init perf_event_init_all_cpus(void)
5250{
5251 int cpu;
5252 struct perf_cpu_context *cpuctx;
5253
5254 for_each_possible_cpu(cpu) {
5255 cpuctx = &per_cpu(perf_cpu_context, cpu);
5256 __perf_event_init_context(&cpuctx->ctx, NULL);
5257 }
5258}
5259
5249static void __cpuinit perf_event_init_cpu(int cpu) 5260static void __cpuinit perf_event_init_cpu(int cpu)
5250{ 5261{
5251 struct perf_cpu_context *cpuctx; 5262 struct perf_cpu_context *cpuctx;
5252 5263
5253 cpuctx = &per_cpu(perf_cpu_context, cpu); 5264 cpuctx = &per_cpu(perf_cpu_context, cpu);
5254 __perf_event_init_context(&cpuctx->ctx, NULL);
5255 5265
5256 spin_lock(&perf_resource_lock); 5266 spin_lock(&perf_resource_lock);
5257 cpuctx->max_pertask = perf_max_events - perf_reserved_percpu; 5267 cpuctx->max_pertask = perf_max_events - perf_reserved_percpu;
@@ -5322,6 +5332,7 @@ static struct notifier_block __cpuinitdata perf_cpu_nb = {
5322 5332
5323void __init perf_event_init(void) 5333void __init perf_event_init(void)
5324{ 5334{
5335 perf_event_init_all_cpus();
5325 perf_cpu_notify(&perf_cpu_nb, (unsigned long)CPU_UP_PREPARE, 5336 perf_cpu_notify(&perf_cpu_nb, (unsigned long)CPU_UP_PREPARE,
5326 (void *)(long)smp_processor_id()); 5337 (void *)(long)smp_processor_id());
5327 perf_cpu_notify(&perf_cpu_nb, (unsigned long)CPU_ONLINE, 5338 perf_cpu_notify(&perf_cpu_nb, (unsigned long)CPU_ONLINE,