From: Puranjay Mohan <puranjay@kernel.org>
To: rcu@vger.kernel.org
Cc: Puranjay Mohan <puranjay@kernel.org>,
"Paul E. McKenney" <paulmck@kernel.org>,
Josh Triplett <josh@joshtriplett.org>,
Steven Rostedt <rostedt@goodmis.org>,
Joel Fernandes <joelagnelf@nvidia.com>,
Boqun Feng <boqun@kernel.org>,
Frederic Weisbecker <frederic@kernel.org>
Subject: [PATCH] rcu: Move expedited-GP callback advancement into __note_gp_changes()
Date: Fri, 16 Oct 2026 03:28:42 -0700 [thread overview]
Message-ID: <20261016102843.2782262-1-puranjay@kernel.org> (raw)
Commit ec5ee5417b002 ("rcu: Advance callbacks for expedited GP
completion in rcu_core()") advances callbacks whose grace period was an
expedited one from a dedicated block in rcu_core(). That block is
needed because __note_gp_changes() returns early when
rdp->gp_seq == rnp->gp_seq, and expedited grace periods do not change
rnp->gp_seq.
The advancement depends only on the grace-period state kept in the
callback list, which covers both normal and expedited grace periods, so
it can be done in __note_gp_changes() instead. Move it there. This is
mostly a relocation, with the following differences:
- rcu_cbs_gp_done() combines rcu_segcblist_nextgp() with
poll_state_synchronize_rcu_full_unordered(), and returns false for
offloaded CPUs, as the removed block did. need_note_gp_changes() uses
it so that the lockless check in note_gp_changes() takes the leaf
rcu_node lock for such a completion, and rcu_pending() now calls
need_note_gp_changes() instead of open-coding the same tests.
- The removed block gated on poll_state_synchronize_rcu_full(), which
executes smp_mb() on every rcu_core() with pending callbacks. The
gate is now the unordered variant from fde2b18817e4f. Which callbacks
become ready is still decided by rcu_segcblist_advance(), which uses
the ordered poll.
- note_gp_changes() has one other caller, the qhimark path of
call_rcu_core(), which now also advances callbacks on an expedited
completion. __note_gp_changes() is also called by rcu_gp_init(),
where an expedited completion now takes rcu_advance_cbs() instead of
rcu_accelerate_cbs(), which it ends with anyway. In rcu_gp_cleanup(),
the leaf rcu_node's ->gp_seq has just advanced, so the existing
completed-grace-period path is taken as before.
- An expedited-only completion (rdp->gp_seq == rnp->gp_seq and ->gpwrap
clear) returns before touching core_needs_qs, cpu_no_qs, rdp->gp_seq
or gp_seq_needed.
One behavior change is not related to expedited grace periods. With
->gpwrap set and rdp->gp_seq == rnp->gp_seq, the early return skipped
the code that clears ->gpwrap and recomputes core_needs_qs from
rnp->qsmask. That case is now processed. It exists at boot:
rcu_data is statically initialized with .gpwrap = true, and
rcutree_prepare_cpu() sets rdp->gp_seq from rnp->gp_seq without
clearing it. Until the first grace period starts, ->gpwrap therefore
stays set and the ->gpwrap test in rcu_pending() reports work pending.
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
---
kernel/rcu/tree.c | 105 ++++++++++++++++++++++++++++------------------
1 file changed, 64 insertions(+), 41 deletions(-)
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index aea3aef7397ec..c224a8bc8aa6b 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -163,6 +163,7 @@ static void rcu_report_exp_rdp(struct rcu_data *rdp);
static void rcu_report_qs_rdp(struct rcu_data *rdp);
static void check_cb_ovld_locked(struct rcu_data *rdp, struct rcu_node *rnp);
static bool rcu_rdp_is_offloaded(struct rcu_data *rdp);
+static bool poll_state_synchronize_rcu_full_unordered(struct rcu_gp_seq *gsp);
static bool rcu_rdp_cpu_online(struct rcu_data *rdp);
static bool rcu_init_invoked(void);
static void rcu_cleanup_dead_rnp(struct rcu_node *rnp_leaf);
@@ -1260,30 +1261,63 @@ static void rcu_strict_gp_check_qs(void)
}
}
+/*
+ * Has a grace period (normal or expedited) that this CPU's pending callbacks
+ * are waiting on completed? Offloaded CPUs are handled by their rcuog
+ * kthreads, so always say "no" for them. This is a racy, memory-ordering-free
+ * hint: the ordered check is done by rcu_segcblist_advance() when the
+ * callbacks are actually advanced. The hint can also be true for callbacks
+ * that have not yet been assigned a grace period, because the stale
+ * ->gp_seq[RCU_WAIT_TAIL] then looks completed. That is harmless, as the
+ * resulting rcu_advance_cbs() assigns them one.
+ */
+static bool rcu_cbs_gp_done(struct rcu_data *rdp)
+{
+ struct rcu_gp_seq gp_state;
+
+ return !rcu_rdp_is_offloaded(rdp) &&
+ rcu_segcblist_nextgp(&rdp->cblist, &gp_state) &&
+ poll_state_synchronize_rcu_full_unordered(&gp_state);
+}
+
/*
* Update CPU-local rcu_data state to record the beginnings and ends of
* grace periods. The caller must hold the ->lock of the leaf rcu_node
* structure corresponding to the current CPU, and must have irqs disabled.
- * Returns true if the grace-period kthread needs to be awakened.
+ * Also advances callbacks whose grace period was an expedited one, which
+ * does not change ->gp_seq. Returns true if the grace-period kthread
+ * needs to be awakened.
*/
static bool __note_gp_changes(struct rcu_node *rnp, struct rcu_data *rdp)
{
bool ret = false;
bool need_qs;
+ bool cbs_done;
const bool offloaded = rcu_rdp_is_offloaded(rdp);
+ // ->gpwrap means ->gp_seq cannot be trusted, even if it now equals ours.
+ const bool gp_changed = rdp->gp_seq != rnp->gp_seq || unlikely(rdp->gpwrap);
+ const bool completed = rcu_seq_completed_gp(rdp->gp_seq, rnp->gp_seq) ||
+ unlikely(rdp->gpwrap);
raw_lockdep_assert_held_rcu_node(rnp);
- if (rdp->gp_seq == rnp->gp_seq)
+ /*
+ * Expedited grace periods do not change ->gp_seq, but they can still
+ * make callbacks ready to invoke. The callback list tracks both
+ * normal and expedited grace periods, so use it to detect that case.
+ */
+ cbs_done = !completed && rcu_cbs_gp_done(rdp);
+ if (!gp_changed && !cbs_done)
return false; /* Nothing to do. */
/* Handle the ends of any preceding grace periods first. */
- if (rcu_seq_completed_gp(rdp->gp_seq, rnp->gp_seq) ||
- unlikely(rdp->gpwrap)) {
+ if (completed || cbs_done) {
if (!offloaded)
ret = rcu_advance_cbs(rnp, rdp); /* Advance CBs. */
- rdp->core_needs_qs = false;
- trace_rcu_grace_period(rcu_state.name, rdp->gp_seq, TPS("cpuend"));
+ if (completed) {
+ rdp->core_needs_qs = false;
+ trace_rcu_grace_period(rcu_state.name, rdp->gp_seq, TPS("cpuend"));
+ }
} else {
if (!offloaded)
ret = rcu_accelerate_cbs(rnp, rdp); /* Recent CBs. */
@@ -1291,6 +1325,10 @@ static bool __note_gp_changes(struct rcu_node *rnp, struct rcu_data *rdp)
rdp->core_needs_qs = !!(rnp->qsmask & rdp->grpmask);
}
+ /* Expedited-only completion: no new grace-period state to record. */
+ if (!gp_changed)
+ return ret;
+
/* Now handle the beginnings of any new-to-this-CPU grace periods. */
if (rcu_seq_new_gp(rdp->gp_seq, rnp->gp_seq) ||
unlikely(rdp->gpwrap)) {
@@ -1315,6 +1353,18 @@ static bool __note_gp_changes(struct rcu_node *rnp, struct rcu_data *rdp)
return ret;
}
+/*
+ * Lockless check of whether __note_gp_changes() has anything to do: a normal
+ * grace period started or ended, ->gp_seq wrapped, or an expedited grace
+ * period completed that this CPU's callbacks were waiting on.
+ */
+static bool need_note_gp_changes(struct rcu_data *rdp)
+{
+ return rdp->gp_seq != rcu_seq_current(&rdp->mynode->gp_seq) ||
+ unlikely(READ_ONCE(rdp->gpwrap)) ||
+ rcu_cbs_gp_done(rdp);
+}
+
static void note_gp_changes(struct rcu_data *rdp)
{
unsigned long flags;
@@ -1323,8 +1373,7 @@ static void note_gp_changes(struct rcu_data *rdp)
local_irq_save(flags);
rnp = rdp->mynode;
- if ((rdp->gp_seq == rcu_seq_current(&rnp->gp_seq) &&
- !unlikely(READ_ONCE(rdp->gpwrap))) || /* w/out lock. */
+ if (!need_note_gp_changes(rdp) || /* w/out lock. */
!raw_spin_trylock_rcu_node(rnp)) { /* irqs already off, so later. */
local_irq_restore(flags);
return;
@@ -2886,23 +2935,6 @@ static __latent_entropy void rcu_core(void)
/* Update RCU state based on any recent quiescent states. */
rcu_check_quiescent_state(rdp);
- /* Advance callbacks if an expedited GP has completed. */
- if (!rcu_rdp_is_offloaded(rdp) && rcu_segcblist_is_enabled(&rdp->cblist)) {
- struct rcu_gp_seq gp_state;
-
- if (rcu_segcblist_nextgp(&rdp->cblist, &gp_state) &&
- poll_state_synchronize_rcu_full(&gp_state)) {
- guard(irqsave)();
- if (raw_spin_trylock_rcu_node(rnp)) {
- bool needwake = rcu_advance_cbs(rnp, rdp);
-
- raw_spin_unlock_rcu_node(rnp);
- if (needwake)
- rcu_gp_kthread_wake();
- }
- }
- }
-
/* No grace period and unregistered callbacks? */
if (!rcu_gp_in_progress() &&
rcu_segcblist_is_enabled(&rdp->cblist) && !rcu_rdp_is_offloaded(rdp)) {
@@ -3828,9 +3860,7 @@ EXPORT_SYMBOL_GPL(cond_synchronize_rcu_full);
static int rcu_pending(int user)
{
bool gp_in_progress;
- struct rcu_gp_seq gp_state;
struct rcu_data *rdp = this_cpu_ptr(&rcu_data);
- struct rcu_node *rnp = rdp->mynode;
lockdep_assert_irqs_disabled();
@@ -3859,26 +3889,19 @@ static int rcu_pending(int user)
rcu_segcblist_ready_cbs(&rdp->cblist))
return 1;
- /*
- * Has a GP (normal or expedited) completed for pending callbacks?
- * This is only a racy hint to decide whether to run rcu_core(); the
- * ordered re-check and callback advancement happen there, so the
- * unordered test avoids paying for memory barriers on every tick.
- */
- if (!rcu_rdp_is_offloaded(rdp) &&
- rcu_segcblist_nextgp(&rdp->cblist, &gp_state) &&
- poll_state_synchronize_rcu_full_unordered(&gp_state))
- return 1;
-
/* Has RCU gone idle with this CPU needing another grace period? */
if (!gp_in_progress && rcu_segcblist_is_enabled(&rdp->cblist) &&
!rcu_rdp_is_offloaded(rdp) &&
!rcu_segcblist_restempty(&rdp->cblist, RCU_NEXT_READY_TAIL))
return 1;
- /* Have RCU grace period completed or started? */
- if (rcu_seq_current(&rnp->gp_seq) != rdp->gp_seq ||
- unlikely(READ_ONCE(rdp->gpwrap))) /* outside lock */
+ /*
+ * Has a grace period completed or started, or have this CPU's
+ * callbacks' normal or expedited grace period completed? This is
+ * only a racy hint to run rcu_core(), where note_gp_changes()
+ * rechecks it under the lock.
+ */
+ if (need_note_gp_changes(rdp)) /* outside lock */
return 1;
/* nothing to do */
base-commit: 940793b926aa94bf65ec404043050888e3053188
--
2.53.0-Meta
next reply other threads:[~2026-10-16 10:28 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 10:28 Puranjay Mohan [this message]
2026-10-20 14:49 ` [PATCH] rcu: Move expedited-GP callback advancement into __note_gp_changes() Frederic Weisbecker
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261016102843.2782262-1-puranjay@kernel.org \
--to=puranjay@kernel.org \
--cc=boqun@kernel.org \
--cc=frederic@kernel.org \
--cc=joelagnelf@nvidia.com \
--cc=josh@joshtriplett.org \
--cc=paulmck@kernel.org \
--cc=rcu@vger.kernel.org \
--cc=rostedt@goodmis.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox