diff options
Diffstat (limited to 'kernel/events/core.c')
-rw-r--r-- | kernel/events/core.c | 32 |
1 files changed, 30 insertions, 2 deletions
diff --git a/kernel/events/core.c b/kernel/events/core.c index b0c95f0f06fd..6b17ac1b0c2a 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c | |||
@@ -7458,7 +7458,19 @@ __perf_event_exit_task(struct perf_event *child_event, | |||
7458 | struct perf_event_context *child_ctx, | 7458 | struct perf_event_context *child_ctx, |
7459 | struct task_struct *child) | 7459 | struct task_struct *child) |
7460 | { | 7460 | { |
7461 | perf_remove_from_context(child_event, true); | 7461 | /* |
7462 | * Do not destroy the 'original' grouping; because of the context | ||
7463 | * switch optimization the original events could've ended up in a | ||
7464 | * random child task. | ||
7465 | * | ||
7466 | * If we were to destroy the original group, all group related | ||
7467 | * operations would cease to function properly after this random | ||
7468 | * child dies. | ||
7469 | * | ||
7470 | * Do destroy all inherited groups, we don't care about those | ||
7471 | * and being thorough is better. | ||
7472 | */ | ||
7473 | perf_remove_from_context(child_event, !!child_event->parent); | ||
7462 | 7474 | ||
7463 | /* | 7475 | /* |
7464 | * It can happen that the parent exits first, and has events | 7476 | * It can happen that the parent exits first, and has events |
@@ -7474,7 +7486,7 @@ __perf_event_exit_task(struct perf_event *child_event, | |||
7474 | static void perf_event_exit_task_context(struct task_struct *child, int ctxn) | 7486 | static void perf_event_exit_task_context(struct task_struct *child, int ctxn) |
7475 | { | 7487 | { |
7476 | struct perf_event *child_event, *next; | 7488 | struct perf_event *child_event, *next; |
7477 | struct perf_event_context *child_ctx; | 7489 | struct perf_event_context *child_ctx, *parent_ctx; |
7478 | unsigned long flags; | 7490 | unsigned long flags; |
7479 | 7491 | ||
7480 | if (likely(!child->perf_event_ctxp[ctxn])) { | 7492 | if (likely(!child->perf_event_ctxp[ctxn])) { |
@@ -7499,6 +7511,15 @@ static void perf_event_exit_task_context(struct task_struct *child, int ctxn) | |||
7499 | raw_spin_lock(&child_ctx->lock); | 7511 | raw_spin_lock(&child_ctx->lock); |
7500 | task_ctx_sched_out(child_ctx); | 7512 | task_ctx_sched_out(child_ctx); |
7501 | child->perf_event_ctxp[ctxn] = NULL; | 7513 | child->perf_event_ctxp[ctxn] = NULL; |
7514 | |||
7515 | /* | ||
7516 | * In order to avoid freeing: child_ctx->parent_ctx->task | ||
7517 | * under perf_event_context::lock, grab another reference. | ||
7518 | */ | ||
7519 | parent_ctx = child_ctx->parent_ctx; | ||
7520 | if (parent_ctx) | ||
7521 | get_ctx(parent_ctx); | ||
7522 | |||
7502 | /* | 7523 | /* |
7503 | * If this context is a clone; unclone it so it can't get | 7524 | * If this context is a clone; unclone it so it can't get |
7504 | * swapped to another process while we're removing all | 7525 | * swapped to another process while we're removing all |
@@ -7509,6 +7530,13 @@ static void perf_event_exit_task_context(struct task_struct *child, int ctxn) | |||
7509 | raw_spin_unlock_irqrestore(&child_ctx->lock, flags); | 7530 | raw_spin_unlock_irqrestore(&child_ctx->lock, flags); |
7510 | 7531 | ||
7511 | /* | 7532 | /* |
7533 | * Now that we no longer hold perf_event_context::lock, drop | ||
7534 | * our extra child_ctx->parent_ctx reference. | ||
7535 | */ | ||
7536 | if (parent_ctx) | ||
7537 | put_ctx(parent_ctx); | ||
7538 | |||
7539 | /* | ||
7512 | * Report the task dead after unscheduling the events so that we | 7540 | * Report the task dead after unscheduling the events so that we |
7513 | * won't get any samples after PERF_RECORD_EXIT. We can however still | 7541 | * won't get any samples after PERF_RECORD_EXIT. We can however still |
7514 | * get a few PERF_RECORD_READ events. | 7542 | * get a few PERF_RECORD_READ events. |