diff options
Diffstat (limited to 'kernel/events/core.c')
| -rw-r--r-- | kernel/events/core.c | 64 | 
1 files changed, 48 insertions, 16 deletions
diff --git a/kernel/events/core.c b/kernel/events/core.c index a17ed56c8ce1..ff01cba86f43 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -4256,7 +4256,7 @@ int perf_event_release_kernel(struct perf_event *event)  	raw_spin_lock_irq(&ctx->lock);  	/* -	 * Mark this even as STATE_DEAD, there is no external reference to it +	 * Mark this event as STATE_DEAD, there is no external reference to it  	 * anymore.  	 *  	 * Anybody acquiring event->child_mutex after the below loop _must_ @@ -10417,21 +10417,22 @@ void perf_event_free_task(struct task_struct *task)  			continue;  		mutex_lock(&ctx->mutex); -again: -		list_for_each_entry_safe(event, tmp, &ctx->pinned_groups, -				group_entry) -			perf_free_event(event, ctx); +		raw_spin_lock_irq(&ctx->lock); +		/* +		 * Destroy the task <-> ctx relation and mark the context dead. +		 * +		 * This is important because even though the task hasn't been +		 * exposed yet the context has been (through child_list). +		 */ +		RCU_INIT_POINTER(task->perf_event_ctxp[ctxn], NULL); +		WRITE_ONCE(ctx->task, TASK_TOMBSTONE); +		put_task_struct(task); /* cannot be last */ +		raw_spin_unlock_irq(&ctx->lock); -		list_for_each_entry_safe(event, tmp, &ctx->flexible_groups, -				group_entry) +		list_for_each_entry_safe(event, tmp, &ctx->event_list, event_entry)  			perf_free_event(event, ctx); -		if (!list_empty(&ctx->pinned_groups) || -				!list_empty(&ctx->flexible_groups)) -			goto again; -  		mutex_unlock(&ctx->mutex); -  		put_ctx(ctx);  	}  } @@ -10469,7 +10470,12 @@ const struct perf_event_attr *perf_event_attrs(struct perf_event *event)  }  /* - * inherit a event from parent task to child task: + * Inherit a event from parent task to child task. + * + * Returns: + *  - valid pointer on success + *  - NULL for orphaned events + *  - IS_ERR() on error   */  static struct perf_event *  inherit_event(struct perf_event *parent_event, @@ -10563,6 +10569,16 @@ inherit_event(struct perf_event *parent_event,  	return child_event;  } +/* + * Inherits an event group. + * + * This will quietly suppress orphaned events; !inherit_event() is not an error. + * This matches with perf_event_release_kernel() removing all child events. + * + * Returns: + *  - 0 on success + *  - <0 on error + */  static int inherit_group(struct perf_event *parent_event,  	      struct task_struct *parent,  	      struct perf_event_context *parent_ctx, @@ -10577,6 +10593,11 @@ static int inherit_group(struct perf_event *parent_event,  				 child, NULL, child_ctx);  	if (IS_ERR(leader))  		return PTR_ERR(leader); +	/* +	 * @leader can be NULL here because of is_orphaned_event(). In this +	 * case inherit_event() will create individual events, similar to what +	 * perf_group_detach() would do anyway. +	 */  	list_for_each_entry(sub, &parent_event->sibling_list, group_entry) {  		child_ctr = inherit_event(sub, parent, parent_ctx,  					    child, leader, child_ctx); @@ -10586,6 +10607,17 @@ static int inherit_group(struct perf_event *parent_event,  	return 0;  } +/* + * Creates the child task context and tries to inherit the event-group. + * + * Clears @inherited_all on !attr.inherited or error. Note that we'll leave + * inherited_all set when we 'fail' to inherit an orphaned event; this is + * consistent with perf_event_release_kernel() removing all child events. + * + * Returns: + *  - 0 on success + *  - <0 on error + */  static int  inherit_task_group(struct perf_event *event, struct task_struct *parent,  		   struct perf_event_context *parent_ctx, @@ -10608,7 +10640,6 @@ inherit_task_group(struct perf_event *event, struct task_struct *parent,  		 * First allocate and initialize a context for the  		 * child.  		 */ -  		child_ctx = alloc_perf_context(parent_ctx->pmu, child);  		if (!child_ctx)  			return -ENOMEM; @@ -10670,7 +10701,7 @@ static int perf_event_init_context(struct task_struct *child, int ctxn)  		ret = inherit_task_group(event, parent, parent_ctx,  					 child, ctxn, &inherited_all);  		if (ret) -			break; +			goto out_unlock;  	}  	/* @@ -10686,7 +10717,7 @@ static int perf_event_init_context(struct task_struct *child, int ctxn)  		ret = inherit_task_group(event, parent, parent_ctx,  					 child, ctxn, &inherited_all);  		if (ret) -			break; +			goto out_unlock;  	}  	raw_spin_lock_irqsave(&parent_ctx->lock, flags); @@ -10714,6 +10745,7 @@ static int perf_event_init_context(struct task_struct *child, int ctxn)  	}  	raw_spin_unlock_irqrestore(&parent_ctx->lock, flags); +out_unlock:  	mutex_unlock(&parent_ctx->mutex);  	perf_unpin_context(parent_ctx);  | 
