srcutree: Add reader-free fastpath to synchronize_srcu_atomic()

synchronize_srcu_atomic() is restricted to srcu_read_lock_atomic() and
srcu_read_unlock_atomic(), whose read-side critical sections disable
preemption.  In the common case where there are no readers at all, the
grace period therefore need not do the index flip.  Add a fastpath
that sums both ranks of the per-CPU ->srcu_ctrs[] counters and, if the
lock counts match the unlock counts on both ranks, ends the grace
period immediately, skipping the srcu_advance_state() scans, mirroring
the similar Tiny SRCU fastpath.

Correctness requires the counter-sum proof to follow the grace-period
anchor written by srcu_gp_start(); placing it before the anchor could
let this grace period miss a pre-existing reader and return without
waiting for it.  The smp_mb() between the unlock and lock sums pairs
with the smp_mb() in __srcu_read_lock().  The grace period is ended
manually under ->lock and ->srcu_atomic_gp_flag.

In theory, this is slower than David Woodhouse's earlier patch, but
David's measurements showed that the performance was close enough
that it makes sense to keep the get_state_synchronize_srcu() and
poll_state_synchronize_srcu semantics.

Link: https://lore.kernel.org/all/20260907075829.2073224-4-kunwu.chan@linux.dev/
Link: https://lore.kernel.org/all/0f4dea21bac43685d3286df329401177e4452b36.camel@infradead.org/
Link: https://lore.kernel.org/all/0d4af6318ac67486858be1df8d436147b444a2d2.camel@infradead.org/
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Cc: David Woodhouse <dwmw2@infradead.org>
This commit is contained in:
Kunwu Chan 2026-09-07 15:58:19 +08:00 committed by Paul E. McKenney
parent ddd326d8f3
commit 30d89cea5f

View File

@ -2119,6 +2119,8 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp)
{
unsigned long srcu_state;
struct srcu_usage *sup = ssp->srcu_sup;
unsigned long rdm0, rdm1;
unsigned long unlocks0, unlocks1;
// Initialize. Either init_srcu_struct() was invoked or
// DEFINE_SRCU() or similar was used. Therefore, no allocation
@ -2154,6 +2156,52 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp)
srcu_gp_start(ssp);
raw_spin_unlock_irq_rcu_node(sup);
//
// Fastpath: If there are no readers at all, neither grace-period
// scan need wait, so both can be satisfied at once without doing
// the index flip. The counter-sum proof is the same as that of
// srcu_readers_active_idx_check(), but spanning both indices.
// Atomic SRCU guarantees that all readers are of
// SRCU_READ_FLAVOR_ATOMIC, so the SLOWGP check never triggers and
// the ->srcu_reader_flavor masks returned by
// srcu_readers_unlock_idx() are unused.
//
// This proof must follow the grace-period anchor written by the
// srcu_gp_start() above, never precede it. With the anchor first,
// a reader whose lock increment is missed by the sums below cannot
// have incremented its lock counter before the anchor, and therefore
// cannot be a pre-existing reader of this grace period. Placing the
// proof before the anchor would let this grace period miss a
// pre-existing reader and return without waiting for it.
//
// The smp_mb() pairs with the smp_mb() in __srcu_read_lock()
// (store-buffering pattern), which guarantees that a lock is always
// counted if the corresponding unlock is counted, the same
// memory-ordering guarantee as is provided by
// srcu_readers_active_idx_check().
//
unlocks0 = srcu_readers_unlock_idx(ssp, 0, &rdm0);
unlocks1 = srcu_readers_unlock_idx(ssp, 1, &rdm1);
smp_mb(); /* A */
if (srcu_readers_lock_idx(ssp, 0, false, unlocks0) &&
srcu_readers_lock_idx(ssp, 1, false, unlocks1)) {
// No readers, so end this grace period manually, skipping
// the index flip. Advancing the sequence number via
// rcu_seq_start() in srcu_gp_start() above and rcu_seq_end()
// below keeps get_state_synchronize_srcu() and
// poll_state_synchronize_srcu() working, all under ->lock
// and ->srcu_atomic_gp_flag, which excludes concurrent
// sequence-number updates.
raw_spin_lock_irq_rcu_node(sup);
rcu_seq_end(&sup->srcu_gp_seq);
raw_spin_unlock_irq_rcu_node(sup);
WARN_ON_ONCE(!poll_state_synchronize_srcu(ssp, srcu_state));
atomic_set_release(&sup->srcu_atomic_gp_flag, 0);
preempt_enable();
non_block_end();
return;
}
// Wait for it to complete, helping it along.
while (!poll_state_synchronize_srcu(ssp, srcu_state)) {
cpu_relax();