* [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue
2026-11-05 9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
@ 2026-11-05 9:30 ` Tengda Wu
2026-11-05 9:30 ` [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events Tengda Wu
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05 9:30 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
linux-perf-users, linux-kernel, Tengda Wu
The sigtrap handling in __perf_event_overflow() checks
event->pending_work before calling task_work_add(), but only sets
pending_work after the task work has been queued.
This leaves a window where an NMI can interrupt task_work_add()
and re-enter __perf_event_overflow() for the same event. Since
pending_work is still zero, the nested invocation can enqueue
the same pending_task again.
The re-entrant call path is:
__perf_event_overflow()
task_work_add()
kasan_record_aux_stack()
<NMI>
__perf_event_overflow()
task_work_add()
The nested invocation can enqueue pending_task before the
outer invocation does. When the outer invocation resumes, it
inserts the same task_work entry again, potentially creating
a cycle in the task work list.
This causes perf_pending_task() to be executed repeatedly
and leads to unbalanced event reference counting.
Use cmpxchg() to atomically claim pending_work before calling
task_work_add(). This ensures that only one invocation can
enqueue the pending_task, even when interrupted by an NMI.
If task_work_add() fails, clear the pending_work claim so that
a subsequent overflow can retry.
Also use READ_ONCE() when checking pending_work in the
exclude_kernel warning path to avoid an unsynchronized read.
Fixes: c5d93d23a260 ("perf: Enqueue SIGTRAP always via task_work.")
Signed-off-by: Tengda Wu <wutengda@huaweicloud.com>
---
kernel/events/core.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 457d75ea7142..ee5c59158b89 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -10925,16 +10925,17 @@ static int __perf_event_overflow(struct perf_event *event,
notify_mode = in_nmi() ? TWA_NMI_CURRENT : TWA_RESUME;
- if (!event->pending_work &&
- !task_work_add(current, &event->pending_task, notify_mode)) {
- event->pending_work = pending_id;
- local_inc(&event->ctx->nr_no_switch_fast);
- WARN_ON_ONCE(!atomic_long_inc_not_zero(&event->refcount));
-
- event->pending_addr = 0;
- if (valid_sample && (data->sample_flags & PERF_SAMPLE_ADDR))
- event->pending_addr = data->addr;
-
+ if (cmpxchg(&event->pending_work, 0, pending_id) == 0) {
+ if (!task_work_add(current, &event->pending_task, notify_mode)) {
+ local_inc(&event->ctx->nr_no_switch_fast);
+ WARN_ON_ONCE(!atomic_long_inc_not_zero(&event->refcount));
+
+ event->pending_addr = 0;
+ if (valid_sample && (data->sample_flags & PERF_SAMPLE_ADDR))
+ event->pending_addr = data->addr;
+ } else {
+ cmpxchg(&event->pending_work, pending_id, 0);
+ }
} else if (event->attr.exclude_kernel && valid_sample) {
/*
* Should not be able to return to user space without
@@ -10948,7 +10949,7 @@ static int __perf_event_overflow(struct perf_event *event,
* To approximate progress (with false negatives),
* check 32-bit hash of the current IP.
*/
- WARN_ON_ONCE(event->pending_work != pending_id);
+ WARN_ON_ONCE(READ_ONCE(event->pending_work) != pending_id);
}
}
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events
2026-11-05 9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
2026-11-05 9:30 ` [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
@ 2026-11-05 9:30 ` Tengda Wu
2026-11-05 9:43 ` [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
2026-11-05 9:47 ` Tengda Wu
3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05 9:30 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
linux-perf-users, linux-kernel, Tengda Wu
perf_sigtrap() contains:
WARN_ON_ONCE(event->ctx->task != current)
The warning assumes that an event's ctx->task always matches the task
that ends up running the pending task_work. That assumption can be
broken by the context fast-swap path.
The failure sequence is:
1. A -> B switch, perf_event_context_sched_out() takes the fast path
and does WRITE_ONCE(ctx->task, next) / WRITE_ONCE(next_ctx->task, task).
An NMI then hits while the context is swapped, and
__perf_event_overflow() queues pending task_work on the *current*
task (A), even though the event now belongs to B's swapped-in
context view.
2. B -> A switch. Because A now has pending task_work,
nr_no_switch_fast is non-zero, so the slow path is taken and
ctx->task is *not* swapped back.
3. A returns to user mode, task_work runs perf_pending_task() ->
perf_sigtrap(), and event->ctx->task no longer equals current,
tripping the WARN_ON_ONCE().
The context fast-swap path was already excluded for events that need
stable task association (inherit + sample read). sigtrap events have
the same requirement: the SIGTRAP is targeted at ctx->task and must not
be delivered after ctx->task has been swapped to another task.
Fix this by also excluding events with attr->sigtrap from the fast-swap
path, i.e. bump nr_no_switch_fast for sigtrap events in
list_add_event()/list_del_event(), and update the comment describing
the invariant accordingly.
Fixes: c5d93d23a260 ("perf: Enqueue SIGTRAP always via task_work.")
Signed-off-by: Tengda Wu <wutengda@huaweicloud.com>
---
include/linux/perf_event.h | 6 ++----
kernel/events/core.c | 9 +++++++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index eb3f7bab498c..5cc0fb144e04 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -1068,10 +1068,8 @@ struct perf_event_context {
* should be avoided.
*
* Sum (event->pending_work + events with
- * (attr->inherit && (attr->sample_type & PERF_SAMPLE_READ)))
- *
- * The SIGTRAP is targeted at ctx->task, as such it won't do changing
- * that until the signal is delivered.
+ * (attr->inherit && (attr->sample_type & PERF_SAMPLE_READ)) +
+ * events with attr->sigtrap)
*/
local_t nr_no_switch_fast;
};
diff --git a/kernel/events/core.c b/kernel/events/core.c
index ee5c59158b89..d693a14ca50d 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -1999,7 +1999,7 @@ list_add_event(struct perf_event *event, struct perf_event_context *ctx)
ctx->nr_user++;
if (event->attr.inherit_stat)
ctx->nr_stat++;
- if (has_inherit_and_sample_read(&event->attr))
+ if (has_inherit_and_sample_read(&event->attr) || event->attr.sigtrap)
local_inc(&ctx->nr_no_switch_fast);
if (event->state > PERF_EVENT_STATE_OFF)
@@ -2225,7 +2225,7 @@ list_del_event(struct perf_event *event, struct perf_event_context *ctx)
ctx->nr_user--;
if (event->attr.inherit_stat)
ctx->nr_stat--;
- if (has_inherit_and_sample_read(&event->attr))
+ if (has_inherit_and_sample_read(&event->attr) || event->attr.sigtrap)
local_dec(&ctx->nr_no_switch_fast);
list_del_rcu(&event->event_entry);
@@ -3831,6 +3831,11 @@ perf_event_context_sched_out(struct task_struct *task, struct task_struct *next)
* SAMPLE_READ events they should be switched
* out using the slow path so that they are
* treated as if they were distinct contexts.
+ *
+ * A context holding a sigtrap event must also
+ * take the slow path, even when nothing is
+ * pending, since the sigtrap delivery relies on
+ * the ctx->task relation as well.
*/
raw_spin_unlock(&next_ctx->lock);
rcu_read_unlock();
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v3 0/2] perf/core: Fix sigtrap task_work races
2026-11-05 9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
2026-11-05 9:30 ` [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
2026-11-05 9:30 ` [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events Tengda Wu
@ 2026-11-05 9:43 ` Tengda Wu
2026-11-05 9:47 ` Tengda Wu
3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05 9:43 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
linux-perf-users, linux-kernel
On 2026/11/5 17:30, Tengda Wu wrote:
> Two fixes for sigtrap task_work handling since the switch to always
> enqueue SIGTRAP via task_work.
>
> The warning splats and PoCs for both patches are too long to inline in
> the commit messages. They are sent as separate replies to this cover
> letter, one per patch.
>
> Patch 1 fixes a re-entrancy race in __perf_event_overflow(): pending_work
> was set only after task_work_add(), so an NMI could re-enter and enqueue
> the same pending_task twice, creating a cycle in the task work list and
> unbalancing the event refcount. Claim pending_work with cmpxchg() before
> enqueuing, and clear it if the enqueue fails.
>
This is the warning splat and PoC for patch 1
("perf/core: Prevent duplicate sigtrap task work enqueue").
Warning:
[ 67.686591] ------------[ cut here ]------------
[ 67.686598] WARNING: kernel/events/core.c:10932 at __perf_event_overflow+0xa48/0xbf0, CPU#1: gpt/1709
[ 67.688569] Modules linked in:
[ 67.689017] CPU: 1 UID: 0 PID: 1709 Comm: gpt Not tainted 7.3.0-next-20261027 #2 PREEMPT(lazy)
[ 67.690194] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org 04/4
[ 67.691718] RIP: 0010:__perf_event_overflow+0xa48/0xbf0
[ 67.692431] Code: ff df 48 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 90 01 00 00 48 8b 04 24 48 8b 80 b8 00 00 00 49 89 86 50 0
[ 67.694915] RSP: 0000:fffffe0000058888 EFLAGS: 00010046
[ 67.695633] RAX: 0000000000000000 RBX: 1fffffc00000b119 RCX: 0000000000000001
[ 67.696594] RDX: 1ffff11023118a5e RSI: 0000000000000008 RDI: ffff8881188c52f0
[ 67.697552] RBP: ffff8881188c51cc R08: 0000000000000001 R09: ffffed1023118a5e
[ 67.698519] R10: ffff8881188c52f7 R11: ffff8881188c5428 R12: ffff8881f70b3538
[ 67.699480] R13: ffff8881188c52f0 R14: ffff8881188c5080 R15: ffff8881188c53c4
[ 67.700445] FS: 00007fed98fc6600(0000) GS:ffff88826ff74000(0000) knlGS:0000000000000000
[ 67.701532] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 67.702317] CR2: 00007fed98d06000 CR3: 000000010507c004 CR4: 0000000000170ef0
[ 67.703281] Call Trace:
[ 67.703636] <NMI>
[ 67.703936] ? __pfx___perf_event_overflow+0x10/0x10
[ 67.704629] ? perf_swevent_set_period+0x131/0x1c0
[ 67.705288] ? __pfx_perf_swevent_set_period+0x10/0x10
[ 67.705992] ? __pfx_perf_event_output_forward+0x10/0x10
[ 67.706726] perf_swevent_event+0x1d0/0x390
[ 67.707304] perf_tp_event+0x3b3/0x8c0
[ 67.707831] ? __perf_event_account_interrupt+0x24/0x570
[ 67.708562] ? __pfx_perf_tp_event+0x10/0x10
[ 67.709158] ? __pfx_x86_pmu_update_perf_regs+0x10/0x10
[ 67.709879] ? handle_pmi_common+0x42f/0x8a0
[ 67.710475] ? __pfx_handle_pmi_common+0x10/0x10
[ 67.711118] perf_trace_msr_trace_class+0x28e/0x450
[ 67.711797] ? __pfx_perf_trace_msr_trace_class+0x10/0x10
[ 67.712532] ? intel_bts_interrupt+0x71/0x4c0
[ 67.713146] do_trace_write_msr+0xaa/0xe0
[ 67.713707] __intel_pmu_enable_all.constprop.0+0x1db/0x350
[ 67.714467] intel_pmu_handle_irq+0x721/0xb70
[ 67.715079] ? __pfx_mce_raise_notify+0x10/0x10
[ 67.715711] ? irq_work_queue+0x7a/0xc0
[ 67.716248] perf_event_nmi_handler+0x13c/0x1a0
[ 67.716885] nmi_handle.part.0+0xb9/0x350
[ 67.717443] ? find_bug+0x3d/0x230
[ 67.717931] default_do_nmi+0x6b/0x180
[ 67.718458] exc_nmi+0xf3/0x120
[ 67.718910] end_repeat_nmi+0xf/0x53
[ 67.719413] RIP: 0010:find_bug+0x3d/0x230
[ 67.719975] Code: 48 89 fb 48 3d b0 65 0d 86 0f 83 8c 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 c1 83 e0 07 48 c1 e9 4
[ 67.722448] RSP: 0000:ffff888118957ae0 EFLAGS: 00000006
[ 67.723166] RAX: 0000000000000003 RBX: ffffffff81a666e5 RCX: 1ffffffff0c14c80
[ 67.724129] RDX: 0000000000000000 RSI: ffffffff81a666e5 RDI: ffffffff81a666e5
[ 67.725098] RBP: ffffffff8713f810 R08: 0000000000000000 R09: 0000000000000000
[ 67.726066] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff81a666e5
[ 67.727033] R13: ffff888118957c88 R14: 0000000000000000 R15: 0000000000000000
[ 67.728000] ? put_pmu_ctx+0x255/0x300
[ 67.728528] ? put_pmu_ctx+0x255/0x300
[ 67.729056] ? put_pmu_ctx+0x255/0x300
[ 67.729583] ? put_pmu_ctx+0x255/0x300
[ 67.730111] ? find_bug+0x3d/0x230
[ 67.730597] ? find_bug+0x3d/0x230
[ 67.731077] </NMI>
[ 67.731383] <TASK>
[ 67.731694] ? put_pmu_ctx+0x255/0x300
[ 67.732217] __report_bug+0x35a/0x3e0
[ 67.732736] ? put_pmu_ctx+0x255/0x300
[ 67.733261] ? __kasan_slab_free+0x43/0x70
[ 67.733834] ? __pfx___report_bug+0x10/0x10
[ 67.734411] ? task_work_run+0x13d/0x200
[ 67.734962] ? irqentry_exit+0x3a8/0x700
[ 67.735507] ? asm_exc_page_fault+0x26/0x30
[ 67.736090] ? __send_signal_locked+0x3c0/0xb40
[ 67.736726] ? __send_signal_locked+0x52f/0xb40
[ 67.737358] ? put_pmu_ctx+0x255/0x300
[ 67.737886] report_bug+0x95/0x1a0
[ 67.738368] ? put_pmu_ctx+0x255/0x300
[ 67.738897] handle_bug+0x267/0x2d0
[ 67.739387] exc_invalid_op+0x18/0x50
[ 67.739904] asm_exc_invalid_op+0x1a/0x20
[ 67.740459] RIP: 0010:put_pmu_ctx+0x255/0x300
[ 67.741067] Code: 75 4a 48 83 c4 68 5b 5d 41 5c 41 5d 41 5e 41 5f e9 50 8b d4 02 48 c7 c6 80 57 a6 81 e8 e4 af b1 ff eb c
[ 67.743549] RSP: 0000:ffff888118957d30 EFLAGS: 00010083
[ 67.744268] RAX: ffff8881188c50a0 RBX: ffff888104258f00 RCX: 1ffff110209df084
[ 67.745235] RDX: 1ffff1102084b1e4 RSI: 0000000000000004 RDI: ffff888107949218
[ 67.746201] RBP: 1ffff1102312afa7 R08: 0000000000000001 R09: ffffed102084b1ea
[ 67.747167] R10: ffff888104258f53 R11: ffffffff810012a6 R12: ffff888104258f20
[ 67.748132] R13: ffff888104258f08 R14: ffff888104ef8400 R15: ffff888104ef8420
[ 67.749098] ? asm_exc_page_fault+0x26/0x30
[ 67.749680] ? __pfx_put_pmu_ctx+0x10/0x10
[ 67.750246] ? mutex_unlock+0x82/0xd0
[ 67.750763] ? __pfx_mutex_unlock+0x10/0x10
[ 67.751342] __free_event+0x1cf/0x680
[ 67.751855] ? _raw_spin_lock_irq+0x8b/0xe0
[ 67.752436] perf_pending_task+0x101/0x330
[ 67.753009] ? count_memcg_events+0x273/0x410
[ 67.753620] task_work_run+0x13d/0x200
[ 67.754146] ? __pfx_task_work_run+0x10/0x10
[ 67.754744] ? __do_sys_rt_sigreturn+0x161/0x1b0
[ 67.755385] irqentry_exit+0x3a8/0x700
[ 67.755912] asm_exc_page_fault+0x26/0x30
[ 67.756467] RIP: 0033:0x7fed98f3cd3a
[ 67.756979] Code: 0f 82 0a 01 00 00 48 83 fa 40 77 7d c5 fe 7f 44 17 e0 c5 fe 7f 07 c5 f8 77 c3 66 0f 1f 44 00 00 48 89 0
[ 67.759451] RSP: 002b:00007ffdb7dc7628 EFLAGS: 00010206
[ 67.760168] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000119010
[ 67.761133] RDX: 00007fed985c7010 RSI: 0000000000000000 RDI: 00007fed98cae000
[ 67.762101] RBP: 00007ffdb7dc7690 R08: 00007fed985c7010 R09: 0000000000000000
[ 67.763069] R10: 0000000000000022 R11: 0000000000000246 R12: 00007ffdb7dc7918
[ 67.764033] R13: 0000000000401976 R14: 00007fed98fffaa0 R15: 0000000000403e08
[ 67.765001] </TASK>
[ 67.765320] Kernel panic - not syncing: kernel: panic_on_warn set ...
[ 67.766196] CPU: 1 UID: 0 PID: 1709 Comm: gpt Not tainted 7.3.0-next-20261027 #2 PREEMPT(lazy)
[ 67.767370] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org 04/4
[ 67.768902] Call Trace:
[ 67.769254] <NMI>
[ 67.769551] vpanic+0x4be/0x540
[ 67.770012] ? __perf_event_overflow+0xa48/0xbf0
[ 67.770653] panic+0xc2/0xd0
[ 67.771068] ? __pfx_panic+0x10/0x10
[ 67.771575] check_panic_on_warn+0x7e/0x80
[ 67.772145] __warn+0xef/0x2b0
[ 67.772586] __report_bug+0x312/0x3e0
[ 67.773102] ? __perf_event_overflow+0xa48/0xbf0
[ 67.773743] ? __pfx___report_bug+0x10/0x10
[ 67.774323] ? perf_tp_event+0x3b3/0x8c0
[ 67.774872] ? arch_irq_work_raise+0x3f/0x60
[ 67.775467] ? __irq_work_queue_local+0x16b/0x3d0
[ 67.776117] ? __perf_event_overflow+0xa48/0xbf0
[ 67.776759] report_bug+0x95/0x1a0
[ 67.777241] ? __perf_event_overflow+0xa48/0xbf0
[ 67.777883] handle_bug+0x267/0x2d0
[ 67.778375] exc_invalid_op+0x18/0x50
[ 67.778895] asm_exc_invalid_op+0x1a/0x20
[ 67.779451] RIP: 0010:__perf_event_overflow+0xa48/0xbf0
[ 67.780169] Code: ff df 48 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 90 01 00 00 48 8b 04 24 48 8b 80 b8 00 00 00 49 89 86 50 0
[ 67.782645] RSP: 0000:fffffe0000058888 EFLAGS: 00010046
[ 67.783359] RAX: 0000000000000000 RBX: 1fffffc00000b119 RCX: 0000000000000001
[ 67.784326] RDX: 1ffff11023118a5e RSI: 0000000000000008 RDI: ffff8881188c52f0
[ 67.785292] RBP: ffff8881188c51cc R08: 0000000000000001 R09: ffffed1023118a5e
[ 67.786258] R10: ffff8881188c52f7 R11: ffff8881188c5428 R12: ffff8881f70b3538
[ 67.787223] R13: ffff8881188c52f0 R14: ffff8881188c5080 R15: ffff8881188c53c4
[ 67.788191] ? __perf_event_overflow+0x975/0xbf0
[ 67.788833] ? __pfx___perf_event_overflow+0x10/0x10
[ 67.789514] ? perf_swevent_set_period+0x131/0x1c0
[ 67.790179] ? __pfx_perf_swevent_set_period+0x10/0x10
[ 67.790888] ? __pfx_perf_event_output_forward+0x10/0x10
[ 67.791618] perf_swevent_event+0x1d0/0x390
[ 67.792197] perf_tp_event+0x3b3/0x8c0
[ 67.792726] ? __perf_event_account_interrupt+0x24/0x570
[ 67.793453] ? __pfx_perf_tp_event+0x10/0x10
[ 67.794051] ? __pfx_x86_pmu_update_perf_regs+0x10/0x10
[ 67.794770] ? handle_pmi_common+0x42f/0x8a0
[ 67.795366] ? __pfx_handle_pmi_common+0x10/0x10
[ 67.796009] perf_trace_msr_trace_class+0x28e/0x450
[ 67.796686] ? __pfx_perf_trace_msr_trace_class+0x10/0x10
[ 67.797424] ? intel_bts_interrupt+0x71/0x4c0
[ 67.798035] do_trace_write_msr+0xaa/0xe0
[ 67.798596] __intel_pmu_enable_all.constprop.0+0x1db/0x350
[ 67.799357] intel_pmu_handle_irq+0x721/0xb70
[ 67.799965] ? __pfx_mce_raise_notify+0x10/0x10
[ 67.800600] ? irq_work_queue+0x7a/0xc0
[ 67.801136] perf_event_nmi_handler+0x13c/0x1a0
[ 67.801770] nmi_handle.part.0+0xb9/0x350
[ 67.802329] ? find_bug+0x3d/0x230
[ 67.802816] default_do_nmi+0x6b/0x180
[ 67.803343] exc_nmi+0xf3/0x120
[ 67.803795] end_repeat_nmi+0xf/0x53
[ 67.804298] RIP: 0010:find_bug+0x3d/0x230
[ 67.804858] Code: 48 89 fb 48 3d b0 65 0d 86 0f 83 8c 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 c1 83 e0 07 48 c1 e9 4
[ 67.807329] RSP: 0000:ffff888118957ae0 EFLAGS: 00000006
[ 67.808047] RAX: 0000000000000003 RBX: ffffffff81a666e5 RCX: 1ffffffff0c14c80
[ 67.809012] RDX: 0000000000000000 RSI: ffffffff81a666e5 RDI: ffffffff81a666e5
[ 67.809978] RBP: ffffffff8713f810 R08: 0000000000000000 R09: 0000000000000000
[ 67.810943] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff81a666e5
[ 67.811909] R13: ffff888118957c88 R14: 0000000000000000 R15: 0000000000000000
[ 67.812877] ? put_pmu_ctx+0x255/0x300
[ 67.813401] ? put_pmu_ctx+0x255/0x300
[ 67.813930] ? put_pmu_ctx+0x255/0x300
[ 67.814453] ? put_pmu_ctx+0x255/0x300
[ 67.814984] ? find_bug+0x3d/0x230
[ 67.815465] ? find_bug+0x3d/0x230
[ 67.815948] </NMI>
[ 67.816255] <TASK>
[ 67.816567] ? put_pmu_ctx+0x255/0x300
[ 67.817092] __report_bug+0x35a/0x3e0
[ 67.817610] ? put_pmu_ctx+0x255/0x300
[ 67.818134] ? __kasan_slab_free+0x43/0x70
[ 67.818705] ? __pfx___report_bug+0x10/0x10
[ 67.819283] ? task_work_run+0x13d/0x200
[ 67.819833] ? irqentry_exit+0x3a8/0x700
[ 67.820378] ? asm_exc_page_fault+0x26/0x30
[ 67.820959] ? __send_signal_locked+0x3c0/0xb40
[ 67.821592] ? __send_signal_locked+0x52f/0xb40
[ 67.822221] ? put_pmu_ctx+0x255/0x300
[ 67.822749] report_bug+0x95/0x1a0
[ 67.823230] ? put_pmu_ctx+0x255/0x300
[ 67.823759] handle_bug+0x267/0x2d0
[ 67.824250] exc_invalid_op+0x18/0x50
[ 67.824766] asm_exc_invalid_op+0x1a/0x20
[ 67.825322] RIP: 0010:put_pmu_ctx+0x255/0x300
[ 67.825932] Code: 75 4a 48 83 c4 68 5b 5d 41 5c 41 5d 41 5e 41 5f e9 50 8b d4 02 48 c7 c6 80 57 a6 81 e8 e4 af b1 ff eb c
[ 67.828401] RSP: 0000:ffff888118957d30 EFLAGS: 00010083
[ 67.829117] RAX: ffff8881188c50a0 RBX: ffff888104258f00 RCX: 1ffff110209df084
[ 67.830083] RDX: 1ffff1102084b1e4 RSI: 0000000000000004 RDI: ffff888107949218
[ 67.831046] RBP: 1ffff1102312afa7 R08: 0000000000000001 R09: ffffed102084b1ea
[ 67.832011] R10: ffff888104258f53 R11: ffffffff810012a6 R12: ffff888104258f20
[ 67.832978] R13: ffff888104258f08 R14: ffff888104ef8400 R15: ffff888104ef8420
[ 67.833948] ? asm_exc_page_fault+0x26/0x30
[ 67.834526] ? __pfx_put_pmu_ctx+0x10/0x10
[ 67.835097] ? mutex_unlock+0x82/0xd0
[ 67.835613] ? __pfx_mutex_unlock+0x10/0x10
[ 67.836192] __free_event+0x1cf/0x680
[ 67.836707] ? _raw_spin_lock_irq+0x8b/0xe0
[ 67.837289] perf_pending_task+0x101/0x330
[ 67.837862] ? count_memcg_events+0x273/0x410
[ 67.838470] task_work_run+0x13d/0x200
[ 67.839000] ? __pfx_task_work_run+0x10/0x10
[ 67.839599] ? __do_sys_rt_sigreturn+0x161/0x1b0
[ 67.840238] irqentry_exit+0x3a8/0x700
[ 67.840767] asm_exc_page_fault+0x26/0x30
[ 67.841323] RIP: 0033:0x7fed98f3cd3a
[ 67.841827] Code: 0f 82 0a 01 00 00 48 83 fa 40 77 7d c5 fe 7f 44 17 e0 c5 fe 7f 07 c5 f8 77 c3 66 0f 1f 44 00 00 48 89 0
[ 67.844298] RSP: 002b:00007ffdb7dc7628 EFLAGS: 00010206
[ 67.845015] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000119010
[ 67.845980] RDX: 00007fed985c7010 RSI: 0000000000000000 RDI: 00007fed98cae000
[ 67.846945] RBP: 00007ffdb7dc7690 R08: 00007fed985c7010 R09: 0000000000000000
[ 67.847909] R10: 0000000000000022 R11: 0000000000000246 R12: 00007ffdb7dc7918
[ 67.848876] R13: 0000000000401976 R14: 00007fed98fffaa0 R15: 0000000000403e08
[ 67.849845] </TASK>
[ 67.850285] Dumping ftrace buffer:
[ 67.850779] (ftrace buffer empty)
[ 67.851279] Kernel Offset: disabled
[ 67.851772] ---[ end Kernel panic - not syncing: kernel: panic_on_warn set ... ]---
PoC:
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include "perf_event.h"
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t sigtrap_count;
static void sigtrap_handler(int sig, siginfo_t *info, void *ctx)
{
(void)sig;
(void)info;
(void)ctx;
sigtrap_count++;
}
static int perf_open(struct perf_event_attr *attr, pid_t pid, int cpu)
{
return syscall(__NR_perf_event_open, attr, pid, cpu, -1,
PERF_FLAG_FD_CLOEXEC);
}
static int read_msr_tracepoint_id(void)
{
static const char * const paths[] = {
"/sys/kernel/tracing/events/msr/write_msr/id",
"/sys/kernel/debug/tracing/events/msr/write_msr/id",
};
char buf[64];
unsigned int i;
for (i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
int fd = open(paths[i], O_RDONLY | O_CLOEXEC);
ssize_t n;
if (fd < 0)
continue;
n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n <= 0)
continue;
buf[n] = '\0';
return (int)strtol(buf, NULL, 10);
}
return -1;
}
static int open_msr_sigtrap_event(int tracepoint_id, int cpu)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_TRACEPOINT;
attr.config = tracepoint_id;
attr.sample_period = 4;
attr.sample_type = PERF_SAMPLE_IP;
attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
PERF_FORMAT_TOTAL_TIME_RUNNING;
attr.pinned = 1;
attr.exclusive = 1;
attr.exclude_idle = 1;
attr.exclude_user = 0;
attr.exclude_kernel = 0;
attr.remove_on_exec = 1;
attr.sigtrap = 1;
attr.sig_data = 7;
/* pid=0 makes this a current-task event constrained to @cpu. */
return perf_open(&attr, 0, cpu);
}
static int open_cache_miss_event(int cpu)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_CACHE_MISSES; /* original config == 3 */
attr.sample_period = 100;
attr.sample_type = PERF_SAMPLE_IP;
attr.pinned = 1;
attr.exclusive = 1;
attr.exclude_idle = 1;
attr.exclude_user = 0;
attr.exclude_kernel = 0;
/* Do not set sigtrap here: the tracepoint event must own pending_task. */
return perf_open(&attr, 0, cpu);
}
static int set_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
return sched_setaffinity(0, sizeof(set), &set);
}
static void workload(unsigned int ms)
{
struct timespec start, now;
uint64_t elapsed;
const size_t bytes = 8 * 1024 * 1024;
volatile unsigned char *buf = malloc(bytes);
volatile uint64_t x = 1;
uint32_t index = 0x12345678;
if (buf)
memset((void *)buf, 0, bytes);
clock_gettime(CLOCK_MONOTONIC, &start);
do {
for (unsigned int i = 0; i < 100000; i++) {
x = x * 1664525 + 1013904223;
/* Pseudo-random cache-line accesses over an 8 MiB set. */
index = index * 1664525 + 1013904223;
if (buf)
buf[(index & (bytes - 1)) & ~63U]++;
}
clock_gettime(CLOCK_MONOTONIC, &now);
elapsed = (uint64_t)(now.tv_sec - start.tv_sec) * 1000;
if (now.tv_nsec >= start.tv_nsec)
elapsed += (uint64_t)(now.tv_nsec - start.tv_nsec) / 1000000;
else
elapsed -= (uint64_t)(start.tv_nsec - now.tv_nsec) / 1000000;
} while (elapsed < ms);
free((void *)buf);
(void)x;
}
/* One short-lived task, matching syzkaller's fork/execute/exit pattern. */
static void one_child(int cpu, int tracepoint_id)
{
struct sigaction sa;
int tp_fd, hw_fd;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigtrap_handler;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGTRAP, &sa, NULL))
_exit(10);
/* As in the original POC, constrain the task after opening events. */
if (set_cpu(cpu)) {
dprintf(2, "[%d] sched_setaffinity failed: %s\n",
getpid(), strerror(errno));
_exit(13);
}
/*
* Open the hardware event first (lower fd), then the tracepoint event
* (higher fd). Linux 6.6 close_files() walks fds upward during exit:
*
* 1. hardware fd closes first and PMU teardown writes MSRs;
* 2. msr:write_msr is still subscribed and can queue task_work;
* 3. tracepoint fd closes, dropping its file reference;
* 4. exit_task_work() runs perf_pending_task(); put_event() can now
* be the final reference and reach perf_trace_event_unreg().
*
* This ordering is intentional and is the main difference from the
* earlier reproducer.
*/
hw_fd = open_cache_miss_event(cpu);
if (hw_fd < 0) {
dprintf(2, "[%d] open cache-misses failed: %s\n",
getpid(), strerror(errno));
_exit(11);
}
tp_fd = open_msr_sigtrap_event(tracepoint_id, cpu);
if (tp_fd < 0) {
dprintf(2, "[%d] open msr tracepoint failed: %s\n",
getpid(), strerror(errno));
_exit(12);
}
workload(5);
/* Do not close either fd here. Exit path ordering is part of the test. */
_exit(0);
}
static void worker(int cpu, int tracepoint_id, time_t end)
{
unsigned long iter = 0;
while (time(NULL) < end) {
pid_t pid = fork();
int status;
if (pid < 0) {
perror("fork");
_exit(2);
}
if (!pid)
one_child(cpu, tracepoint_id);
while (waitpid(pid, &status, 0) < 0 && errno == EINTR)
;
iter++;
}
dprintf(2, "[worker %d] cpu=%d iterations=%lu\n",
getpid(), cpu, iter);
_exit(0);
}
int main(int argc, char **argv)
{
int cpu = argc > 1 ? atoi(argv[1]) : 1;
int workers = argc > 2 ? atoi(argv[2]) : 4;
int duration = argc > 3 ? atoi(argv[3]) : 60;
int tracepoint_id, ncpu;
pid_t *pids;
time_t end;
if (workers < 1)
workers = 1;
if (duration < 1)
duration = 1;
ncpu = (int)sysconf(_SC_NPROCESSORS_ONLN);
if (cpu < 0 || cpu >= ncpu) {
fprintf(stderr, "invalid cpu %d (online cpus: %d)\n", cpu, ncpu);
return 1;
}
tracepoint_id = read_msr_tracepoint_id();
if (tracepoint_id < 0) {
fprintf(stderr, "cannot read msr:write_msr tracepoint id; "
"mount tracefs/debugfs and check CONFIG_X86_MSR\n");
return 1;
}
fprintf(stderr, "msr:write_msr id=%d cpu=%d workers=%d duration=%ds\n",
tracepoint_id, cpu, workers, duration);
fprintf(stderr, "Each child opens tracepoint(sigtrap) + "
"hardware(cache-misses), then exits without close().\n");
pids = calloc((size_t)workers, sizeof(*pids));
if (!pids)
return 1;
end = time(NULL) + duration;
for (int i = 0; i < workers; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork worker");
break;
}
if (!pid)
worker(cpu, tracepoint_id, end);
pids[i] = pid;
}
for (int i = 0; i < workers; i++)
if (pids[i] > 0)
waitpid(pids[i], NULL, 0);
free(pids);
return 0;
}
Thanks,
Tengda
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v3 0/2] perf/core: Fix sigtrap task_work races
2026-11-05 9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
` (2 preceding siblings ...)
2026-11-05 9:43 ` [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
@ 2026-11-05 9:47 ` Tengda Wu
3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05 9:47 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
linux-perf-users, linux-kernel
On 2026/11/5 17:30, Tengda Wu wrote:
> Two fixes for sigtrap task_work handling since the switch to always
> enqueue SIGTRAP via task_work.
>
> The warning splats and PoCs for both patches are too long to inline in
> the commit messages. They are sent as separate replies to this cover
> letter, one per patch.
>
> Patch 1 fixes a re-entrancy race in __perf_event_overflow(): pending_work
> was set only after task_work_add(), so an NMI could re-enter and enqueue
> the same pending_task twice, creating a cycle in the task work list and
> unbalancing the event refcount. Claim pending_work with cmpxchg() before
> enqueuing, and clear it if the enqueue fails.
>
> Patch 2 fixes a WARN_ON_ONCE() in perf_sigtrap() triggered by the context
> fast-swap path swapping ctx->task while a sigtrap event has pending task
> work. SIGTRAP is targeted at ctx->task, so exclude sigtrap events from
> fast-swap alongside inherit+sample-read events.
>
This is the warning splat and PoC for patch 2
("perf/core: Disable context fast-swap for sigtrap events").
Warning observed before the fix:
[ 370.271075] ------------[ cut here ]------------
[ 370.271206] WARNING: kernel/events/core.c:7654 at perf_pending_task+0x2ad/0x330, CPU#1: fastswap/1414
[ 370.273456] Modules linked in:
[ 370.273990] CPU: 1 UID: 0 PID: 1414 Comm: fastswap Not tainted 7.3.0-next-20261027+ #4 PREEMPT(lazy)
[ 370.275315] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org 04/4
[ 370.277114] RIP: 0010:perf_pending_task+0x2ad/0x330
[ 370.277989] Code: 4c 8b ab c8 fe ff ff 49 ff 8d e8 00 00 00 e9 37 fe ff ff 4c 89 ef e8 52 c2 24 00 e9 e6 fd ff ff e8 a8 9
[ 370.280596] RSP: 0018:ffff88810922fe10 EFLAGS: 00010283
[ 370.281502] RAX: ffff8881093b9240 RBX: ffff888108f18398 RCX: 0000000000000000
[ 370.282606] RDX: ffff88811679b6c0 RSI: 0000000000000004 RDI: ffff888103287680
[ 370.283691] RBP: ffff888108f18000 R08: 0000000000000001 R09: ffffed1021245fbf
[ 370.284834] R10: 0000000000000003 R11: 0000000000000000 R12: 0000000000000000
[ 370.285958] R13: ffff888103287600 R14: ffff888108f18260 R15: 0000000000000000
[ 370.287097] FS: 00007f6f8537e600(0000) GS:ffff88826ff74000(0000) knlGS:0000000000000000
[ 370.288387] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 370.289168] CR2: 00007f6f84a0c000 CR3: 00000001100a2006 CR4: 0000000000170ef0
[ 370.290336] Call Trace:
[ 370.290688] <TASK>
[ 370.291034] ? count_memcg_events+0x273/0x410
[ 370.291720] task_work_run+0x13d/0x200
[ 370.292365] ? __pfx_task_work_run+0x10/0x10
[ 370.292956] irqentry_exit+0x3a8/0x700
[ 370.293579] asm_exc_page_fault+0x26/0x30
[ 370.294143] RIP: 0033:0x401316
[ 370.294695] Code: c6 bf 2a 01 00 00 b8 00 00 00 00 e8 14 fe ff ff c9 c3 55 48 89 e5 89 7d fc 48 89 75 f0 48 89 55 e8 8b d
[ 370.297200] RSP: 002b:00007ffe7c0027f0 EFLAGS: 00010206
[ 370.297963] RAX: 000000000000002e RBX: 0000000000000001 RCX: 00007f6f85254d13
[ 370.299006] RDX: 00007ffe7c002800 RSI: 00007ffe7c002930 RDI: 0000000000000005
[ 370.300021] RBP: 00007ffe7c0027f0 R08: 0000000000000000 R09: 00007ffe7c002b67
[ 370.301043] R10: 00007f6f8537e8d0 R11: 0000000000000246 R12: 00007ffe7c0030f8
[ 370.302066] R13: 0000000000401a8a R14: 00007f6f853b7aa0 R15: 0000000000403e08
[ 370.303112] </TASK>
[ 370.303495] ---[ end trace 0000000000000000 ]---
PoC (not easily reproducible, needs several rounds of running):
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include "perf_event.h"
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t sigtrap_count;
static int perf_open(struct perf_event_attr *attr, pid_t pid, int cpu)
{
return syscall(__NR_perf_event_open, attr, pid, cpu, -1,
PERF_FLAG_FD_CLOEXEC);
}
static void sigtrap_handler(int sig, siginfo_t *info, void *ctx)
{
(void)sig;
(void)info;
(void)ctx;
sigtrap_count++;
}
static int set_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
return sched_setaffinity(0, sizeof(set), &set);
}
static int read_msr_tracepoint_id(void)
{
static const char * const paths[] = {
"/sys/kernel/tracing/events/msr/write_msr/id",
"/sys/kernel/debug/tracing/events/msr/write_msr/id",
};
char buf[64];
unsigned int i;
for (i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
int fd = open(paths[i], O_RDONLY | O_CLOEXEC);
ssize_t n;
if (fd < 0)
continue;
n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n <= 0)
continue;
buf[n] = '\0';
return (int)strtol(buf, NULL, 10);
}
return -1;
}
/*
* Open inherited MSR tracepoint event.
*
* The event is created by the worker. Forked children inherit
* the event and get their own child perf context.
*/
static int open_msr_sigtrap_event(int tracepoint_id, int cpu)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_TRACEPOINT;
attr.config = tracepoint_id;
attr.sample_period = 64;
attr.sample_type = PERF_SAMPLE_IP;
attr.pinned = 1;
attr.exclusive = 1;
attr.exclude_idle = 1;
attr.exclude_user = 0;
attr.exclude_kernel = 0;
attr.inherit = 1;
attr.remove_on_exec = 1;
attr.sigtrap = 1;
attr.sig_data = 7;
return perf_open(&attr, 0, cpu);
}
/*
* Open inherited cache-miss event.
*/
static int open_cache_miss_event(int cpu)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_CACHE_MISSES;
attr.sample_period = 100;
attr.sample_type = PERF_SAMPLE_IP;
attr.pinned = 1;
attr.exclusive = 1;
attr.exclude_idle = 1;
attr.exclude_user = 0;
attr.exclude_kernel = 0;
attr.inherit = 1;
/* The hardware event does not own pending_task. */
return perf_open(&attr, 0, cpu);
}
/*
* Generate memory pressure and frequent voluntary context switches.
*/
static void workload(unsigned int ms)
{
struct timespec start, now;
uint64_t elapsed;
const size_t bytes = 8 * 1024 * 1024;
volatile unsigned char *buf;
volatile uint64_t x = 1;
uint32_t index = 0x12345678;
buf = malloc(bytes);
if (buf)
memset((void *)buf, 0, bytes);
clock_gettime(CLOCK_MONOTONIC, &start);
do {
for (unsigned int i = 0; i < 100000; i++) {
x = x * 1664525 + 1013904223;
index = index * 1664525 + 1013904223;
if (buf)
buf[(index & (bytes - 1)) & ~63U]++;
/*
* Yield every 64 iterations to increase
* sched_out/in activity on the same CPU.
*/
if (!(i & 63))
sched_yield();
}
clock_gettime(CLOCK_MONOTONIC, &now);
elapsed = (uint64_t)(now.tv_sec - start.tv_sec) * 1000;
if (now.tv_nsec >= start.tv_nsec)
elapsed += (uint64_t)(now.tv_nsec -
start.tv_nsec) / 1000000;
else
elapsed -= (uint64_t)(start.tv_nsec -
now.tv_nsec) / 1000000;
} while (elapsed < ms);
free((void *)buf);
(void)x;
}
/*
* Forked child:
*
* - inherits worker's perf events;
* - inherits worker's CPU affinity;
* - executes a short workload;
* - exits without explicitly closing perf event FDs.
*/
static void one_child(void)
{
sigtrap_count = 0;
workload(5);
_exit(0);
}
/*
* Worker:
*
* 1. Bind to the target CPU.
* 2. Create perf events with inherit=1.
* 3. Repeatedly fork short-lived children.
* 4. Wait for child exit.
*
* All workers share the same CPU.
*/
static void worker(int cpu, int tracepoint_id, time_t end)
{
struct sigaction sa;
unsigned long iter = 0;
int hw_fd, tp_fd;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigtrap_handler;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGTRAP, &sa, NULL))
_exit(10);
if (set_cpu(cpu)) {
dprintf(2, "[%d] sched_setaffinity failed: %s\n",
getpid(), strerror(errno));
_exit(13);
}
/*
* Open hardware event first, then tracepoint event.
*/
hw_fd = open_cache_miss_event(cpu);
if (hw_fd < 0) {
dprintf(2, "[%d] open cache-misses failed: %s\n",
getpid(), strerror(errno));
_exit(11);
}
tp_fd = open_msr_sigtrap_event(tracepoint_id, cpu);
if (tp_fd < 0) {
dprintf(2, "[%d] open msr tracepoint failed: %s\n",
getpid(), strerror(errno));
close(hw_fd);
_exit(12);
}
dprintf(2,
"[worker %d] cpu=%d hw_fd=%d tp_fd=%d started\n",
getpid(), cpu, hw_fd, tp_fd);
while (time(NULL) < end) {
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
if (errno == EINTR)
continue;
perror("fork");
break;
}
if (pid == 0)
one_child();
/*
* Worker sleeps in waitpid().
* Child runs on the same CPU.
* Child exit wakes the worker.
*/
for (;;) {
pid_t ret = waitpid(pid, &status, 0);
if (ret == pid)
break;
if (ret < 0 && errno == EINTR)
continue;
if (ret < 0)
perror("waitpid");
break;
}
iter++;
}
dprintf(2,
"[worker %d] cpu=%d iterations=%lu sigtrap=%d\n",
getpid(), cpu, iter, (int)sigtrap_count);
/*
* Keep the parent events alive throughout the workload.
* Child inherited events are released through child exit.
*/
close(hw_fd);
close(tp_fd);
_exit(0);
}
int main(int argc, char **argv)
{
int cpu = argc > 1 ? atoi(argv[1]) : 1;
int workers = argc > 2 ? atoi(argv[2]) : 8;
int duration = argc > 3 ? atoi(argv[3]) : 60;
int tracepoint_id;
int ncpu;
int created = 0;
pid_t *pids;
time_t end;
if (workers < 1)
workers = 1;
if (duration < 1)
duration = 1;
ncpu = (int)sysconf(_SC_NPROCESSORS_ONLN);
if (cpu < 0 || cpu >= ncpu) {
fprintf(stderr,
"invalid cpu %d (online cpus: %d)\n",
cpu, ncpu);
return 1;
}
tracepoint_id = read_msr_tracepoint_id();
if (tracepoint_id < 0) {
fprintf(stderr,
"cannot read msr:write_msr tracepoint id; "
"check tracefs/debugfs and CONFIG_X86_MSR\n");
return 1;
}
pids = calloc((size_t)workers, sizeof(*pids));
if (!pids)
return 1;
end = time(NULL) + duration;
fprintf(stderr,
"msr:write_msr id=%d cpu=%d workers=%d duration=%ds\n",
tracepoint_id, cpu, workers, duration);
fprintf(stderr,
"Both perf events use inherit=1.\n");
fprintf(stderr,
"All workers and children are pinned to CPU %d.\n", cpu);
fprintf(stderr,
"Children yield frequently and exit after short workloads.\n");
for (int i = 0; i < workers; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork worker");
break;
}
if (pid == 0)
worker(cpu, tracepoint_id, end);
pids[created++] = pid;
}
for (int i = 0; i < created; i++) {
while (waitpid(pids[i], NULL, 0) < 0) {
if (errno == EINTR)
continue;
break;
}
}
free(pids);
fprintf(stderr, "All workers finished.\n");
return 0;
}
Thanks,
Tengda
^ permalink raw reply [flat|nested] 5+ messages in thread