* [PATCH v6 0/10] tracing/osnoise: Synchronization fixes
@ 2026-10-28 22:01 Crystal Wood
2026-10-28 22:01 ` [PATCH v6 01/10] tracing/osnoise: Variable cleanup Crystal Wood
` (9 more replies)
0 siblings, 10 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Various improvements to synchronization in osnoise/timerlat.
v6:
- Revived the "Always check OSN_WORKLOAD" and "Split out per-cpu
variable resets" patches, with fixes, to address minor issues raised
by Sashiko
- Other minor Sashiko-requseted fixes
v5:
- Removed "Split out per-cpu variable resets" due to Sashiko feedback
about races with NMIs and other tracing hooks; the main motivation
for that patch was problems with osn->sampling and tlat->thread,
both of which are now explicitly cleared.
- Fixed some patch shuffling bugs in fd detachment that were revealed
by dropping the reset patch
- Added "Start all eligible cpus from hotplug workfn"
- Removed "Always check OSN_WORKLOAD" which is no longer needed after the
hotplug workfn patch
https://lore.kernel.org/all/20261024015614.3095251-1-crwood@redhat.com/
v4: fixed a problem with the v3 fix spotted by Sashiko
https://lore.kernel.org/all/20261014220759.908839-1-crwood@redhat.com/
v3: make sure osn->sampling gets cleared on stop, even for non-workload
osnoise (reported by Sashiko)
https://lore.kernel.org/all/20261014022640.695786-1-crwood@redhat.com/
v2: addresses issues raised by Sashiko, as well as a few more problems
discovered in additional testing.
https://lore.kernel.org/all/20261010022338.2507589-1-crwood@redhat.com/
v1: https://lore.kernel.org/all/20260824211544.3984835-1-crwood@redhat.com/
Crystal Wood (10):
tracing/osnoise: Variable cleanup
tracing/osnoise: start_kthread: Always check OSN_WORKLOAD
tracing/osnoise: Start all eligible cpus from hotplug workfn
tracing/osnoise: Hold a ref on osn->kthread
tracing/osnoise: Per-cpu mutexes and fd detachment
tracing/osnoise: Split out per-cpu variable resets
tracing/osnoise: kthreads: Verify CPU after migrate_disable()
tracing/osnoise: Take trace_types_lock in timerlat_fd_open
tracing/osnoise: Print taint message for timerlat
tracing/osnoise: Refuse timerlat fd open if tracer inactive
Documentation/trace/timerlat-tracer.rst | 5 +-
kernel/trace/trace_osnoise.c | 480 ++++++++++++++----------
2 files changed, 287 insertions(+), 198 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v6 01/10] tracing/osnoise: Variable cleanup
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:01 ` [PATCH v6 02/10] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD Crystal Wood
` (8 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Add cpu_osn_var/cpu_tmr_var() accessors and use them.
Timerlat threads aren't always kthreads, so rename the variable to just
"thread".
rel_period isn't used at all, so remove it.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: new patch
---
kernel/trace/trace_osnoise.c | 53 +++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 22 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 0e1265acd1cc..953936bd5eec 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -250,6 +250,11 @@ static inline struct osnoise_variables *this_cpu_osn_var(void)
return this_cpu_ptr(&per_cpu_osnoise_var);
}
+static inline struct osnoise_variables *cpu_osn_var(int cpu)
+{
+ return per_cpu_ptr(&per_cpu_osnoise_var, cpu);
+}
+
/*
* Protect the interface.
*/
@@ -260,9 +265,8 @@ static struct mutex interface_lock;
* Runtime information for the timer mode.
*/
struct timerlat_variables {
- struct task_struct *kthread;
+ struct task_struct *thread;
struct hrtimer timer;
- u64 rel_period;
u64 abs_period;
bool tracing_thread;
u64 count;
@@ -284,6 +288,11 @@ static inline struct timerlat_variables *this_cpu_tmr_var(void)
return this_cpu_ptr(&per_cpu_timerlat_var);
}
+static inline struct timerlat_variables *cpu_tmr_var(int cpu)
+{
+ return per_cpu_ptr(&per_cpu_timerlat_var, cpu);
+}
+
/*
* tlat_var_reset - Reset the values of the given timerlat_variables
*/
@@ -300,8 +309,8 @@ static inline void tlat_var_reset(void)
* zeroing the structure is perfect.
*/
for_each_online_cpu(cpu) {
- tlat_var = per_cpu_ptr(&per_cpu_timerlat_var, cpu);
- if (tlat_var->kthread)
+ tlat_var = cpu_tmr_var(cpu);
+ if (tlat_var->thread)
hrtimer_cancel(&tlat_var->timer);
memset(tlat_var, 0, sizeof(*tlat_var));
}
@@ -330,7 +339,7 @@ static inline void osn_var_reset(void)
* zeroing the structure is perfect.
*/
for_each_online_cpu(cpu) {
- osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
+ osn_var = cpu_osn_var(cpu);
memset(osn_var, 0, sizeof(*osn_var));
}
}
@@ -1226,9 +1235,9 @@ static void trace_sched_migrate_callback(void *data, struct task_struct *p, int
struct osnoise_variables *osn_var;
long cpu = task_cpu(p);
- osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
+ osn_var = cpu_osn_var(cpu);
if (osn_var->pid == p->pid && dest_cpu != cpu) {
- per_cpu_ptr(&per_cpu_timerlat_var, cpu)->uthread_migrate = 1;
+ cpu_tmr_var(cpu)->uthread_migrate = 1;
osnoise_taint("timerlat user-thread migrated\n");
osnoise_stop_exception("timerlat user-thread migrated", cpu);
}
@@ -1826,13 +1835,13 @@ static enum hrtimer_restart timerlat_irq(struct hrtimer *timer)
osnoise_stop_tracing();
notify_new_max_latency(diff);
- wake_up_process(tlat->kthread);
+ wake_up_process(tlat->thread);
return HRTIMER_NORESTART;
}
}
- wake_up_process(tlat->kthread);
+ wake_up_process(tlat->thread);
if (osnoise_data.print_stack)
timerlat_save_stack(0);
@@ -1925,7 +1934,7 @@ static int timerlat_main(void *data)
tlat->tracing_thread = false;
hrtimer_setup(&tlat->timer, timerlat_irq, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD);
- tlat->kthread = current;
+ tlat->thread = current;
osn_var->pid = current->pid;
/*
* Annotate the arrival time.
@@ -1980,7 +1989,7 @@ static void stop_kthread(unsigned int cpu)
{
struct task_struct *kthread;
- kthread = xchg_relaxed(&(per_cpu(per_cpu_osnoise_var, cpu).kthread), NULL);
+ kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
if (kthread) {
if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask) &&
!WARN_ON(!test_bit(OSN_WORKLOAD, &osnoise_options))) {
@@ -2000,7 +2009,7 @@ static void stop_kthread(unsigned int cpu)
/*
* This is set in the osnoise tracer case.
*/
- per_cpu(per_cpu_osnoise_var, cpu).sampling = false;
+ cpu_osn_var(cpu)->sampling = false;
barrier();
}
}
@@ -2034,7 +2043,7 @@ static int start_kthread(unsigned int cpu)
char comm[24];
/* Do not start a new thread if it is already running */
- if (per_cpu(per_cpu_osnoise_var, cpu).kthread)
+ if (cpu_osn_var(cpu)->kthread)
return 0;
if (timerlat_enabled()) {
@@ -2043,7 +2052,7 @@ static int start_kthread(unsigned int cpu)
} else {
/* if no workload, just return */
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
- per_cpu(per_cpu_osnoise_var, cpu).sampling = true;
+ cpu_osn_var(cpu)->sampling = true;
barrier();
return 0;
}
@@ -2057,7 +2066,7 @@ static int start_kthread(unsigned int cpu)
return -ENOMEM;
}
- per_cpu(per_cpu_osnoise_var, cpu).kthread = kthread;
+ cpu_osn_var(cpu)->kthread = kthread;
cpumask_set_cpu(cpu, &kthread_cpumask);
return 0;
@@ -2090,7 +2099,7 @@ static int start_per_cpu_kthreads(void)
if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask)) {
struct task_struct *kthread;
- kthread = xchg_relaxed(&(per_cpu(per_cpu_osnoise_var, cpu).kthread), NULL);
+ kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
if (!WARN_ON(!kthread))
kthread_stop(kthread);
}
@@ -2519,7 +2528,7 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
return -EINVAL;
}
} else {
- per_cpu_ptr(&per_cpu_timerlat_var, cpu)->uthread_migrate = 1;
+ cpu_tmr_var(cpu)->uthread_migrate = 1;
osnoise_taint("timerlat user thread migrate\n");
osnoise_stop_tracing();
migrate_enable();
@@ -2563,7 +2572,7 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
} else {
tlat->tracing_thread = false;
- tlat->kthread = current;
+ tlat->thread = current;
/* Annotate now to drift new period */
tlat->abs_period = hrtimer_cb_get_time(&tlat->timer);
@@ -2612,10 +2621,10 @@ static int timerlat_fd_release(struct inode *inode, struct file *file)
migrate_disable();
mutex_lock(&interface_lock);
- osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
- tlat_var = per_cpu_ptr(&per_cpu_timerlat_var, cpu);
+ osn_var = cpu_osn_var(cpu);
+ tlat_var = cpu_tmr_var(cpu);
- if (tlat_var->kthread)
+ if (tlat_var->thread)
hrtimer_cancel(&tlat_var->timer);
memset(tlat_var, 0, sizeof(*tlat_var));
@@ -3095,7 +3104,7 @@ static void timerlat_tracer_stop(struct trace_array *tr)
*/
if (!osnoise_has_registered_instances()) {
for_each_online_cpu(cpu)
- per_cpu(per_cpu_osnoise_var, cpu).sampling = 0;
+ cpu_osn_var(cpu)->sampling = 0;
}
osnoise_workload_stop();
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 02/10] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
2026-10-28 22:01 ` [PATCH v6 01/10] tracing/osnoise: Variable cleanup Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:06 ` sashiko-bot
2026-10-28 22:01 ` [PATCH v6 03/10] tracing/osnoise: Start all eligible cpus from hotplug workfn Crystal Wood
` (7 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
We can get here on timerlat with OSN_WORKLOAD cleared via
osnoise_hotplug_workfn().
While we're moving this code, remove the pointless compiler barrier
immediately before returning from a function. Even if it's inlined,
it's not doing anything useful.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v6: revive patch
---
kernel/trace/trace_osnoise.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 953936bd5eec..a6f7fbb342b6 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -2046,16 +2046,17 @@ static int start_kthread(unsigned int cpu)
if (cpu_osn_var(cpu)->kthread)
return 0;
+ /* Can still happen with timerlat via cpu hotplug */
+ if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
+ if (!timerlat_enabled())
+ osn->sampling = true;
+ return 0;
+ }
+
if (timerlat_enabled()) {
snprintf(comm, 24, "timerlat/%d", cpu);
main = timerlat_main;
} else {
- /* if no workload, just return */
- if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
- cpu_osn_var(cpu)->sampling = true;
- barrier();
- return 0;
- }
snprintf(comm, 24, "osnoise/%d", cpu);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 03/10] tracing/osnoise: Start all eligible cpus from hotplug workfn
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
2026-10-28 22:01 ` [PATCH v6 01/10] tracing/osnoise: Variable cleanup Crystal Wood
2026-10-28 22:01 ` [PATCH v6 02/10] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:01 ` [PATCH v6 04/10] tracing/osnoise: Hold a ref on osn->kthread Crystal Wood
` (6 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Previously, a start could be lost if multiple cpus get onlined
before the workqueue has a chance to run.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v5: new patch
I have this in my notes as being a Sashiko comment, though I can't
find the source now. I wasn't able to reproduce the race given how
slow and serialized cpu hotplug operations are, but it's possible.
On the other hand, user threads detach on cpu offline with no mechanism
for reattaching (other than userspace figuring it out and reopening the
fd), and you really probably shouldn't be offlining cpus that you're
measuring with tools like this, so you could make an argument for removing
the restart mechanism entirely... it'd certainly shrink the potential bug
surface.
This also eliminates the need for the "Always check OSN_WORKLOAD" patch.
v6:
Removed the OSN_WORKLOAD check from this function, and revived the
"Always check OSN_WORKLOAD", so that start_kthread() can set osn->sampling
in the NO_OSN_WORKLOAD osnoise case
---
kernel/trace/trace_osnoise.c | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index a6f7fbb342b6..ff7277052af2 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -2123,7 +2123,7 @@ static int start_per_cpu_kthreads(void)
#ifdef CONFIG_HOTPLUG_CPU
static void osnoise_hotplug_workfn(struct work_struct *dummy)
{
- unsigned int cpu = smp_processor_id();
+ unsigned int cpu;
guard(mutex)(&trace_types_lock);
@@ -2133,13 +2133,10 @@ static void osnoise_hotplug_workfn(struct work_struct *dummy)
guard(cpus_read_lock)();
guard(mutex)(&interface_lock);
- if (!cpu_online(cpu))
- return;
-
- if (!cpumask_test_cpu(cpu, &osnoise_cpumask))
- return;
-
- start_kthread(cpu);
+ for_each_online_cpu(cpu) {
+ if (cpumask_test_cpu(cpu, &osnoise_cpumask))
+ start_kthread(cpu);
+ }
}
static DECLARE_WORK(osnoise_hotplug_work, osnoise_hotplug_workfn);
@@ -2149,7 +2146,7 @@ static DECLARE_WORK(osnoise_hotplug_work, osnoise_hotplug_workfn);
*/
static int osnoise_cpu_init(unsigned int cpu)
{
- schedule_work_on(cpu, &osnoise_hotplug_work);
+ schedule_work(&osnoise_hotplug_work);
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 04/10] tracing/osnoise: Hold a ref on osn->kthread
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
` (2 preceding siblings ...)
2026-10-28 22:01 ` [PATCH v6 03/10] tracing/osnoise: Start all eligible cpus from hotplug workfn Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:01 ` [PATCH v6 05/10] tracing/osnoise: Per-cpu mutexes and fd detachment Crystal Wood
` (5 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Deal with early kthread termination by holding a reference to the
task_struct and always running the cleanup code in stop_kthread(), rather
than with hacks like osnoise_migration_pending().
This avoids a potential deadlock when a later patch switches from xchg
to using a lock to protect osn->kthread updates, and also deals with
any other situations where a kthread might exit early.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: New patch. I did this in response to Sashiko pointing out a deadlock
in v1 with the use of osn->lock in osnoise_migration_pending. That might
no longer be an issue now that detach_lock is split out, but this is still
the cleaner and more robust approach.
---
kernel/trace/trace_osnoise.c | 17 ++++-------------
1 file changed, 4 insertions(+), 13 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index ff7277052af2..5a2294e9c70a 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -1691,17 +1691,6 @@ static inline int osnoise_migration_pending(void)
* thus: taint and leave to unblock the blocked thread.
*/
osnoise_taint("migration requested to osnoise threads, leaving.");
-
- /*
- * Unset this thread from the threads managed by the interface.
- * The tracers are responsible for cleaning their env before
- * exiting.
- */
- mutex_lock(&interface_lock);
- this_cpu_osn_var()->kthread = NULL;
- cpumask_clear_cpu(smp_processor_id(), &kthread_cpumask);
- mutex_unlock(&interface_lock);
-
return 1;
}
@@ -2001,8 +1990,8 @@ static void stop_kthread(unsigned int cpu)
* by killing the thread. NOTE: this is a purpose specific file.
*/
kill_pid(kthread->thread_pid, SIGKILL, 1);
- put_task_struct(kthread);
}
+ put_task_struct(kthread);
} else {
/* if no workload, just return */
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
@@ -2060,15 +2049,17 @@ static int start_kthread(unsigned int cpu)
snprintf(comm, 24, "osnoise/%d", cpu);
}
- kthread = kthread_run_on_cpu(main, NULL, cpu, comm);
+ kthread = kthread_create_on_cpu(main, NULL, cpu, comm);
if (IS_ERR(kthread)) {
pr_err(BANNER "could not start sampling thread\n");
return -ENOMEM;
}
+ get_task_struct(kthread);
cpu_osn_var(cpu)->kthread = kthread;
cpumask_set_cpu(cpu, &kthread_cpumask);
+ wake_up_process(kthread);
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 05/10] tracing/osnoise: Per-cpu mutexes and fd detachment
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
` (3 preceding siblings ...)
2026-10-28 22:01 ` [PATCH v6 04/10] tracing/osnoise: Hold a ref on osn->kthread Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:20 ` sashiko-bot
2026-10-28 22:01 ` [PATCH v6 06/10] tracing/osnoise: Split out per-cpu variable resets Crystal Wood
` (4 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Clean up a variety of synchronization issues and related bandaids
by having a per-cpu mutex that guards changes to kthread, and
another than guards fd open/close/revoke.
Replace the SIGKILL hack for userspace timerlat threads (that doesn't
even work, because we don't wait for the process to actually die) with
a mutex-protected detachment mechanism. The mutex should be uncontended
during normal timerlat_fd_read() usage.
This also eliminates the need for the "quick fix" of e6a53481da292
("tracing/timerlat: Only clear timer if a kthread exists").
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2:
- Use a separate lock for detachment, so that we can wake the thread that
holds the detach lock, while holding osn->lock to make sure the thread
is valid.
- Return -EINVAL if detached while waiting, and -ERESTARTSYS on a signal
- Init mutex on every possible cpu (as pointed out by Sashiko)
- Update documentation for SIGKILL removal
v3:
- Clear osn_var->sampling in stop_kthread(), as reporteed by Sashiko
for the osnoise non-workload case
v5:
- Fix some patch-shuffling bugs in timerlat_fd_detach() that were previously
undone by the reset patch.
v6:
- Move migration checks after the detach lock
- Clear osn->sampling on signal exit. It's not expected that the signal will
be non-fatal given the limited use case, but this gives somewhat more reasonable
behavior if it does get restarted. Or do we want to fail more noisily
in that case?
- Remove detachment language from user doc that doesn't apply in all scenarios
---
Documentation/trace/timerlat-tracer.rst | 5 +-
kernel/trace/trace_osnoise.c | 320 +++++++++++++++---------
2 files changed, 209 insertions(+), 116 deletions(-)
diff --git a/Documentation/trace/timerlat-tracer.rst b/Documentation/trace/timerlat-tracer.rst
index 68d429d454a5..e23467376ff5 100644
--- a/Documentation/trace/timerlat-tracer.rst
+++ b/Documentation/trace/timerlat-tracer.rst
@@ -220,8 +220,9 @@ After reporting the total latency, timerlat will restart the cycle, arm
a timer, and go to sleep for the following activation.
If at any time one of the conditions is broken, e.g., the thread migrates
-while in user space, or the timerlat tracer is disabled, the SIG_KILL
-signal will be sent to the user-space thread.
+while in user space, or the timerlat tracer is disabled, subsequent usage
+will fail with EINVAL. Note that older kernels may deliver a SIGKILL
+instead.
Here is an basic example of user-space code for timerlat::
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 5a2294e9c70a..ce6d02346ea4 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -222,19 +222,30 @@ struct osn_thread {
u64 delta_start;
};
+struct fd_data {
+ struct task_struct *thread;
+ void (*detach)(struct fd_data *fdd);
+ int cpu;
+};
+
/*
* Runtime information: this structure saves the runtime information used by
* one sampling thread.
*/
struct osnoise_variables {
+ struct mutex lock; /* covers kthread and fdd changes */
struct task_struct *kthread;
- bool sampling;
- pid_t pid;
- struct osn_nmi nmi;
- struct osn_irq irq;
- struct osn_softirq softirq;
- struct osn_thread thread;
- local_t int_counter;
+ struct fd_data *fdd;
+
+ struct_group(zero,
+ bool sampling;
+ pid_t pid;
+ struct osn_nmi nmi;
+ struct osn_irq irq;
+ struct osn_softirq softirq;
+ struct osn_thread thread;
+ local_t int_counter;
+ );
};
/*
@@ -265,12 +276,25 @@ static struct mutex interface_lock;
* Runtime information for the timer mode.
*/
struct timerlat_variables {
- struct task_struct *thread;
- struct hrtimer timer;
- u64 abs_period;
- bool tracing_thread;
- u64 count;
- bool uthread_migrate;
+ /*
+ * This is separate from osn->lock in order to allow waking
+ * the waiting thread during forced detach, without having to
+ * wait for the next period (and possibly longer, due to
+ * priority-based lock stealing).
+ *
+ * Both locks must be held during the actual detachment.
+ */
+ struct mutex detach_lock;
+
+ struct_group(zero,
+ struct task_struct *thread;
+ struct hrtimer timer;
+ u64 abs_period;
+ bool tracing_thread;
+ u64 count;
+ bool uthread_migrate;
+ bool detach;
+ );
};
static DEFINE_PER_CPU(struct timerlat_variables, per_cpu_timerlat_var);
@@ -301,26 +325,20 @@ static inline void tlat_var_reset(void)
struct timerlat_variables *tlat_var;
int cpu;
- /* Synchronize with the timerlat interfaces */
- mutex_lock(&interface_lock);
-
/*
* So far, all the values are initialized as 0, so
* zeroing the structure is perfect.
*/
for_each_online_cpu(cpu) {
tlat_var = cpu_tmr_var(cpu);
- if (tlat_var->thread)
- hrtimer_cancel(&tlat_var->timer);
- memset(tlat_var, 0, sizeof(*tlat_var));
+ WARN_ON_ONCE(tlat_var->thread);
+ memset(&tlat_var->zero, 0, sizeof(tlat_var->zero));
}
/*
* Reset also align_next, to be filled by a new offset by the first timerlat
* thread that wakes up, if TIMERLAT_ALIGN is set.
*/
atomic64_set(&align_next, 0);
-
- mutex_unlock(&interface_lock);
}
#else /* CONFIG_TIMERLAT_TRACER */
#define tlat_var_reset() do {} while (0)
@@ -340,7 +358,10 @@ static inline void osn_var_reset(void)
*/
for_each_online_cpu(cpu) {
osn_var = cpu_osn_var(cpu);
- memset(osn_var, 0, sizeof(*osn_var));
+
+ WARN_ON(osn_var->kthread);
+ WARN_ON(osn_var->fdd);
+ memset(&osn_var->zero, 0, sizeof(osn_var->zero));
}
}
@@ -1883,10 +1904,18 @@ static int wait_next_period(struct timerlat_variables *tlat)
}
set_current_state(TASK_INTERRUPTIBLE);
+ if (!tlat->detach && !signal_pending(current)) {
+ hrtimer_start(&tlat->timer, next_abs_period, HRTIMER_MODE_ABS_PINNED_HARD);
+ schedule();
+ }
+ __set_current_state(TASK_RUNNING);
- hrtimer_start(&tlat->timer, next_abs_period, HRTIMER_MODE_ABS_PINNED_HARD);
- schedule();
- return 1;
+ if (tlat->detach || signal_pending(current)) {
+ hrtimer_cancel(&tlat->timer);
+ return -1;
+ }
+
+ return 0;
}
/*
@@ -1960,7 +1989,9 @@ static int timerlat_main(void *data)
wait_next_period(tlat);
}
+ WARN_ON_ONCE(hrtimer_active(&tlat->timer));
hrtimer_cancel(&tlat->timer);
+ tlat->thread = NULL;
migrate_enable();
return 0;
}
@@ -1976,32 +2007,24 @@ static int timerlat_main(void *data)
*/
static void stop_kthread(unsigned int cpu)
{
- struct task_struct *kthread;
+ struct osnoise_variables *osn_var = cpu_osn_var(cpu);
- kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
- if (kthread) {
- if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask) &&
- !WARN_ON(!test_bit(OSN_WORKLOAD, &osnoise_options))) {
- kthread_stop(kthread);
- } else if (!WARN_ON(test_bit(OSN_WORKLOAD, &osnoise_options))) {
- /*
- * This is a user thread waiting on the timerlat_fd. We need
- * to close all users, and the best way to guarantee this is
- * by killing the thread. NOTE: this is a purpose specific file.
- */
- kill_pid(kthread->thread_pid, SIGKILL, 1);
- }
- put_task_struct(kthread);
- } else {
- /* if no workload, just return */
- if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
- /*
- * This is set in the osnoise tracer case.
- */
- cpu_osn_var(cpu)->sampling = false;
- barrier();
- }
+ mutex_lock(&osn_var->lock);
+
+ if (osn_var->fdd) {
+ WARN_ON(test_bit(OSN_WORKLOAD, &osnoise_options));
+ WARN_ON(osn_var->kthread);
+ osn_var->fdd->detach(osn_var->fdd);
+ } else if (osn_var->kthread) {
+ WARN_ON(!cpumask_test_and_clear_cpu(cpu, &kthread_cpumask));
+ WARN_ON(!test_bit(OSN_WORKLOAD, &osnoise_options));
+ kthread_stop(osn_var->kthread);
+ put_task_struct(osn_var->kthread);
+ osn_var->kthread = NULL;
}
+
+ osn_var->sampling = false;
+ mutex_unlock(&osn_var->lock);
}
/*
@@ -2027,19 +2050,24 @@ static void stop_per_cpu_kthreads(void)
*/
static int start_kthread(unsigned int cpu)
{
+ struct osnoise_variables *osn = cpu_osn_var(cpu);
struct task_struct *kthread;
void *main = osnoise_main;
char comm[24];
+ int ret = 0;
+
+ lockdep_assert_held(&trace_types_lock);
+ mutex_lock(&osn->lock);
/* Do not start a new thread if it is already running */
- if (cpu_osn_var(cpu)->kthread)
- return 0;
+ if (osn->kthread)
+ goto out;
/* Can still happen with timerlat via cpu hotplug */
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
if (!timerlat_enabled())
osn->sampling = true;
- return 0;
+ goto out;
}
if (timerlat_enabled()) {
@@ -2053,15 +2081,18 @@ static int start_kthread(unsigned int cpu)
if (IS_ERR(kthread)) {
pr_err(BANNER "could not start sampling thread\n");
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto out;
}
get_task_struct(kthread);
- cpu_osn_var(cpu)->kthread = kthread;
+ osn->kthread = kthread;
cpumask_set_cpu(cpu, &kthread_cpumask);
wake_up_process(kthread);
- return 0;
+out:
+ mutex_unlock(&osn->lock);
+ return ret;
}
/*
@@ -2087,15 +2118,8 @@ static int start_per_cpu_kthreads(void)
*/
cpumask_and(current_mask, cpu_online_mask, &osnoise_cpumask);
- for_each_possible_cpu(cpu) {
- if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask)) {
- struct task_struct *kthread;
-
- kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
- if (!WARN_ON(!kthread))
- kthread_stop(kthread);
- }
- }
+ for_each_possible_cpu(cpu)
+ WARN_ON(cpumask_test_cpu(cpu, &kthread_cpumask));
for_each_cpu(cpu, current_mask) {
retval = start_kthread(cpu);
@@ -2415,11 +2439,46 @@ osnoise_cpus_write(struct file *filp, const char __user *ubuf, size_t count,
}
#ifdef CONFIG_TIMERLAT_TRACER
+static void timerlat_fd_detach(struct fd_data *fdd)
+{
+ struct osnoise_variables *osn;
+ struct timerlat_variables *tlat;
+
+ osn = cpu_osn_var(fdd->cpu);
+ tlat = cpu_tmr_var(fdd->cpu);
+
+ lockdep_assert_held(&osn->lock);
+ if (WARN_ON(fdd != osn->fdd))
+ return;
+
+ WARN_ON(osn->kthread);
+ WARN_ON(fdd->thread != tlat->thread);
+ tlat->detach = true;
+ wake_up_process(tlat->thread);
+
+ mutex_lock(&tlat->detach_lock);
+
+ osn->sampling = 0;
+ osn->pid = 0;
+
+ WARN_ON_ONCE(hrtimer_active(&tlat->timer));
+ hrtimer_cancel(&tlat->timer);
+ put_task_struct(fdd->thread);
+
+ fdd->thread = NULL;
+ osn->fdd = NULL;
+ memset(&tlat->zero, 0, sizeof(tlat->zero));
+
+ mutex_unlock(&tlat->detach_lock);
+}
+
static int timerlat_fd_open(struct inode *inode, struct file *file)
{
+ struct fd_data *fdd;
struct osnoise_variables *osn_var;
struct timerlat_variables *tlat;
long cpu = (long) inode->i_cdev;
+ int ret = 0;
mutex_lock(&interface_lock);
@@ -2435,16 +2494,16 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
migrate_disable();
osn_var = this_cpu_osn_var();
+ tlat = this_cpu_tmr_var();
+ mutex_lock(&osn_var->lock);
- /*
- * The osn_var->pid holds the single access to this file.
- */
- if (osn_var->pid) {
- mutex_unlock(&interface_lock);
- migrate_enable();
- return -EBUSY;
+ if (osn_var->fdd) {
+ ret = -EBUSY;
+ goto err;
}
+ WARN_ON_ONCE(osn_var->kthread);
+
/*
* timerlat tracer is a per-cpu tracer. Check if the user-space too
* is pinned to a single CPU. The tracer laters monitor if the task
@@ -2453,24 +2512,35 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
* setup.
*/
if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id()) {
- mutex_unlock(&interface_lock);
- migrate_enable();
- return -EPERM;
+ ret = -EPERM;
+ goto err;
+ }
+
+ fdd = kmalloc(sizeof(*fdd), GFP_KERNEL);
+ if (!fdd) {
+ ret = -ENOMEM;
+ goto err;
}
/*
* From now on, it is good to go.
*/
- file->private_data = inode->i_cdev;
+ fdd->thread = current;
+ fdd->detach = timerlat_fd_detach;
+ fdd->cpu = cpu;
+ file->private_data = fdd;
+ tlat->tracing_thread = false;
+ tlat->thread = current;
get_task_struct(current);
- osn_var->kthread = current;
osn_var->pid = current->pid;
+ osn_var->fdd = fdd;
/*
* Setup is done.
*/
+ mutex_unlock(&osn_var->lock);
mutex_unlock(&interface_lock);
tlat = this_cpu_tmr_var();
@@ -2480,6 +2550,12 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
migrate_enable();
return 0;
+
+err:
+ mutex_unlock(&osn_var->lock);
+ mutex_unlock(&interface_lock);
+ migrate_enable();
+ return ret;
};
/*
@@ -2495,37 +2571,44 @@ static ssize_t
timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
loff_t *ppos)
{
- long cpu = (long) file->private_data;
+ struct fd_data *fdd = file->private_data;
struct osnoise_variables *osn_var;
struct timerlat_variables *tlat;
struct timerlat_sample s;
s64 diff;
u64 now;
+ int ret = 0;
migrate_disable();
tlat = this_cpu_tmr_var();
+ osn_var = this_cpu_osn_var();
+
+ /* In normal usage, this should always be uncontended. */
+ mutex_lock(&tlat->detach_lock);
+ if (fdd != osn_var->fdd || current != fdd->thread) {
+ ret = -EINVAL;
+ goto out;
+ }
/*
* While in user-space, the thread is migratable. There is nothing
* we can do about it.
* So, if the thread is running on another CPU, stop the machinery.
*/
- if (cpu == smp_processor_id()) {
+ if (fdd->cpu == smp_processor_id()) {
if (tlat->uthread_migrate) {
- migrate_enable();
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
} else {
- cpu_tmr_var(cpu)->uthread_migrate = 1;
+ cpu_tmr_var(fdd->cpu)->uthread_migrate = 1;
osnoise_taint("timerlat user thread migrate\n");
osnoise_stop_tracing();
- migrate_enable();
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
- osn_var = this_cpu_osn_var();
-
/*
* The timerlat in user-space runs in a different order:
* the read() starts from the execution of the previous occurrence,
@@ -2560,9 +2643,6 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
}
} else {
- tlat->tracing_thread = false;
- tlat->thread = current;
-
/* Annotate now to drift new period */
tlat->abs_period = hrtimer_cb_get_time(&tlat->timer);
@@ -2570,7 +2650,20 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
/* wait for the next period */
- wait_next_period(tlat);
+ if (wait_next_period(tlat) < 0) {
+ if (signal_pending(current))
+ ret = -ERESTARTSYS;
+ else
+ ret = -EINVAL;
+
+ osn_var->sampling = false;
+ goto out;
+ }
+
+ if (WARN_ON_ONCE(fdd != osn_var->fdd)) {
+ ret = -EINVAL;
+ goto out;
+ }
/* This is the wakeup from this cycle */
now = ktime_to_ns(hrtimer_cb_get_time(&tlat->timer));
@@ -2597,39 +2690,24 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
out:
+ mutex_unlock(&tlat->detach_lock);
migrate_enable();
- return 0;
+ return ret;
}
static int timerlat_fd_release(struct inode *inode, struct file *file)
{
+ struct fd_data *fdd = file->private_data;
struct osnoise_variables *osn_var;
- struct timerlat_variables *tlat_var;
- long cpu = (long) file->private_data;
-
- migrate_disable();
- mutex_lock(&interface_lock);
-
- osn_var = cpu_osn_var(cpu);
- tlat_var = cpu_tmr_var(cpu);
- if (tlat_var->thread)
- hrtimer_cancel(&tlat_var->timer);
- memset(tlat_var, 0, sizeof(*tlat_var));
+ osn_var = cpu_osn_var(fdd->cpu);
- osn_var->sampling = 0;
- osn_var->pid = 0;
+ mutex_lock(&osn_var->lock);
+ if (fdd == osn_var->fdd)
+ timerlat_fd_detach(fdd);
+ mutex_unlock(&osn_var->lock);
- /*
- * We are leaving, not being stopped... see stop_kthread();
- */
- if (osn_var->kthread) {
- put_task_struct(osn_var->kthread);
- osn_var->kthread = NULL;
- }
-
- mutex_unlock(&interface_lock);
- migrate_enable();
+ kfree(fdd);
return 0;
}
#endif
@@ -3144,6 +3222,14 @@ static struct tracer timerlat_tracer __read_mostly = {
__init static int init_timerlat_tracer(void)
{
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ struct timerlat_variables *tlat = cpu_tmr_var(cpu);
+
+ mutex_init(&tlat->detach_lock);
+ }
+
return register_tracer(&timerlat_tracer);
}
#else /* CONFIG_TIMERLAT_TRACER */
@@ -3155,10 +3241,16 @@ __init static int init_timerlat_tracer(void)
__init static int init_osnoise_tracer(void)
{
- int ret;
+ int ret, cpu;
mutex_init(&interface_lock);
+ for_each_possible_cpu(cpu) {
+ struct osnoise_variables *osn = cpu_osn_var(cpu);
+
+ mutex_init(&osn->lock);
+ }
+
cpumask_copy(&osnoise_cpumask, cpu_all_mask);
ret = register_tracer(&osnoise_tracer);
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 06/10] tracing/osnoise: Split out per-cpu variable resets
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
` (4 preceding siblings ...)
2026-10-28 22:01 ` [PATCH v6 05/10] tracing/osnoise: Per-cpu mutexes and fd detachment Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:24 ` sashiko-bot
2026-10-28 22:01 ` [PATCH v6 07/10] tracing/osnoise: kthreads: Verify CPU after migrate_disable() Crystal Wood
` (3 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Reset per-cpu state every time we stop a kthread or detach an fd, rather
than assume everything gets cleaned up properly, and avoiding the need
to explicitly zero individual fields.
Also, stop initializing tlat->thread lazily, add some debug checks to
detect an unclean state, and clear osnoise_data.tainted in
osn_var_reset().
The interrupt counters do not really need to be reset, and resetting them
on CPU detachment is racy because the hooks are still in place, so move
them out of the zero group.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: New patch. Without this, I was seeing sampling = 1 leaking from a
kthread context to an fd context, which confused the latter into
operating with tlat->thread set to the old kernel thread, causing the fd
thread to hang.
v3: Reset the per-cpu context unconditionally in stop_kthread(), so sampling
gets cleared as noted in patch 3/9
v4: Only reset the cpu data if the fdd is a match
v6: Revive patch, but remove the interrupt counters from the zeroed area
---
kernel/trace/trace_osnoise.c | 121 +++++++++++++++++------------------
1 file changed, 60 insertions(+), 61 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index ce6d02346ea4..f07b62a5ccb8 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -236,15 +236,15 @@ struct osnoise_variables {
struct mutex lock; /* covers kthread and fdd changes */
struct task_struct *kthread;
struct fd_data *fdd;
+ struct osn_nmi nmi;
+ struct osn_irq irq;
+ struct osn_softirq softirq;
+ struct osn_thread thread;
+ local_t int_counter;
struct_group(zero,
bool sampling;
pid_t pid;
- struct osn_nmi nmi;
- struct osn_irq irq;
- struct osn_softirq softirq;
- struct osn_thread thread;
- local_t int_counter;
);
};
@@ -317,23 +317,19 @@ static inline struct timerlat_variables *cpu_tmr_var(int cpu)
return per_cpu_ptr(&per_cpu_timerlat_var, cpu);
}
+static inline void tlat_reset_cpu(int cpu)
+{
+ struct timerlat_variables *tlat = cpu_tmr_var(cpu);
+
+ WARN_ON_ONCE(tlat->thread);
+ memset(&tlat->zero, 0, sizeof(tlat->zero));
+}
+
/*
- * tlat_var_reset - Reset the values of the given timerlat_variables
+ * tlat_var_reset - Reset non-per-cpu state
*/
static inline void tlat_var_reset(void)
{
- struct timerlat_variables *tlat_var;
- int cpu;
-
- /*
- * So far, all the values are initialized as 0, so
- * zeroing the structure is perfect.
- */
- for_each_online_cpu(cpu) {
- tlat_var = cpu_tmr_var(cpu);
- WARN_ON_ONCE(tlat_var->thread);
- memset(&tlat_var->zero, 0, sizeof(tlat_var->zero));
- }
/*
* Reset also align_next, to be filled by a new offset by the first timerlat
* thread that wakes up, if TIMERLAT_ALIGN is set.
@@ -341,38 +337,12 @@ static inline void tlat_var_reset(void)
atomic64_set(&align_next, 0);
}
#else /* CONFIG_TIMERLAT_TRACER */
-#define tlat_var_reset() do {} while (0)
-#endif /* CONFIG_TIMERLAT_TRACER */
-
-/*
- * osn_var_reset - Reset the values of the given osnoise_variables
- */
-static inline void osn_var_reset(void)
+static inline void tlat_reset_cpu(int cpu)
{
- struct osnoise_variables *osn_var;
- int cpu;
-
- /*
- * So far, all the values are initialized as 0, so
- * zeroing the structure is perfect.
- */
- for_each_online_cpu(cpu) {
- osn_var = cpu_osn_var(cpu);
-
- WARN_ON(osn_var->kthread);
- WARN_ON(osn_var->fdd);
- memset(&osn_var->zero, 0, sizeof(osn_var->zero));
- }
}
-/*
- * osn_var_reset_all - Reset the value of all per-cpu osnoise_variables
- */
-static inline void osn_var_reset_all(void)
-{
- osn_var_reset();
- tlat_var_reset();
-}
+#define tlat_var_reset() do {} while (0)
+#endif /* CONFIG_TIMERLAT_TRACER */
/*
* Tells NMIs to call back to the osnoise tracer to record timestamps.
@@ -407,6 +377,36 @@ static struct osnoise_data {
#endif
};
+static inline void osn_reset_cpu(int cpu)
+{
+ struct osnoise_variables *osn = cpu_osn_var(cpu);
+
+ WARN_ON(osn->kthread);
+ WARN_ON(osn->fdd);
+ memset(&osn->zero, 0, sizeof(osn->zero));
+ tlat_reset_cpu(cpu);
+}
+
+/*
+ * osn_var_reset - Reset non-per-cpu state, and check for stale data
+ */
+static inline void osn_var_reset(void)
+{
+ struct osnoise_variables *osn;
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ osn = cpu_osn_var(cpu);
+
+ WARN_ON(osn->kthread);
+ WARN_ON(osn->fdd);
+ WARN_ON(osn->sampling);
+ }
+
+ tlat_var_reset();
+ osnoise_data.tainted = false;
+}
+
#ifdef CONFIG_TIMERLAT_TRACER
static inline bool timerlat_enabled(void)
{
@@ -2023,7 +2023,7 @@ static void stop_kthread(unsigned int cpu)
osn_var->kthread = NULL;
}
- osn_var->sampling = false;
+ osn_reset_cpu(cpu);
mutex_unlock(&osn_var->lock);
}
@@ -2451,25 +2451,22 @@ static void timerlat_fd_detach(struct fd_data *fdd)
if (WARN_ON(fdd != osn->fdd))
return;
- WARN_ON(osn->kthread);
WARN_ON(fdd->thread != tlat->thread);
tlat->detach = true;
wake_up_process(tlat->thread);
mutex_lock(&tlat->detach_lock);
- osn->sampling = 0;
- osn->pid = 0;
-
WARN_ON_ONCE(hrtimer_active(&tlat->timer));
hrtimer_cancel(&tlat->timer);
put_task_struct(fdd->thread);
fdd->thread = NULL;
+ tlat->thread = NULL;
osn->fdd = NULL;
- memset(&tlat->zero, 0, sizeof(tlat->zero));
mutex_unlock(&tlat->detach_lock);
+
}
static int timerlat_fd_open(struct inode *inode, struct file *file)
@@ -2530,8 +2527,6 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
fdd->cpu = cpu;
file->private_data = fdd;
- tlat->tracing_thread = false;
- tlat->thread = current;
get_task_struct(current);
osn_var->pid = current->pid;
@@ -2544,7 +2539,11 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
mutex_unlock(&interface_lock);
tlat = this_cpu_tmr_var();
- tlat->count = 0;
+ WARN_ON(tlat->thread);
+ WARN_ON(tlat->count != 0);
+ WARN_ON(tlat->tracing_thread);
+ WARN_ON(osn_var->sampling);
+ tlat->thread = current;
hrtimer_setup(&tlat->timer, timerlat_irq, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD);
@@ -2698,13 +2697,13 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
static int timerlat_fd_release(struct inode *inode, struct file *file)
{
struct fd_data *fdd = file->private_data;
- struct osnoise_variables *osn_var;
-
- osn_var = cpu_osn_var(fdd->cpu);
+ struct osnoise_variables *osn_var = cpu_osn_var(fdd->cpu);
mutex_lock(&osn_var->lock);
- if (fdd == osn_var->fdd)
+ if (fdd == osn_var->fdd) {
timerlat_fd_detach(fdd);
+ osn_reset_cpu(fdd->cpu);
+ }
mutex_unlock(&osn_var->lock);
kfree(fdd);
@@ -3018,7 +3017,7 @@ static int osnoise_workload_start(void)
if (osnoise_has_registered_instances())
return 0;
- osn_var_reset_all();
+ osn_var_reset();
retval = osnoise_hook_events();
if (retval)
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 07/10] tracing/osnoise: kthreads: Verify CPU after migrate_disable()
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
` (5 preceding siblings ...)
2026-10-28 22:01 ` [PATCH v6 06/10] tracing/osnoise: Split out per-cpu variable resets Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:01 ` [PATCH v6 08/10] tracing/osnoise: Take trace_types_lock in timerlat_fd_open Crystal Wood
` (2 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Pass osn vars as the kthread arg, and make sure it matches where ve're
running in case of (e.g.) hotplug issues.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2:
- Do it for osnoise too, not just timerlat
- I haven't been able to reproduce this in a while, and I'm not seeing the
hole in the hotplug sync that would allow it, but I also don't see what
would have allowed it in v1 (maybe it was in some pre-v1 iteration? or
maybe just a tiny race window).
So we can maybe drop this patch, though it still feels better to explicitly
splat if problems happen rather than rely on this_cpu_osn_var() blowing up
due to the affinity being set to multiple cpus.
---
kernel/trace/trace_osnoise.c | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index f07b62a5ccb8..614adac25a89 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -241,6 +241,7 @@ struct osnoise_variables {
struct osn_softirq softirq;
struct osn_thread thread;
local_t int_counter;
+ int cpu;
struct_group(zero,
bool sampling;
@@ -1723,6 +1724,7 @@ static inline int osnoise_migration_pending(void)
*/
static int osnoise_main(void *data)
{
+ struct osnoise_variables *osn = data;
unsigned long flags;
/*
@@ -1733,6 +1735,11 @@ static int osnoise_main(void *data)
* flag.
*/
migrate_disable();
+ if (WARN_ON_ONCE(osn->cpu != smp_processor_id())) {
+ migrate_enable();
+ return 1;
+ }
+
raw_spin_lock_irqsave(¤t->pi_lock, flags);
current->flags &= ~(PF_NO_SETAFFINITY);
raw_spin_unlock_irqrestore(¤t->pi_lock, flags);
@@ -1923,8 +1930,8 @@ static int wait_next_period(struct timerlat_variables *tlat)
*/
static int timerlat_main(void *data)
{
- struct osnoise_variables *osn_var = this_cpu_osn_var();
- struct timerlat_variables *tlat = this_cpu_tmr_var();
+ struct osnoise_variables *osn_var = data;
+ struct timerlat_variables *tlat;
struct timerlat_sample s;
struct sched_param sp;
unsigned long flags;
@@ -1944,6 +1951,13 @@ static int timerlat_main(void *data)
* flag.
*/
migrate_disable();
+ if (WARN_ON_ONCE(osn_var->cpu != smp_processor_id())) {
+ migrate_enable();
+ return 1;
+ }
+
+ tlat = this_cpu_tmr_var();
+
raw_spin_lock_irqsave(¤t->pi_lock, flags);
current->flags &= ~(PF_NO_SETAFFINITY);
raw_spin_unlock_irqrestore(¤t->pi_lock, flags);
@@ -2077,7 +2091,7 @@ static int start_kthread(unsigned int cpu)
snprintf(comm, 24, "osnoise/%d", cpu);
}
- kthread = kthread_create_on_cpu(main, NULL, cpu, comm);
+ kthread = kthread_create_on_cpu(main, cpu_osn_var(cpu), cpu, comm);
if (IS_ERR(kthread)) {
pr_err(BANNER "could not start sampling thread\n");
@@ -3248,6 +3262,7 @@ __init static int init_osnoise_tracer(void)
struct osnoise_variables *osn = cpu_osn_var(cpu);
mutex_init(&osn->lock);
+ osn->cpu = cpu;
}
cpumask_copy(&osnoise_cpumask, cpu_all_mask);
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 08/10] tracing/osnoise: Take trace_types_lock in timerlat_fd_open
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
` (6 preceding siblings ...)
2026-10-28 22:01 ` [PATCH v6 07/10] tracing/osnoise: kthreads: Verify CPU after migrate_disable() Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:01 ` [PATCH v6 09/10] tracing/osnoise: Print taint message for timerlat Crystal Wood
2026-10-28 22:01 ` [PATCH v6 10/10] tracing/osnoise: Refuse timerlat fd open if tracer inactive Crystal Wood
9 siblings, 0 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
This prevents a user fd from being opened when osnoise_options_write()
has already stopped threads but not yet set OSN_WORKLOAD, and matches what
all callers of start_kthread() do.
The original intent was probably for interface_lock to cover things like
this, but it never covered the full race window, and currently can't
due to lock order issues stemming from using interface_lock in
osnoise_sleep(). There does not appear to be any remaining reason
to take interface_lock in timerlat_fd_open(), so remove it.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: convert rest of function to guards
Do we really need that interface_lock in osnoise_sleep()? Technically
we should probably be grabbing it every time we access osnoise_data, but
we don't seem to do so anywhere else. Perhaps this was due to concerns
about the atomicity of "period - runtime", but that's just synchronization
theater given that userspace has no way of writing to both atomically.
In any case, we shouldn't use the same lock for protecting osnoise_data
members that we use for major reconfiguration procedures.
---
kernel/trace/trace_osnoise.c | 43 +++++++++---------------------------
1 file changed, 10 insertions(+), 33 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 614adac25a89..9e37733f7395 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -2489,29 +2489,24 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
struct osnoise_variables *osn_var;
struct timerlat_variables *tlat;
long cpu = (long) inode->i_cdev;
- int ret = 0;
- mutex_lock(&interface_lock);
+ guard(mutex)(&trace_types_lock);
/*
* This file is accessible only if timerlat is enabled, and
* NO_OSNOISE_WORKLOAD is set.
*/
- if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options)) {
- mutex_unlock(&interface_lock);
+ if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options))
return -EINVAL;
- }
- migrate_disable();
+ guard(migrate)();
osn_var = this_cpu_osn_var();
tlat = this_cpu_tmr_var();
- mutex_lock(&osn_var->lock);
+ guard(mutex)(&osn_var->lock);
- if (osn_var->fdd) {
- ret = -EBUSY;
- goto err;
- }
+ if (osn_var->fdd)
+ return -EBUSY;
WARN_ON_ONCE(osn_var->kthread);
@@ -2522,16 +2517,12 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
* worth doing this basic acceptance test to avoid obviusly wrong
* setup.
*/
- if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id()) {
- ret = -EPERM;
- goto err;
- }
+ if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id())
+ return -EPERM;
fdd = kmalloc(sizeof(*fdd), GFP_KERNEL);
- if (!fdd) {
- ret = -ENOMEM;
- goto err;
- }
+ if (!fdd)
+ return -ENOMEM;
/*
* From now on, it is good to go.
@@ -2546,12 +2537,6 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
osn_var->pid = current->pid;
osn_var->fdd = fdd;
- /*
- * Setup is done.
- */
- mutex_unlock(&osn_var->lock);
- mutex_unlock(&interface_lock);
-
tlat = this_cpu_tmr_var();
WARN_ON(tlat->thread);
WARN_ON(tlat->count != 0);
@@ -2560,15 +2545,7 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
tlat->thread = current;
hrtimer_setup(&tlat->timer, timerlat_irq, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD);
-
- migrate_enable();
return 0;
-
-err:
- mutex_unlock(&osn_var->lock);
- mutex_unlock(&interface_lock);
- migrate_enable();
- return ret;
};
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 09/10] tracing/osnoise: Print taint message for timerlat
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
` (7 preceding siblings ...)
2026-10-28 22:01 ` [PATCH v6 08/10] tracing/osnoise: Take trace_types_lock in timerlat_fd_open Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
2026-10-28 22:01 ` [PATCH v6 10/10] tracing/osnoise: Refuse timerlat fd open if tracer inactive Crystal Wood
9 siblings, 0 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Provide taint information the same as is done in print_osnoise_headers().
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: new patch
---
kernel/trace/trace_osnoise.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 9e37733f7395..ff4744c0701e 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -576,6 +576,9 @@ static void record_osnoise_sample(struct osnoise_sample *sample)
#ifdef CONFIG_PREEMPT_RT
static void print_timerlat_headers(struct seq_file *s)
{
+ if (osnoise_data.tainted)
+ seq_puts(s, "# timerlat is tainted!\n");
+
seq_puts(s, "# _-------=> irqs-off\n");
seq_puts(s, "# / _------=> need-resched\n");
seq_puts(s, "# | / _-----=> need-resched-lazy\n");
@@ -593,6 +596,9 @@ static void print_timerlat_headers(struct seq_file *s)
#else /* CONFIG_PREEMPT_RT */
static void print_timerlat_headers(struct seq_file *s)
{
+ if (osnoise_data.tainted)
+ seq_puts(s, "# timerlat is tainted!\n");
+
seq_puts(s, "# _-----=> irqs-off\n");
seq_puts(s, "# / _----=> need-resched\n");
seq_puts(s, "# | / _---=> hardirq/softirq\n");
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v6 10/10] tracing/osnoise: Refuse timerlat fd open if tracer inactive
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
` (8 preceding siblings ...)
2026-10-28 22:01 ` [PATCH v6 09/10] tracing/osnoise: Print taint message for timerlat Crystal Wood
@ 2026-10-28 22:01 ` Crystal Wood
9 siblings, 0 replies; 14+ messages in thread
From: Crystal Wood @ 2026-10-28 22:01 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Besides being generally bad, allowing an fd open when there are no active
instances means that osnoise_options_write() and osnoise_cpus_write() will
leave it active during reconfiguration, causing at least WARN splats.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: new patch
---
kernel/trace/trace_osnoise.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index ff4744c0701e..4617c12072c7 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -2502,7 +2502,8 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
* This file is accessible only if timerlat is enabled, and
* NO_OSNOISE_WORKLOAD is set.
*/
- if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options))
+ if (!osnoise_has_registered_instances() || !timerlat_enabled() ||
+ test_bit(OSN_WORKLOAD, &osnoise_options))
return -EINVAL;
guard(migrate)();
--
2.54.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v6 02/10] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD
2026-10-28 22:01 ` [PATCH v6 02/10] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD Crystal Wood
@ 2026-10-28 22:06 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-28 22:06 UTC (permalink / raw)
To: Crystal Wood; +Cc: linux-trace-kernel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] tracing/osnoise: undeclared identifier osn in start_kthread() breaks build
--
commit 4a3b715cf1d6cbcedaf9c94747a13d69da987d02
Author: Crystal Wood <crwood@redhat.com>
tracing/osnoise: start_kthread: Always check OSN_WORKLOAD
This commit moves the OSN_WORKLOAD check in start_kthread() ahead of the
timerlat_enabled() check so that timerlat CPU hotplug does not start a kthread
when OSN_WORKLOAD is cleared, and removes a compiler barrier before returning.
> diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
> index aa764ae13047e..33477239351cf 100644
> --- a/kernel/trace/trace_osnoise.c
> +++ b/kernel/trace/trace_osnoise.c
> @@ -2047,16 +2047,17 @@ static int start_kthread(unsigned int cpu)
> if (cpu_osn_var(cpu)->kthread)
> return 0;
>
> + /* Can still happen with timerlat via cpu hotplug */
> + if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
> + if (!timerlat_enabled())
> + osn->sampling = true;
[Severity: Low]
Does this break the build when CONFIG_OSNOISE_TRACER is enabled because osn
is undeclared in start_kthread() at this point in the series?
In parent commit 1860e8883e92 ("tracing/osnoise: Variable cleanup"),
start_kthread() accessed this field via cpu_osn_var(cpu)->sampling = true
without a local osn variable:
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
cpu_osn_var(cpu)->sampling = true;
barrier();
return 0;
}
The local declaration struct osnoise_variables *osn = cpu_osn_var(cpu); is
only introduced later in the series in commit fc0df59115a1 ("tracing/osnoise:
Per-cpu mutexes and fd detachment"), leaving this commit and the following two
commits unable to compile. Should this still use cpu_osn_var(cpu)->sampling =
true here to preserve bisection?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028220120.4005187-1-crwood@redhat.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v6 05/10] tracing/osnoise: Per-cpu mutexes and fd detachment
2026-10-28 22:01 ` [PATCH v6 05/10] tracing/osnoise: Per-cpu mutexes and fd detachment Crystal Wood
@ 2026-10-28 22:20 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-28 22:20 UTC (permalink / raw)
To: Crystal Wood; +Cc: linux-trace-kernel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] tracing/osnoise: unreachable migration handling in timerlat_fd_read()
- [Medium] tracing/osnoise: stale tracing_thread after signal in timerlat_fd_read()
--
commit fc0df59115a148fd84654dadbdbb8ffa3b889971
Author: Crystal Wood <crwood@redhat.com>
tracing/osnoise: Per-cpu mutexes and fd detachment
This patch introduces per-CPU mutexes in osnoise_variables and
timerlat_variables to synchronize kthread lifecycle and file descriptor
open, close, and detachment, replacing the previous SIGKILL mechanism
for user-space timerlat threads.
> diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
> index 791a9103c2ec0..cd386c75926e4 100644
> --- a/kernel/trace/trace_osnoise.c
> +++ b/kernel/trace/trace_osnoise.c
[ ... ]
> @@ -2496,37 +2572,44 @@ static ssize_t
> timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
> loff_t *ppos)
> {
> - long cpu = (long) file->private_data;
> + struct fd_data *fdd = file->private_data;
> struct osnoise_variables *osn_var;
> struct timerlat_variables *tlat;
> struct timerlat_sample s;
> s64 diff;
> u64 now;
> + int ret = 0;
>
> migrate_disable();
>
> tlat = this_cpu_tmr_var();
> + osn_var = this_cpu_osn_var();
> +
> + /* In normal usage, this should always be uncontended. */
> + mutex_lock(&tlat->detach_lock);
> + if (fdd != osn_var->fdd || current != fdd->thread) {
> + ret = -EINVAL;
> + goto out;
> + }
>
> /*
> * While in user-space, the thread is migratable. There is nothing
> * we can do about it.
> * So, if the thread is running on another CPU, stop the machinery.
> */
> - if (cpu == smp_processor_id()) {
> + if (fdd->cpu == smp_processor_id()) {
> if (tlat->uthread_migrate) {
> - migrate_enable();
> - return -EINVAL;
> + ret = -EINVAL;
> + goto out;
> }
> } else {
> - cpu_tmr_var(cpu)->uthread_migrate = 1;
> + cpu_tmr_var(fdd->cpu)->uthread_migrate = 1;
> osnoise_taint("timerlat user thread migrate\n");
> osnoise_stop_tracing();
> - migrate_enable();
> - return -EINVAL;
> + ret = -EINVAL;
> + goto out;
> }
[Severity: Medium]
In timerlat_fd_open(), fdd is stored only in the osnoise_variables for
fdd->cpu.
In timerlat_fd_read(), osn_var and tlat are fetched via this_cpu_osn_var()
and this_cpu_tmr_var() for smp_processor_id(). If the user-space thread
migrated to another CPU before calling read(), osn_var belongs to the new
CPU, where osn_var->fdd cannot match fdd.
Because fdd != osn_var->fdd is checked before fdd->cpu == smp_processor_id(),
won't a migrated thread always lock the new CPU's detach_lock and bail out
at the fdd != osn_var->fdd check, making the else branch that sets
cpu_tmr_var(fdd->cpu)->uthread_migrate, calls osnoise_taint(), and stops
tracing via osnoise_stop_tracing() unreachable?
> @@ -2561,9 +2644,6 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
> }
> }
> } else {
> - tlat->tracing_thread = false;
> - tlat->thread = current;
> -
> /* Annotate now to drift new period */
> tlat->abs_period = hrtimer_cb_get_time(&tlat->timer);
>
> @@ -2571,7 +2651,20 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
> }
>
> /* wait for the next period */
> - wait_next_period(tlat);
> + if (wait_next_period(tlat) < 0) {
> + if (signal_pending(current))
> + ret = -ERESTARTSYS;
> + else
> + ret = -EINVAL;
> +
> + osn_var->sampling = false;
> + goto out;
> + }
[Severity: Medium]
If timerlat_irq() fires in hardirq context while the thread is in
wait_next_period(), it sets tlat->tracing_thread = true:
kernel/trace/trace_osnoise.c:timerlat_irq() {
...
/*
* Enable the osnoise: events for thread an softirq.
*/
tlat->tracing_thread = true;
...
}
If a signal is also pending before wait_next_period() checks
signal_pending(current), wait_next_period() returns -1 and timerlat_fd_read()
clears osn_var->sampling = false, but leaves tlat->tracing_thread set to
true.
Since tlat->tracing_thread = false was removed from the !osn_var->sampling
branch above, if the read() is restarted or called again, it sets
osn_var->sampling = 1 with tlat->tracing_thread still true before entering
wait_next_period(). While sleeping for the next timer activation, won't
timerlat_thread_exit() and timerlat_softirq_exit() see both osn_var->sampling
and tlat->tracing_thread as true and emit spurious noise events before the
timer expires?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028220120.4005187-1-crwood@redhat.com?part=5
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v6 06/10] tracing/osnoise: Split out per-cpu variable resets
2026-10-28 22:01 ` [PATCH v6 06/10] tracing/osnoise: Split out per-cpu variable resets Crystal Wood
@ 2026-10-28 22:24 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-28 22:24 UTC (permalink / raw)
To: Crystal Wood; +Cc: linux-trace-kernel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] tracing/osnoise: unreset delta_start and arrival_time in osn_reset_cpu()
- [Medium] tracing/osnoise: race on osn->pid in timerlat_fd_detach() vs migration check
--
commit fffc251bf1aed17ce8e75b3b4c7d5990c0ecad11
Author: Crystal Wood <crwood@redhat.com>
tracing/osnoise: Split out per-cpu variable resets
This commit splits per-CPU variable resets out of osn_var_reset() and
tlat_var_reset() into osn_reset_cpu() and tlat_reset_cpu(), resetting
per-CPU state whenever stopping a kthread or detaching an fd. It also moves
the interrupt and thread tracking structures out of the zeroed group in
struct osnoise_variables.
> diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
> index cd386c75926e4..1abe2c3507f76 100644
> --- a/kernel/trace/trace_osnoise.c
> +++ b/kernel/trace/trace_osnoise.c
> @@ -237,15 +237,15 @@ struct osnoise_variables {
> struct mutex lock; /* covers kthread and fdd changes */
> struct task_struct *kthread;
> struct fd_data *fdd;
> + struct osn_nmi nmi;
> + struct osn_irq irq;
> + struct osn_softirq softirq;
> + struct osn_thread thread;
> + local_t int_counter;
>
> struct_group(zero,
> bool sampling;
> pid_t pid;
> - struct osn_nmi nmi;
> - struct osn_irq irq;
> - struct osn_softirq softirq;
> - struct osn_thread thread;
> - local_t int_counter;
> );
> };
[Severity: Medium]
Can moving nmi, irq, softirq, and thread out of struct_group(zero, ...)
without resetting their delta_start and arrival_time fields in
osn_reset_cpu() leave stale non-zero timestamps across runs?
In addition to count, struct osn_nmi, struct osn_irq, struct osn_softirq,
and struct osn_thread contain arrival_time and delta_start. A non-zero
delta_start is used to detect an active window in
cond_move_irq_delta_start(), cond_move_softirq_delta_start(),
cond_move_thread_delta_start(), and timerlat_irq():
if (!IS_ENABLED(CONFIG_PREEMPT_RT) && osn_var->softirq.delta_start) {
copy_int_safe_time(osn_var, &osn_var->thread.delta_start,
&osn_var->softirq.delta_start);
copy_int_safe_time(osn_var, &osn_var->softirq.delta_start,
&osn_var->irq.delta_start);
}
Normally, delta_start and arrival_time are cleared to zero in
osnoise_trace_irq_exit(), trace_softirq_exit_callback(), and thread_exit().
However, all three exit callbacks return early when osn_var->sampling is
false:
if (!osn_var->sampling)
return;
If osn_var->sampling is cleared while a measurement window is open (for
example, when timerlat_main() exits while sampling is still true and
schedules in the next task via thread_entry() before stop_kthread() runs,
when NO_OSNOISE_WORKLOAD or timerlat_fd stops while a task is running on the
CPU, or when timerlat_tracer_stop() clears sampling from another CPU while
a softirq or IRQ is in flight), the exit callback skips clearing
delta_start and arrival_time.
On a subsequent timerlat or NO_OSNOISE_WORKLOAD run, won't a stale non-zero
osn_var->softirq.delta_start cause timerlat_irq() to misdetect softirq
preemption and overwrite thread.delta_start on every period, or a stale
osn_var->thread.delta_start corrupt the first thread_exit() duration?
[ ... ]
> @@ -2452,25 +2452,22 @@ static void timerlat_fd_detach(struct fd_data *fdd)
> if (WARN_ON(fdd != osn->fdd))
> return;
>
> - WARN_ON(osn->kthread);
> WARN_ON(fdd->thread != tlat->thread);
> tlat->detach = true;
> wake_up_process(tlat->thread);
>
> mutex_lock(&tlat->detach_lock);
>
> - osn->sampling = 0;
> - osn->pid = 0;
> -
> WARN_ON_ONCE(hrtimer_active(&tlat->timer));
> hrtimer_cancel(&tlat->timer);
> put_task_struct(fdd->thread);
>
> fdd->thread = NULL;
> + tlat->thread = NULL;
> osn->fdd = NULL;
> - memset(&tlat->zero, 0, sizeof(tlat->zero));
>
> mutex_unlock(&tlat->detach_lock);
> +
> }
[Severity: Medium]
Does removing osn->pid = 0 from timerlat_fd_detach() open a race window
with trace_sched_migrate_callback() before osn_reset_cpu() runs?
In timerlat_fd_detach(), waking tlat->thread and unlocking
tlat->detach_lock allows the user thread in timerlat_fd_read() to finish
its read path, call migrate_enable(), and return to userspace (or get
pushed off a dying CPU during hotplug via osnoise_cpu_die() ->
stop_kthread() -> timerlat_fd_detach()) before the caller invokes
osn_reset_cpu(cpu) to zero osn->pid.
While osn->pid still holds the detaching thread's PID,
trace_sched_migrate_callback() checks osn_var->pid locklessly:
osn_var = cpu_osn_var(cpu);
if (osn_var->pid == p->pid && dest_cpu != cpu) {
cpu_tmr_var(cpu)->uthread_migrate = 1;
osnoise_taint("timerlat user-thread migrated\n");
osnoise_stop_exception("timerlat user-thread migrated", cpu);
}
If the detached thread migrates in that window before osn_reset_cpu()
clears osn->pid, won't trace_sched_migrate_callback() falsely taint and
stop tracing?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028220120.4005187-1-crwood@redhat.com?part=6
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-28 22:24 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 22:01 [PATCH v6 0/10] tracing/osnoise: Synchronization fixes Crystal Wood
2026-10-28 22:01 ` [PATCH v6 01/10] tracing/osnoise: Variable cleanup Crystal Wood
2026-10-28 22:01 ` [PATCH v6 02/10] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD Crystal Wood
2026-10-28 22:06 ` sashiko-bot
2026-10-28 22:01 ` [PATCH v6 03/10] tracing/osnoise: Start all eligible cpus from hotplug workfn Crystal Wood
2026-10-28 22:01 ` [PATCH v6 04/10] tracing/osnoise: Hold a ref on osn->kthread Crystal Wood
2026-10-28 22:01 ` [PATCH v6 05/10] tracing/osnoise: Per-cpu mutexes and fd detachment Crystal Wood
2026-10-28 22:20 ` sashiko-bot
2026-10-28 22:01 ` [PATCH v6 06/10] tracing/osnoise: Split out per-cpu variable resets Crystal Wood
2026-10-28 22:24 ` sashiko-bot
2026-10-28 22:01 ` [PATCH v6 07/10] tracing/osnoise: kthreads: Verify CPU after migrate_disable() Crystal Wood
2026-10-28 22:01 ` [PATCH v6 08/10] tracing/osnoise: Take trace_types_lock in timerlat_fd_open Crystal Wood
2026-10-28 22:01 ` [PATCH v6 09/10] tracing/osnoise: Print taint message for timerlat Crystal Wood
2026-10-28 22:01 ` [PATCH v6 10/10] tracing/osnoise: Refuse timerlat fd open if tracer inactive Crystal Wood
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox