srcu: Update comment after the index flip
authorPaul E. McKenney <paulmck@kernel.org>
Wed, 21 Dec 2022 16:32:51 +0000 (08:32 -0800)
committerPaul E. McKenney <paulmck@kernel.org>
Wed, 4 Jan 2023 01:49:23 +0000 (17:49 -0800)
Because there is not guaranteed to be a full memory barrier between
the ->srcu_unlock_count increment of an srcu_read_unlock() and the
->srcu_lock_count increment of the next srcu_read_lock(), this next
srcu_read_lock() is not guaranteed to see the effect of the index flip
just prior to this comment.  However, this next srcu_read_lock() will
execute a full memory barrier, so the srcu_read_lock() after that is
guaranteed to see that index flip.

This guarantee is illustrated by the following diagram of events and
the litmus test following that.

------------------------------------------------------------------------

READER                  UPDATER
-------------           ----------
                           // idx is initially 0.

                           srcu_flip() {
                              smp_mb();
// RSCS

srcu_read_unlock() {
  smp_mb();
                              idx++;    // P
                              smp_mb(); // QQ
                           }

                           srcu_readers_unlock_idx(0) {
        ,--counted------------ count all unlock[0]; // Q
        |
  unlock[0]++;  // X

}
                               smp_mb();
srcu_read_lock() {
  READ(idx) = 0;         ,---- count all lock[0]; // contributes imbalance of 1.
  lock[0]++;  ----counted              |
  smp_mb(); // PP          }           |
}                                      |
                                       |
// RSCS                             not going to effect above scan
                                       |
srcu_read_unlock() {                   |
  smp_mb();                            |
  unlock[0]++;                         |
}                                      |
                                      /
                                     /
srcu_read_lock() {                  |
  READ(idx);  // Y  -----cannot be counted because of P (has to sample idx as 1)
  lock[1]++;
  ...
}

------------------------------------------------------------------------

This makes it similar to the store buffer pattern. Using X, Y, P and Q
annotated above, we get:

------------------------------------------------------------------------

READER                    UPDATER
X (write)                 P (write)

smp_mb(); //PP            smp_mb(); //QQ

Y (read)                  Q (read)

------------------------------------------------------------------------

ASCII art courtesy of Joel Fernandes.

Reported-by: Joel Fernandes <joel@joelfernandes.org>
Reported-by: Boqun Feng <boqun.feng@gmail.com>
Reported-by: Frederic Weisbecker <frederic@kernel.org>
Reported-by: Neeraj Upadhyay <quic_neeraju@quicinc.com>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
kernel/rcu/srcutree.c

index d2d2e31c42b1360604a3295d350699d04be9e73a..ab4ee58af84bfcfaf6890a6650b40117be09bfb0 100644 (file)
@@ -1098,10 +1098,11 @@ static void srcu_flip(struct srcu_struct *ssp)
 
        /*
         * Ensure that if the updater misses an __srcu_read_unlock()
-        * increment, that task's next __srcu_read_lock() will see the
-        * above counter update.  Note that both this memory barrier
-        * and the one in srcu_readers_active_idx_check() provide the
-        * guarantee for __srcu_read_lock().
+        * increment, that task's __srcu_read_lock() following its next
+        * __srcu_read_lock() or __srcu_read_unlock() will see the above
+        * counter update.  Note that both this memory barrier and the
+        * one in srcu_readers_active_idx_check() provide the guarantee
+        * for __srcu_read_lock().
         */
        smp_mb(); /* D */  /* Pairs with C. */
 }