On Fri, 2026-08-21 at 00:45 +0800, [email protected] wrote:
> From: Wen Yang <[email protected]>
> +
> +/* start probe; busy-spin so running_ns dominates */
> +noinline void tlob_busy_work(unsigned long duration_ms)
> +{
> +     struct timespec start, now;
> +     unsigned long elapsed;
> +
> +     clock_gettime(CLOCK_MONOTONIC, &start);
> +     do {
> +             clock_gettime(CLOCK_MONOTONIC, &now);
> +             elapsed = (unsigned long)(now.tv_sec - start.tv_sec)
> +                       * 1000000000UL
> +                     + (unsigned long)(now.tv_nsec - start.tv_nsec);
> +     } while (elapsed < duration_ms * 1000000UL);

I really don't like repeated code, cannot this go to a static inline
__tlob_busy_wait(duration_ms) and you call that from tlob_busy_work() and
tlob_preempt_work() ?

> +
> +     tlob_busy_work_done();
> +}
> +
> +/* stop probe; noinline keeps the entry point visible to uprobes */
> +noinline void tlob_sleep_work_done(void)
> +{
> +     asm volatile("" ::: "memory");
> +}
> +
> +/* start probe; nanosleep so sleeping_ns dominates */
> +noinline void tlob_sleep_work(unsigned long duration_ms)
> +{
> +     struct timespec ts = {
> +             .tv_sec  = duration_ms / 1000,
> +             .tv_nsec = (long)(duration_ms % 1000) * 1000000L,
> +     };
> +     nanosleep(&ts, NULL);
> +     tlob_sleep_work_done();
> +}
> +
> +/* stop probe; noinline keeps the entry point visible to uprobes */
> +noinline void tlob_preempt_work_done(void)
> +{
> +     asm volatile("" ::: "memory");
> +}
> +
> +/*
> + * start probe; busy-spin so an RT competitor on the same CPU drives
> + * waiting_ns (prev_state==0 -> preempt event, task stays runnable off-CPU).
> + */
> +noinline void tlob_preempt_work(unsigned long duration_ms)
> +{
> +     struct timespec start, now;
> +     unsigned long elapsed;
> +
> +     clock_gettime(CLOCK_MONOTONIC, &start);
> +     do {
> +             clock_gettime(CLOCK_MONOTONIC, &now);
> +             elapsed = (unsigned long)(now.tv_sec - start.tv_sec)
> +                       * 1000000000UL
> +                     + (unsigned long)(now.tv_nsec - start.tv_nsec);
> +     } while (elapsed < duration_ms * 1000000UL);

So here you'd just call __tlob_busy_wait(duration_ms).

> +
> +     tlob_preempt_work_done();
> +}

Tests look good. Apparently all that started_list/started_node thing in
tlob.c is for multiple tasks sharing the same binding right. Shouldn't
that be tested? I just got a simple test for that, it's generated and
manually fixed but you may want to double check if it's really relevant
for this.

Anyway that isn't too important, for now:

Reviewed-by: Gabriele Monaco <[email protected]>

Thanks,
Gabriele

diff --git 
a/tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc 
b/tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc
new file mode 100644
index 000000000000..33404683d29a
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc
@@ -0,0 +1,68 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor multiple instances of same uprobe binding 
(concurrent tasks on same binding)
+# requires: tlob:monitor
+
+# Fall back to the verification directory relative to FTRACETEST_ROOT when not
+# set by make (e.g. in installed kselftest environments).
+: "${VERIFICATIONTEST_BINDIR:="$FTRACETEST_ROOT/../verification"}"
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_SYM="${VERIFICATIONTEST_BINDIR}/tlob_sym"
+TLOB_MONITOR=monitors/tlob/monitor
+UPROBE_COMM=$(basename ${UPROBE_TARGET})
+
+busy_offset=$("$TLOB_SYM" sym_offset "$UPROBE_TARGET" tlob_busy_work 
2>/dev/null)
+busy_stop=$("$TLOB_SYM" sym_offset "$UPROBE_TARGET" tlob_busy_work_done 
2>/dev/null)
+
+# Start 3 concurrent instances of the target running the same binary probe
+"$UPROBE_TARGET" 30000 &
+pid1=$!
+"$UPROBE_TARGET" 30000 &
+pid2=$!
+"$UPROBE_TARGET" 30000 &
+pid3=$!
+
+teardown() {
+       kill "$pid1" 2>/dev/null || true; wait "$pid1" 2>/dev/null || true
+       kill "$pid2" 2>/dev/null || true; wait "$pid2" 2>/dev/null || true
+       kill "$pid3" 2>/dev/null || true; wait "$pid3" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+echo 1 > ../events/rv/event_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+# 5 s budget on the busy probe - must not fire in 200 ms loops
+echo "p ${UPROBE_TARGET}:${busy_offset} ${busy_stop} threshold=5000000000" > 
"$TLOB_MONITOR"
+
+# Wait up to 2 s for all three pids to be registered and transition through 
start.
+# This proves multiple tasks can hit the same uprobe binding concurrently.
+found1=0; found2=0; found3=0
+i=0
+while [ "$i" -lt 20 ]; do
+       sleep 0.1
+       grep "event_tlob" ../trace | grep -wq "${UPROBE_COMM}-${pid1}" && 
found1=1
+       grep "event_tlob" ../trace | grep -wq "${UPROBE_COMM}-${pid2}" && 
found2=1
+       grep "event_tlob" ../trace | grep -wq "${UPROBE_COMM}-${pid3}" && 
found3=1
+       if [ "$found1" = "1" ] && [ "$found2" = "1" ] && [ "$found3" = "1" ]; 
then
+               break
+       fi
+       i=$((i+1))
+done
+
+[ "$found1" = "1" ]
+[ "$found2" = "1" ]
+[ "$found3" = "1" ]
+
+# Removing the uprobe while tasks are active must succeed cleanly and unbind 
them.
+# Active tasks will be detached (binding set to NULL), parked tasks will be 
destroyed.
+echo "-${UPROBE_TARGET}:${busy_offset}" > "$TLOB_MONITOR"
+! grep -q "^p .*:0x${busy_offset#0x} " "$TLOB_MONITOR" || false
+
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/event_tlob/enable
+echo > ../trace


Reply via email to