Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a80b4a3 authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Ingo Molnar
Browse files

perf_counter: Fix a race on perf_counter_ctx



While extending perfcounters with BTS hw-tracing, Markus
Metzger managed to trigger this warning:

   [  995.557128] WARNING: at kernel/perf_counter.c:1191 __perf_counter_task_sched_out+0x48/0x6b()

triggers because commit
9f498cc5 (perf_counter: Full
task tracing) removed clearing of tsk->perf_counter_ctxp out
from under ctx->lock which introduced a race (against
perf_lock_task_context).

Move it back and deal with the exit notification by explicitly
passing along the former task context.

Reported-by: default avatarMarkus T Metzger <markus.t.metzger@intel.com>
Signed-off-by: default avatarPeter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
LKML-Reference: <1249667341.17467.5.camel@twins>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent 3a43ce68
Loading
Loading
Loading
Loading
+15 −15
Original line number Diff line number Diff line
@@ -2851,6 +2851,7 @@ perf_counter_read_event(struct perf_counter *counter,

struct perf_task_event {
	struct task_struct		*task;
	struct perf_counter_context	*task_ctx;

	struct {
		struct perf_event_header	header;
@@ -2910,24 +2911,23 @@ static void perf_counter_task_ctx(struct perf_counter_context *ctx,
static void perf_counter_task_event(struct perf_task_event *task_event)
{
	struct perf_cpu_context *cpuctx;
	struct perf_counter_context *ctx;
	struct perf_counter_context *ctx = task_event->task_ctx;

	cpuctx = &get_cpu_var(perf_cpu_context);
	perf_counter_task_ctx(&cpuctx->ctx, task_event);
	put_cpu_var(perf_cpu_context);

	rcu_read_lock();
	/*
	 * doesn't really matter which of the child contexts the
	 * events ends up in.
	 */
	ctx = rcu_dereference(current->perf_counter_ctxp);
	if (!ctx)
		ctx = rcu_dereference(task_event->task->perf_counter_ctxp);
	if (ctx)
		perf_counter_task_ctx(ctx, task_event);
	rcu_read_unlock();
}

static void perf_counter_task(struct task_struct *task, int new)
static void perf_counter_task(struct task_struct *task,
			      struct perf_counter_context *task_ctx,
			      int new)
{
	struct perf_task_event task_event;

@@ -2938,6 +2938,7 @@ static void perf_counter_task(struct task_struct *task, int new)

	task_event = (struct perf_task_event){
		.task	  = task,
		.task_ctx = task_ctx,
		.event    = {
			.header = {
				.type = new ? PERF_EVENT_FORK : PERF_EVENT_EXIT,
@@ -2956,7 +2957,7 @@ static void perf_counter_task(struct task_struct *task, int new)

void perf_counter_fork(struct task_struct *task)
{
	perf_counter_task(task, 1);
	perf_counter_task(task, NULL, 1);
}

/*
@@ -4310,7 +4311,7 @@ void perf_counter_exit_task(struct task_struct *child)
	unsigned long flags;

	if (likely(!child->perf_counter_ctxp)) {
		perf_counter_task(child, 0);
		perf_counter_task(child, NULL, 0);
		return;
	}

@@ -4330,6 +4331,7 @@ void perf_counter_exit_task(struct task_struct *child)
	 * incremented the context's refcount before we do put_ctx below.
	 */
	spin_lock(&child_ctx->lock);
	child->perf_counter_ctxp = NULL;
	/*
	 * If this context is a clone; unclone it so it can't get
	 * swapped to another process while we're removing all
@@ -4343,9 +4345,7 @@ void perf_counter_exit_task(struct task_struct *child)
	 * won't get any samples after PERF_EVENT_EXIT. We can however still
	 * get a few PERF_EVENT_READ events.
	 */
	perf_counter_task(child, 0);

	child->perf_counter_ctxp = NULL;
	perf_counter_task(child, child_ctx, 0);

	/*
	 * We can recurse on the same lock type through: