Hello!
One of the commits [1] that I used to chase down a recent bug [2] (and
thank you again for the fix!) uses the scheduler-internal cpu_curr()
macro to map from a given CPU to the task currently running on that CPU.
In the commit, the actual operation is mapping from a stalled (AKA
preempted but runnable) task to the task running on the corresponding CPU.
This tasks's ->comm and ->pid are then printed in the RCU CPU stall
warning console output.
One of the real-time folks voted in favor being able to use this (the
group is CCed here), which suggests that I push this commit to mainline.
Which means that I need to do something other than my crude hack of
creating a rcu_cpu_curr() function in kernel/sched/core.c to get access
to the scheduler-private cpu_curr() macro.
Please tell me what I should instead be doing (in the check_slow_task()
function shown below) to get at this information.
Thanx, Paul
[1] 68a2e5363f8f ("EXP rcu: Print CPU that preempted stalled reader wants to run on")
This is in -rcu, and is also shown at the end of this email.
[2] https://lore.kernel.org/all/8fa7f795-518c-4507-b684-60b0c1cac2c5@paulmck-laptop/
------------------------------------------------------------------------
68a2e5363f8f ("EXP rcu: Print CPU that preempted stalled reader wants to run on")
------------------------------------------------------------------------
commit 68a2e5363f8f30f17038b3daee5dbbd661a7207e
Author: Paul E. McKenney <paulmck@kernel.org>
Date: Mon Sep 28 13:35:01 2026 -0700
EXP rcu: Print CPU that preempted stalled reader wants to run on
This commit uses the dirty trick of defining an rcu_cpu_curr() within
kernel/sched/core.c in order to allow the RCU CPU stall warning call out
the CPU that a task preempted within an RCU reader would like to run on.
In addition, print out the task (if any) currently running on that CPU,
and add an exclamation mark ("!") after the CPU number if that CPU is
currently offline.
This is a potential step towards allowing people (or LLMs, for that
matter) to locate any tasks that might be preventing the preempted RCU
reader task from running.
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cca..a8b3434edb2b4 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2532,3 +2532,5 @@ extern void migrate_enable(void);
DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
#endif
+
+struct task_struct *rcu_cpu_curr(int cpu);
diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h
index 803a56af3259f..7826c4d905248 100644
--- a/kernel/rcu/tree_stall.h
+++ b/kernel/rcu/tree_stall.h
@@ -309,6 +309,9 @@ struct rcu_stall_chk_rdr {
bool on_blkd_list;
bool rcu_rdr_running;
int rcu_rdr_boosted;
+ int rcu_rdr_cpu;
+ int rcu_rdr_cpu_offline;
+ struct task_struct *rcu_rdr_cpu_task;
};
/*
@@ -317,8 +320,10 @@ struct rcu_stall_chk_rdr {
*/
static int check_slow_task(struct task_struct *t, void *arg)
{
+ int cpu;
struct rcu_node *rnp;
struct rcu_stall_chk_rdr *rscrp = arg;
+ struct task_struct *th;
if (task_curr(t))
return -EBUSY; // It is running, so decline to inspect it.
@@ -327,6 +332,9 @@ static int check_slow_task(struct task_struct *t, void *arg)
rscrp->on_blkd_list = !list_empty(&t->rcu_node_entry);
rscrp->rcu_rdr_running = task_curr(t);
rscrp->rcu_rdr_boosted = 0;
+ rscrp->rcu_rdr_cpu = cpu = task_cpu(t);
+ rscrp->rcu_rdr_cpu_offline = cpu_is_offline(cpu);
+ rscrp->rcu_rdr_cpu_task = th = rcu_cpu_curr(cpu);
if (rscrp->on_blkd_list) {
rnp = READ_ONCE(t->rcu_blocked_node);
raw_spin_lock_rcu_node(rnp); /* irqs already disabled. */
@@ -352,6 +360,7 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
int ndetected = 0;
struct rcu_stall_chk_rdr rscr;
struct task_struct *t;
+ struct task_struct *tc;
struct task_struct *ts[8];
lockdep_assert_irqs_disabled();
@@ -372,17 +381,24 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
while (i) {
t = ts[--i];
- if (task_call_func(t, check_slow_task, &rscr))
+ if (task_call_func(t, check_slow_task, &rscr)) {
pr_cont(" P%d", t->pid);
- else
- pr_cont(" P%d/%d:%c%c%c%c%c%c",
+ } else {
+ tc = rscr.rcu_rdr_cpu_task;
+ pr_cont(" P%d/%d:%c%c%c%c%c%c CPU %d%s %s (%d)%s",
t->pid, rscr.nesting,
".b"[rscr.rs.b.blocked],
".q"[rscr.rs.b.need_qs],
".e"[rscr.rs.b.exp_hint],
".l"[rscr.on_blkd_list],
".R"[rscr.rcu_rdr_running],
- ".B?"[rscr.rcu_rdr_boosted]);
+ ".B?"[rscr.rcu_rdr_boosted],
+ rscr.rcu_rdr_cpu,
+ rscr.rcu_rdr_cpu_offline ? "!" : "",
+ tc ? tc->comm : "???",
+ tc ? tc->pid : -1,
+ tc ? (is_idle_task(tc) ? " (idle!)" : "") : " (no task)");
+ }
lockdep_assert_irqs_disabled();
put_task_struct(t);
ndetected++;
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index b77152edafd92..e137cba24fc92 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -11321,3 +11321,10 @@ void sched_change_end(struct sched_change_ctx *ctx)
p->sched_class->prio_changed(rq, p, ctx->prio);
}
}
+
+// Not for mainline, but hopefully someone has a trick that gets this
+// job done.
+struct task_struct *rcu_cpu_curr(int cpu)
+{
+ return cpu_curr(cpu);
+}
Hello Paul,
On 10/15/2026 11:11 PM, Paul E. McKenney wrote:
> Hello!
>
> One of the commits [1] that I used to chase down a recent bug [2] (and
> thank you again for the fix!) uses the scheduler-internal cpu_curr()
> macro to map from a given CPU to the task currently running on that CPU.
> In the commit, the actual operation is mapping from a stalled (AKA
> preempted but runnable) task to the task running on the corresponding CPU.
> This tasks's ->comm and ->pid are then printed in the RCU CPU stall
> warning console output.
>
> One of the real-time folks voted in favor being able to use this (the
> group is CCed here), which suggests that I push this commit to mainline.
> Which means that I need to do something other than my crude hack of
> creating a rcu_cpu_curr() function in kernel/sched/core.c to get access
> to the scheduler-private cpu_curr() macro.
>
> Please tell me what I should instead be doing (in the check_slow_task()
> function shown below) to get at this information.
[..snip..]
> @@ -327,6 +332,9 @@ static int check_slow_task(struct task_struct *t, void *arg)
> rscrp->on_blkd_list = !list_empty(&t->rcu_node_entry);
> rscrp->rcu_rdr_running = task_curr(t);
> rscrp->rcu_rdr_boosted = 0;
> + rscrp->rcu_rdr_cpu = cpu = task_cpu(t);
> + rscrp->rcu_rdr_cpu_offline = cpu_is_offline(cpu);
> + rscrp->rcu_rdr_cpu_task = th = rcu_cpu_curr(cpu);
If you don't mind taking the overhead of rq_lock, you can use
cpu_curr_snapshot(). It should give you gives a rcu protected
task struct of cpu_curr() and ...
> if (rscrp->on_blkd_list) {
> rnp = READ_ONCE(t->rcu_blocked_node);
> raw_spin_lock_rcu_node(rnp); /* irqs already disabled. */
> @@ -352,6 +360,7 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
> int ndetected = 0;
> struct rcu_stall_chk_rdr rscr;
> struct task_struct *t;
> + struct task_struct *tc;
> struct task_struct *ts[8];
>
> lockdep_assert_irqs_disabled();
> @@ -372,17 +381,24 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
> raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
> while (i) {
> t = ts[--i];
> - if (task_call_func(t, check_slow_task, &rscr))
> + if (task_call_func(t, check_slow_task, &rscr)) {
> pr_cont(" P%d", t->pid);
> - else
> - pr_cont(" P%d/%d:%c%c%c%c%c%c",
> + } else {
> + tc = rscr.rcu_rdr_cpu_task;
> + pr_cont(" P%d/%d:%c%c%c%c%c%c CPU %d%s %s (%d)%s",
> t->pid, rscr.nesting,
> ".b"[rscr.rs.b.blocked],
> ".q"[rscr.rs.b.need_qs],
> ".e"[rscr.rs.b.exp_hint],
> ".l"[rscr.on_blkd_list],
> ".R"[rscr.rcu_rdr_running],
> - ".B?"[rscr.rcu_rdr_boosted]);
> + ".B?"[rscr.rcu_rdr_boosted],
> + rscr.rcu_rdr_cpu,
> + rscr.rcu_rdr_cpu_offline ? "!" : "",
> + tc ? tc->comm : "???",
> + tc ? tc->pid : -1,
> + tc ? (is_idle_task(tc) ? " (idle!)" : "") : " (no task)");
> + }
> lockdep_assert_irqs_disabled();
... since you are IRQs disabled, it should be fine to use it here.
If you don't want to take the overhead of rq_lock() + barrier,
you can perhaps define a cpu_curr_snapshot_rcu() like:
struct task_struct *cpu_curr_snapshot_rcu(int cpu)
{
return rcu_dereference_all(cpu_curr(cpu));
}
in kernel/sched/core.c and expose it similar to
cpu_curr_snapshot().
I'll let Peter comment if he hates it or not ;-)
--
Thanks and Regards,
Prateek
On Fri, Oct 16, 2026 at 10:14:27AM +0530, K Prateek Nayak wrote:
> > @@ -372,17 +381,24 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
> > raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
> > while (i) {
> > t = ts[--i];
> > - if (task_call_func(t, check_slow_task, &rscr))
> > + if (task_call_func(t, check_slow_task, &rscr)) {
> > pr_cont(" P%d", t->pid);
> > - else
> > - pr_cont(" P%d/%d:%c%c%c%c%c%c",
> > + } else {
> > + tc = rscr.rcu_rdr_cpu_task;
> > + pr_cont(" P%d/%d:%c%c%c%c%c%c CPU %d%s %s (%d)%s",
> > t->pid, rscr.nesting,
> > ".b"[rscr.rs.b.blocked],
> > ".q"[rscr.rs.b.need_qs],
> > ".e"[rscr.rs.b.exp_hint],
> > ".l"[rscr.on_blkd_list],
> > ".R"[rscr.rcu_rdr_running],
> > - ".B?"[rscr.rcu_rdr_boosted]);
> > + ".B?"[rscr.rcu_rdr_boosted],
> > + rscr.rcu_rdr_cpu,
> > + rscr.rcu_rdr_cpu_offline ? "!" : "",
> > + tc ? tc->comm : "???",
> > + tc ? tc->pid : -1,
> > + tc ? (is_idle_task(tc) ? " (idle!)" : "") : " (no task)");
> > + }
> > lockdep_assert_irqs_disabled();
>
> ... since you are IRQs disabled, it should be fine to use it here.
>
> If you don't want to take the overhead of rq_lock() + barrier,
> you can perhaps define a cpu_curr_snapshot_rcu() like:
>
> struct task_struct *cpu_curr_snapshot_rcu(int cpu)
> {
> return rcu_dereference_all(cpu_curr(cpu));
> }
>
> in kernel/sched/core.c and expose it similar to
> cpu_curr_snapshot().
>
> I'll let Peter comment if he hates it or not ;-)
I would much rather do this than expose rq_lock().
On Fri, Oct 16, 2026 at 09:25:00AM +0200, Peter Zijlstra wrote:
> On Fri, Oct 16, 2026 at 10:14:27AM +0530, K Prateek Nayak wrote:
>
> > > @@ -372,17 +381,24 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
> > > raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
> > > while (i) {
> > > t = ts[--i];
> > > - if (task_call_func(t, check_slow_task, &rscr))
> > > + if (task_call_func(t, check_slow_task, &rscr)) {
> > > pr_cont(" P%d", t->pid);
> > > - else
> > > - pr_cont(" P%d/%d:%c%c%c%c%c%c",
> > > + } else {
> > > + tc = rscr.rcu_rdr_cpu_task;
> > > + pr_cont(" P%d/%d:%c%c%c%c%c%c CPU %d%s %s (%d)%s",
> > > t->pid, rscr.nesting,
> > > ".b"[rscr.rs.b.blocked],
> > > ".q"[rscr.rs.b.need_qs],
> > > ".e"[rscr.rs.b.exp_hint],
> > > ".l"[rscr.on_blkd_list],
> > > ".R"[rscr.rcu_rdr_running],
> > > - ".B?"[rscr.rcu_rdr_boosted]);
> > > + ".B?"[rscr.rcu_rdr_boosted],
> > > + rscr.rcu_rdr_cpu,
> > > + rscr.rcu_rdr_cpu_offline ? "!" : "",
> > > + tc ? tc->comm : "???",
> > > + tc ? tc->pid : -1,
> > > + tc ? (is_idle_task(tc) ? " (idle!)" : "") : " (no task)");
> > > + }
> > > lockdep_assert_irqs_disabled();
> >
> > ... since you are IRQs disabled, it should be fine to use it here.
> >
> > If you don't want to take the overhead of rq_lock() + barrier,
> > you can perhaps define a cpu_curr_snapshot_rcu() like:
> >
> > struct task_struct *cpu_curr_snapshot_rcu(int cpu)
> > {
> > return rcu_dereference_all(cpu_curr(cpu));
> > }
> >
> > in kernel/sched/core.c and expose it similar to
> > cpu_curr_snapshot().
> >
> > I'll let Peter comment if he hates it or not ;-)
>
> I would much rather do this than expose rq_lock().
Thank you both! Like this?
Thanx, Paul
------------------------------------------------------------------------
commit 1468f6acb32add422b8f4dcebc0d75e7fd4b95aa
Author: Paul E. McKenney <paulmck@kernel.org>
Date: Mon Sep 28 13:35:01 2026 -0700
rcu: Print CPU that preempted stalled reader wants to run on
This commit uses the dirty trick of defining an rcu_cpu_curr() within
kernel/sched/core.c in order to allow the RCU CPU stall warning call out
the CPU that a task preempted within an RCU reader would like to run on.
In addition, print out the task (if any) currently running on that CPU,
and add an exclamation mark ("!") after the CPU number if that CPU is
currently offline.
This is a potential step towards allowing people (or LLMs, for that
matter) to locate any tasks that might be preventing the preempted RCU
reader task from running.
[ paulmck: Apply feedback from K Prateek Nayak and Peter Zijlstra. ]
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cca..3606e5a2cec32 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2532,3 +2532,5 @@ extern void migrate_enable(void);
DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
#endif
+
+struct task_struct *cpu_curr_snapshot_rcu(int cpu);
diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h
index 803a56af3259f..33e953bb40b33 100644
--- a/kernel/rcu/tree_stall.h
+++ b/kernel/rcu/tree_stall.h
@@ -309,6 +309,9 @@ struct rcu_stall_chk_rdr {
bool on_blkd_list;
bool rcu_rdr_running;
int rcu_rdr_boosted;
+ int rcu_rdr_cpu;
+ int rcu_rdr_cpu_offline;
+ struct task_struct *rcu_rdr_cpu_task;
};
/*
@@ -317,8 +320,10 @@ struct rcu_stall_chk_rdr {
*/
static int check_slow_task(struct task_struct *t, void *arg)
{
+ int cpu;
struct rcu_node *rnp;
struct rcu_stall_chk_rdr *rscrp = arg;
+ struct task_struct *th;
if (task_curr(t))
return -EBUSY; // It is running, so decline to inspect it.
@@ -327,6 +332,10 @@ static int check_slow_task(struct task_struct *t, void *arg)
rscrp->on_blkd_list = !list_empty(&t->rcu_node_entry);
rscrp->rcu_rdr_running = task_curr(t);
rscrp->rcu_rdr_boosted = 0;
+ rscrp->rcu_rdr_cpu = cpu = task_cpu(t);
+ rscrp->rcu_rdr_cpu_offline = cpu_is_offline(cpu);
+ // Interrupt are disabled for RCU read-side critical section.
+ rscrp->rcu_rdr_cpu_task = th = cpu_curr_snapshot_rcu(cpu);
if (rscrp->on_blkd_list) {
rnp = READ_ONCE(t->rcu_blocked_node);
raw_spin_lock_rcu_node(rnp); /* irqs already disabled. */
@@ -352,6 +361,7 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
int ndetected = 0;
struct rcu_stall_chk_rdr rscr;
struct task_struct *t;
+ struct task_struct *tc;
struct task_struct *ts[8];
lockdep_assert_irqs_disabled();
@@ -372,17 +382,24 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
while (i) {
t = ts[--i];
- if (task_call_func(t, check_slow_task, &rscr))
+ if (task_call_func(t, check_slow_task, &rscr)) {
pr_cont(" P%d", t->pid);
- else
- pr_cont(" P%d/%d:%c%c%c%c%c%c",
+ } else {
+ tc = rscr.rcu_rdr_cpu_task;
+ pr_cont(" P%d/%d:%c%c%c%c%c%c CPU %d%s %s (%d)%s",
t->pid, rscr.nesting,
".b"[rscr.rs.b.blocked],
".q"[rscr.rs.b.need_qs],
".e"[rscr.rs.b.exp_hint],
".l"[rscr.on_blkd_list],
".R"[rscr.rcu_rdr_running],
- ".B?"[rscr.rcu_rdr_boosted]);
+ ".B?"[rscr.rcu_rdr_boosted],
+ rscr.rcu_rdr_cpu,
+ rscr.rcu_rdr_cpu_offline ? "!" : "",
+ tc ? tc->comm : "???",
+ tc ? tc->pid : -1,
+ tc ? (is_idle_task(tc) ? " (idle!)" : "") : " (no task)");
+ }
lockdep_assert_irqs_disabled();
put_task_struct(t);
ndetected++;
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 559735f71b5dd..1fa63eee528c2 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -11320,3 +11320,10 @@ void sched_change_end(struct sched_change_ctx *ctx)
p->sched_class->prio_changed(rq, p, ctx->prio);
}
}
+
+// Not for mainline, but hopefully someone has a trick that gets this
+// job done.
+struct task_struct *cpu_curr_snapshot_rcu(int cpu)
+{
+ return rcu_dereference_all(cpu_curr(cpu));
+}
This commit defines a cpu_curr_snapshot_rcu() function that invokes
rcu_dereference_all(cpu_curr()) to determine which task is currently
running on the specified CPU. The RCU CPU stall warning then uses this
new function in order to print the task that has preempted the stalled
task (the stalled task being the one that is in an RCU read-side critical
section, thus preventing the current RCU grace period from completing).
This modified stall-warning message also includes the CPU, along with
an exclamation mark ("!") after the CPU number if that CPU is currently
offline.
This is a potential step towards allowing people (or LLMs, for that
matter) locate any tasks that might be preventing the preempted RCU
reader task from running.
Adding a get_task_struct() for the currently running task would be a
bug because we are not in a position to prevent that task from exiting.
Yes, yes, because interrupts are disabled throughout, the corresponding
task_struct structure cannot be freed, but following sequence of events
is all too possible:
o The check_slow_task() function places a pointer to the task_struct
structure in its local variable "th".
o The corresponding task exits, reducing its task_struct structure's
->usage to zero. The task_struct structure is therefore passed
to call_rcu().
o The check_slow_task() function invokes get_task_struct(th),
setting th->usage to one.
o The rcu_print_task_stall() function invokes put_task_struct(),
again reducing ->usage to zero, and again passing the task
struct to call_rcu(). This results in either double-call_rcu()
or use-after-free, depending on when the RCU grace period for
the first call_rcu() completes. Both outcomes are bad.
This commit therefore relies solely on disabled interrupts acting as
an implicit RCU read-side critical section that prevents all of the
task_struct structures from being freed, thus avoiding the need for
get_task_struct().
The original get_task_struct() calls in rcu_print_task_stall() on the
tasks on the ->blkd_tasks list are safe because those tasks cannot exit
without first acquiring the rcu_node ->lock that this code already holds
(because these tasks must remove themselves from the ->blkd_tasks list).
But it is better to be consistent and rely on RCU in both cases.
These pre-existing get_task_struct() and put_task_struct() calls are
therefore removed.
[ paulmck: Apply feedback from K Prateek Nayak and Peter Zijlstra. ]
[ paulmck: Apply feedback from Steven Rostedt. ]
Link: https://lore.kernel.org/all/a7a21bf3-a7d5-45bb-be4f-0119fe53fa20@paulmck-laptop/
Link: https://lore.kernel.org/all/e8b6b372-d16b-4939-aa7a-755b7149e0fb@paulmck-laptop/
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cca..3606e5a2cec32 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2532,3 +2532,5 @@ extern void migrate_enable(void);
DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
#endif
+
+struct task_struct *cpu_curr_snapshot_rcu(int cpu);
diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h
index 803a56af3259f..08699652f51c2 100644
--- a/kernel/rcu/tree_stall.h
+++ b/kernel/rcu/tree_stall.h
@@ -309,6 +309,9 @@ struct rcu_stall_chk_rdr {
bool on_blkd_list;
bool rcu_rdr_running;
int rcu_rdr_boosted;
+ int rcu_rdr_cpu;
+ int rcu_rdr_cpu_offline;
+ struct task_struct *rcu_rdr_cpu_task;
};
/*
@@ -317,8 +320,10 @@ struct rcu_stall_chk_rdr {
*/
static int check_slow_task(struct task_struct *t, void *arg)
{
+ int cpu;
struct rcu_node *rnp;
struct rcu_stall_chk_rdr *rscrp = arg;
+ struct task_struct *th;
if (task_curr(t))
return -EBUSY; // It is running, so decline to inspect it.
@@ -327,6 +332,13 @@ static int check_slow_task(struct task_struct *t, void *arg)
rscrp->on_blkd_list = !list_empty(&t->rcu_node_entry);
rscrp->rcu_rdr_running = task_curr(t);
rscrp->rcu_rdr_boosted = 0;
+ rscrp->rcu_rdr_cpu = cpu = task_cpu(t);
+ rscrp->rcu_rdr_cpu_offline = cpu_is_offline(cpu);
+ lockdep_assert_irqs_disabled();
+ rscrp->rcu_rdr_cpu_task = th = cpu_curr_snapshot_rcu(cpu);
+ // There is no need for get_task_struct(t) because we are in
+ // an implicit RCU read-side critical section due to interrupts
+ // being disabled.
if (rscrp->on_blkd_list) {
rnp = READ_ONCE(t->rcu_blocked_node);
raw_spin_lock_rcu_node(rnp); /* irqs already disabled. */
@@ -352,6 +364,7 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
int ndetected = 0;
struct rcu_stall_chk_rdr rscr;
struct task_struct *t;
+ struct task_struct *tc;
struct task_struct *ts[8];
lockdep_assert_irqs_disabled();
@@ -364,7 +377,9 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
t = list_entry(rnp->gp_tasks->prev,
struct task_struct, rcu_node_entry);
list_for_each_entry_continue(t, &rnp->blkd_tasks, rcu_node_entry) {
- get_task_struct(t);
+ // There is no need for get_task_struct(t) because we
+ // are in an implicit RCU read-side critical section due
+ // to interrupts being disabled.
ts[i++] = t;
if (i >= ARRAY_SIZE(ts))
break;
@@ -372,19 +387,25 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags)
raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
while (i) {
t = ts[--i];
- if (task_call_func(t, check_slow_task, &rscr))
+ if (task_call_func(t, check_slow_task, &rscr)) {
pr_cont(" P%d", t->pid);
- else
- pr_cont(" P%d/%d:%c%c%c%c%c%c",
+ } else {
+ tc = rscr.rcu_rdr_cpu_task;
+ pr_cont(" P%d/%d:%c%c%c%c%c%c CPU %d%s %s (%d)%s",
t->pid, rscr.nesting,
".b"[rscr.rs.b.blocked],
".q"[rscr.rs.b.need_qs],
".e"[rscr.rs.b.exp_hint],
".l"[rscr.on_blkd_list],
".R"[rscr.rcu_rdr_running],
- ".B?"[rscr.rcu_rdr_boosted]);
+ ".B?"[rscr.rcu_rdr_boosted],
+ rscr.rcu_rdr_cpu,
+ rscr.rcu_rdr_cpu_offline ? "!" : "",
+ tc ? tc->comm : "???",
+ tc ? tc->pid : -1,
+ tc ? (is_idle_task(tc) ? " (idle!)" : "") : " (no task)");
+ }
lockdep_assert_irqs_disabled();
- put_task_struct(t);
ndetected++;
}
pr_cont("\n");
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 559735f71b5dd..3d88ad36ec9b4 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -11320,3 +11320,16 @@ void sched_change_end(struct sched_change_ctx *ctx)
p->sched_class->prio_changed(rq, p, ctx->prio);
}
}
+
+/**
+ * cpu_curr_snapshot_rcu - Return the task currently running on CPU
+ * @cpu: CPU to query
+ *
+ * The caller must be within an RCU read-side critical section.
+ *
+ * Return: Pointer to task_struct of task running on @cpu.
+ */
+struct task_struct *cpu_curr_snapshot_rcu(int cpu)
+{
+ return rcu_dereference_all(cpu_curr(cpu));
+}
Hello Paul,
On 10/20/2026 2:17 AM, Paul E. McKenney wrote:
> This commit defines a cpu_curr_snapshot_rcu() function that invokes
> rcu_dereference_all(cpu_curr()) to determine which task is currently
> running on the specified CPU. The RCU CPU stall warning then uses this
> new function in order to print the task that has preempted the stalled
> task (the stalled task being the one that is in an RCU read-side critical
> section, thus preventing the current RCU grace period from completing).
> This modified stall-warning message also includes the CPU, along with
> an exclamation mark ("!") after the CPU number if that CPU is currently
> offline.
>
> This is a potential step towards allowing people (or LLMs, for that
> matter) locate any tasks that might be preventing the preempted RCU
> reader task from running.
>
> Adding a get_task_struct() for the currently running task would be a
> bug because we are not in a position to prevent that task from exiting.
> Yes, yes, because interrupts are disabled throughout, the corresponding
> task_struct structure cannot be freed, but following sequence of events
> is all too possible:
>
> o The check_slow_task() function places a pointer to the task_struct
> structure in its local variable "th".
>
> o The corresponding task exits, reducing its task_struct structure's
> ->usage to zero. The task_struct structure is therefore passed
> to call_rcu().
>
> o The check_slow_task() function invokes get_task_struct(th),
> setting th->usage to one.
>
> o The rcu_print_task_stall() function invokes put_task_struct(),
> again reducing ->usage to zero, and again passing the task
> struct to call_rcu(). This results in either double-call_rcu()
> or use-after-free, depending on when the RCU grace period for
> the first call_rcu() completes. Both outcomes are bad.
>
> This commit therefore relies solely on disabled interrupts acting as
> an implicit RCU read-side critical section that prevents all of the
> task_struct structures from being freed, thus avoiding the need for
> get_task_struct().
>
> The original get_task_struct() calls in rcu_print_task_stall() on the
> tasks on the ->blkd_tasks list are safe because those tasks cannot exit
> without first acquiring the rcu_node ->lock that this code already holds
> (because these tasks must remove themselves from the ->blkd_tasks list).
> But it is better to be consistent and rely on RCU in both cases.
> These pre-existing get_task_struct() and put_task_struct() calls are
> therefore removed.
>
> [ paulmck: Apply feedback from K Prateek Nayak and Peter Zijlstra. ]
> [ paulmck: Apply feedback from Steven Rostedt. ]
>
> Link: https://lore.kernel.org/all/a7a21bf3-a7d5-45bb-be4f-0119fe53fa20@paulmck-laptop/
> Link: https://lore.kernel.org/all/e8b6b372-d16b-4939-aa7a-755b7149e0fb@paulmck-laptop/
> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
> Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
I tested this on your old dev branch on top of commit 5433117c67f0 ("EXP
rcutorture: Disable RCU priority boost testing on non-TREE03 scenarios")
and the idle state during stalls are reported correctly.
Feel free to include:
Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
Tested-by: K Prateek Nayak <kprateek.nayak@amd.com>
>
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 8b3d47a325cca..3606e5a2cec32 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -2532,3 +2532,5 @@ extern void migrate_enable(void);
> DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
>
> #endif
> +
> +struct task_struct *cpu_curr_snapshot_rcu(int cpu);
nit.
I think this and the function definition in core.c can go right after
cpu_curr_snapshot() in respective files to keep the related bits
together.
--
Thanks and Regards,
Prateek
On Wed, Oct 21, 2026 at 09:11:02AM +0530, K Prateek Nayak wrote:
> Hello Paul,
>
> On 10/20/2026 2:17 AM, Paul E. McKenney wrote:
> > This commit defines a cpu_curr_snapshot_rcu() function that invokes
> > rcu_dereference_all(cpu_curr()) to determine which task is currently
> > running on the specified CPU. The RCU CPU stall warning then uses this
> > new function in order to print the task that has preempted the stalled
> > task (the stalled task being the one that is in an RCU read-side critical
> > section, thus preventing the current RCU grace period from completing).
> > This modified stall-warning message also includes the CPU, along with
> > an exclamation mark ("!") after the CPU number if that CPU is currently
> > offline.
> >
> > This is a potential step towards allowing people (or LLMs, for that
> > matter) locate any tasks that might be preventing the preempted RCU
> > reader task from running.
> >
> > Adding a get_task_struct() for the currently running task would be a
> > bug because we are not in a position to prevent that task from exiting.
> > Yes, yes, because interrupts are disabled throughout, the corresponding
> > task_struct structure cannot be freed, but following sequence of events
> > is all too possible:
> >
> > o The check_slow_task() function places a pointer to the task_struct
> > structure in its local variable "th".
> >
> > o The corresponding task exits, reducing its task_struct structure's
> > ->usage to zero. The task_struct structure is therefore passed
> > to call_rcu().
> >
> > o The check_slow_task() function invokes get_task_struct(th),
> > setting th->usage to one.
> >
> > o The rcu_print_task_stall() function invokes put_task_struct(),
> > again reducing ->usage to zero, and again passing the task
> > struct to call_rcu(). This results in either double-call_rcu()
> > or use-after-free, depending on when the RCU grace period for
> > the first call_rcu() completes. Both outcomes are bad.
> >
> > This commit therefore relies solely on disabled interrupts acting as
> > an implicit RCU read-side critical section that prevents all of the
> > task_struct structures from being freed, thus avoiding the need for
> > get_task_struct().
> >
> > The original get_task_struct() calls in rcu_print_task_stall() on the
> > tasks on the ->blkd_tasks list are safe because those tasks cannot exit
> > without first acquiring the rcu_node ->lock that this code already holds
> > (because these tasks must remove themselves from the ->blkd_tasks list).
> > But it is better to be consistent and rely on RCU in both cases.
> > These pre-existing get_task_struct() and put_task_struct() calls are
> > therefore removed.
> >
> > [ paulmck: Apply feedback from K Prateek Nayak and Peter Zijlstra. ]
> > [ paulmck: Apply feedback from Steven Rostedt. ]
> >
> > Link: https://lore.kernel.org/all/a7a21bf3-a7d5-45bb-be4f-0119fe53fa20@paulmck-laptop/
> > Link: https://lore.kernel.org/all/e8b6b372-d16b-4939-aa7a-755b7149e0fb@paulmck-laptop/
> > Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
> > Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
>
> I tested this on your old dev branch on top of commit 5433117c67f0 ("EXP
> rcutorture: Disable RCU priority boost testing on non-TREE03 scenarios")
> and the idle state during stalls are reported correctly.
>
> Feel free to include:
>
> Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
> Tested-by: K Prateek Nayak <kprateek.nayak@amd.com>
Thank you, and I will apply this on my next rebase.
> > diff --git a/include/linux/sched.h b/include/linux/sched.h
> > index 8b3d47a325cca..3606e5a2cec32 100644
> > --- a/include/linux/sched.h
> > +++ b/include/linux/sched.h
> > @@ -2532,3 +2532,5 @@ extern void migrate_enable(void);
> > DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
> >
> > #endif
> > +
> > +struct task_struct *cpu_curr_snapshot_rcu(int cpu);
>
> nit.
>
> I think this and the function definition in core.c can go right after
> cpu_curr_snapshot() in respective files to keep the related bits
> together.
Excellent idea, especially given that its current location is getting me
merge conflicts when I test with the -next tree. Done!
Thanx, Paul
© 2016 - 2026 Red Hat, Inc.