mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 01:10:04 +00:00
![Alan Stern](/assets/img/avatar_default.png)
LKMM has long provided only approximate handling of SRCU read-side critical sections. This has not been a pressing problem because LKMM's traditional handling is correct for the common cases of non-overlapping and properly nested critical sections. However, LKMM's traditional handling of partially overlapping critical sections incorrectly fuses them into one large critical section. For example, consider the following litmus test: ------------------------------------------------------------------------ C C-srcu-nest-5 (* * Result: Sometimes * * This demonstrates non-nested overlapping of SRCU read-side critical * sections. Unlike RCU, SRCU critical sections do not unconditionally * nest. *) {} P0(int *x, int *y, struct srcu_struct *s1) { int r1; int r2; int r3; int r4; r3 = srcu_read_lock(s1); r2 = READ_ONCE(*y); r4 = srcu_read_lock(s1); srcu_read_unlock(s1, r3); r1 = READ_ONCE(*x); srcu_read_unlock(s1, r4); } P1(int *x, int *y, struct srcu_struct *s1) { WRITE_ONCE(*y, 1); synchronize_srcu(s1); WRITE_ONCE(*x, 1); } locations [0:r1] exists (0:r1=1 /\ 0:r2=0) ------------------------------------------------------------------------ Current mainline incorrectly flattens the two critical sections into one larger critical section, giving "Never" instead of the correct "Sometimes": ------------------------------------------------------------------------ $ herd7 -conf linux-kernel.cfg C-srcu-nest-5.litmus Test C-srcu-nest-5 Allowed States 3 0:r1=0; 0:r2=0; 0:r1=0; 0:r2=1; 0:r1=1; 0:r2=1; No Witnesses Positive: 0 Negative: 3 Flag srcu-bad-nesting Condition exists (0:r1=1 /\ 0:r2=0) Observation C-srcu-nest-5 Never 0 3 Time C-srcu-nest-5 0.01 Hash=e692c106cf3e84e20f12991dc438ff1b ------------------------------------------------------------------------ To its credit, it does complain about bad nesting. But with this commit we get the following result, which has the virtue of being correct: ------------------------------------------------------------------------ $ herd7 -conf linux-kernel.cfg C-srcu-nest-5.litmus Test C-srcu-nest-5 Allowed States 4 0:r1=0; 0:r2=0; 0:r1=0; 0:r2=1; 0:r1=1; 0:r2=0; 0:r1=1; 0:r2=1; Ok Witnesses Positive: 1 Negative: 3 Condition exists (0:r1=1 /\ 0:r2=0) Observation C-srcu-nest-5 Sometimes 1 3 Time C-srcu-nest-5 0.05 Hash=e692c106cf3e84e20f12991dc438ff1b ------------------------------------------------------------------------ In addition, there are new srcu_down_read() and srcu_up_read() functions on their way to mainline. Roughly speaking, these are to srcu_read_lock() and srcu_read_unlock() as down() and up() are to mutex_lock() and mutex_unlock(). The key point is that srcu_down_read() can execute in one process and the matching srcu_up_read() in another, as shown in this litmus test: ------------------------------------------------------------------------ C C-srcu-nest-6 (* * Result: Never * * This would be valid for srcu_down_read() and srcu_up_read(). *) {} P0(int *x, int *y, struct srcu_struct *s1, int *idx, int *f) { int r2; int r3; r3 = srcu_down_read(s1); WRITE_ONCE(*idx, r3); r2 = READ_ONCE(*y); smp_store_release(f, 1); } P1(int *x, int *y, struct srcu_struct *s1, int *idx, int *f) { int r1; int r3; int r4; r4 = smp_load_acquire(f); r1 = READ_ONCE(*x); r3 = READ_ONCE(*idx); srcu_up_read(s1, r3); } P2(int *x, int *y, struct srcu_struct *s1) { WRITE_ONCE(*y, 1); synchronize_srcu(s1); WRITE_ONCE(*x, 1); } locations [0:r1] filter (1:r4=1) exists (1:r1=1 /\ 0:r2=0) ------------------------------------------------------------------------ When run on current mainline, this litmus test gets a complaint about an unknown macro srcu_down_read(). With this commit: ------------------------------------------------------------------------ herd7 -conf linux-kernel.cfg C-srcu-nest-6.litmus Test C-srcu-nest-6 Allowed States 3 0:r1=0; 0:r2=0; 1:r1=0; 0:r1=0; 0:r2=1; 1:r1=0; 0:r1=0; 0:r2=1; 1:r1=1; No Witnesses Positive: 0 Negative: 3 Condition exists (1:r1=1 /\ 0:r2=0) Observation C-srcu-nest-6 Never 0 3 Time C-srcu-nest-6 0.02 Hash=c1f20257d052ca5e899be508bedcb2a1 ------------------------------------------------------------------------ Note that the user must supply the flag "f" and the "filter" clause, similar to what must be done to emulate call_rcu(). The commit works by treating srcu_read_lock()/srcu_down_read() as loads and srcu_read_unlock()/srcu_up_read() as stores. This allows us to determine which unlock matches which lock by looking for a data dependency between them. In order for this to work properly, the data dependencies have to be tracked through stores to intermediate variables such as "idx" in the litmus test above; this is handled by the new carry-srcu-data relation. But it's important here (and in the existing carry-dep relation) to avoid tracking the dependencies through SRCU unlock stores. Otherwise, in situations resembling: A: r1 = srcu_read_lock(s); B: srcu_read_unlock(s, r1); C: r2 = srcu_read_lock(s); D: srcu_read_unlock(s, r2); it would look as if D was dependent on both A and C, because "s" would appear to be an intermediate variable written by B and read by C. This explains the complications in the definitions of carry-srcu-dep and carry-dep. As a debugging aid, the commit adds a check for errors in which the value returned by one call to srcu_read_lock()/srcu_down_read() is passed to more than one instance of srcu_read_unlock()/srcu_up_read(). Finally, since these SRCU-related primitives are now treated as ordinary reads and writes, we have to add them into the lists of marked accesses (i.e., not subject to data races) and lock-related accesses (i.e., one shouldn't try to access an srcu_struct with a non-lock-related primitive such as READ_ONCE() or a plain write). Portions of this approach were suggested by Boqun Feng and Jonas Oberhauser. [ paulmck: Fix space-before-tab whitespace nit. ] Reported-by: Paul E. McKenney <paulmck@kernel.org> Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Reviewed-by: Jonas Oberhauser <jonas.oberhauser@huaweicloud.com> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
143 lines
4.6 KiB
Text
143 lines
4.6 KiB
Text
// SPDX-License-Identifier: GPL-2.0+
|
|
(*
|
|
* Copyright (C) 2016 Luc Maranget <luc.maranget@inria.fr> for Inria
|
|
* Copyright (C) 2017 Alan Stern <stern@rowland.harvard.edu>
|
|
*)
|
|
|
|
(*
|
|
* Generate coherence orders and handle lock operations
|
|
*)
|
|
|
|
include "cross.cat"
|
|
|
|
(*
|
|
* The lock-related events generated by herd7 are as follows:
|
|
*
|
|
* LKR Lock-Read: the read part of a spin_lock() or successful
|
|
* spin_trylock() read-modify-write event pair
|
|
* LKW Lock-Write: the write part of a spin_lock() or successful
|
|
* spin_trylock() RMW event pair
|
|
* UL Unlock: a spin_unlock() event
|
|
* LF Lock-Fail: a failed spin_trylock() event
|
|
* RL Read-Locked: a spin_is_locked() event which returns True
|
|
* RU Read-Unlocked: a spin_is_locked() event which returns False
|
|
*
|
|
* LKR and LKW events always come paired, like all RMW event sequences.
|
|
*
|
|
* LKR, LF, RL, and RU are read events; LKR has Acquire ordering.
|
|
* LKW and UL are write events; UL has Release ordering.
|
|
* LKW, LF, RL, and RU have no ordering properties.
|
|
*)
|
|
|
|
(* Backward compatibility *)
|
|
let RL = try RL with emptyset
|
|
let RU = try RU with emptyset
|
|
|
|
(* Treat RL as a kind of LF: a read with no ordering properties *)
|
|
let LF = LF | RL
|
|
|
|
(* There should be no ordinary R or W accesses to spinlocks or SRCU structs *)
|
|
let ALL-LOCKS = LKR | LKW | UL | LF | RU | Srcu-lock | Srcu-unlock | Sync-srcu
|
|
flag ~empty [M \ IW \ ALL-LOCKS] ; loc ; [ALL-LOCKS] as mixed-lock-accesses
|
|
|
|
(* Link Lock-Reads to their RMW-partner Lock-Writes *)
|
|
let lk-rmw = ([LKR] ; po-loc ; [LKW]) \ (po ; po)
|
|
let rmw = rmw | lk-rmw
|
|
|
|
(* The litmus test is invalid if an LKR/LKW event is not part of an RMW pair *)
|
|
flag ~empty LKW \ range(lk-rmw) as unpaired-LKW
|
|
flag ~empty LKR \ domain(lk-rmw) as unpaired-LKR
|
|
|
|
(*
|
|
* An LKR must always see an unlocked value; spin_lock() calls nested
|
|
* inside a critical section (for the same lock) always deadlock.
|
|
*)
|
|
empty ([LKW] ; po-loc ; [LKR]) \ (po-loc ; [UL] ; po-loc) as lock-nest
|
|
|
|
(* The final value of a spinlock should not be tested *)
|
|
flag ~empty [FW] ; loc ; [ALL-LOCKS] as lock-final
|
|
|
|
(*
|
|
* Put lock operations in their appropriate classes, but leave UL out of W
|
|
* until after the co relation has been generated.
|
|
*)
|
|
let R = R | LKR | LF | RU
|
|
let W = W | LKW
|
|
|
|
let Release = Release | UL
|
|
let Acquire = Acquire | LKR
|
|
|
|
(* Match LKW events to their corresponding UL events *)
|
|
let critical = ([LKW] ; po-loc ; [UL]) \ (po-loc ; [LKW | UL] ; po-loc)
|
|
|
|
flag ~empty UL \ range(critical) as unmatched-unlock
|
|
|
|
(* Allow up to one unmatched LKW per location; more must deadlock *)
|
|
let UNMATCHED-LKW = LKW \ domain(critical)
|
|
empty ([UNMATCHED-LKW] ; loc ; [UNMATCHED-LKW]) \ id as unmatched-locks
|
|
|
|
(* rfi for LF events: link each LKW to the LF events in its critical section *)
|
|
let rfi-lf = ([LKW] ; po-loc ; [LF]) \ ([LKW] ; po-loc ; [UL] ; po-loc)
|
|
|
|
(* rfe for LF events *)
|
|
let all-possible-rfe-lf =
|
|
(*
|
|
* Given an LF event r, compute the possible rfe edges for that event
|
|
* (all those starting from LKW events in other threads),
|
|
* and then convert that relation to a set of single-edge relations.
|
|
*)
|
|
let possible-rfe-lf r =
|
|
let pair-to-relation p = p ++ 0
|
|
in map pair-to-relation ((LKW * {r}) & loc & ext)
|
|
(* Do this for each LF event r that isn't in rfi-lf *)
|
|
in map possible-rfe-lf (LF \ range(rfi-lf))
|
|
|
|
(* Generate all rf relations for LF events *)
|
|
with rfe-lf from cross(all-possible-rfe-lf)
|
|
let rf-lf = rfe-lf | rfi-lf
|
|
|
|
(*
|
|
* RU, i.e., spin_is_locked() returning False, is slightly different.
|
|
* We rely on the memory model to rule out cases where spin_is_locked()
|
|
* within one of the lock's critical sections returns False.
|
|
*)
|
|
|
|
(* rfi for RU events: an RU may read from the last po-previous UL *)
|
|
let rfi-ru = ([UL] ; po-loc ; [RU]) \ ([UL] ; po-loc ; [LKW] ; po-loc)
|
|
|
|
(* rfe for RU events: an RU may read from an external UL or the initial write *)
|
|
let all-possible-rfe-ru =
|
|
let possible-rfe-ru r =
|
|
let pair-to-relation p = p ++ 0
|
|
in map pair-to-relation (((UL | IW) * {r}) & loc & ext)
|
|
in map possible-rfe-ru RU
|
|
|
|
(* Generate all rf relations for RU events *)
|
|
with rfe-ru from cross(all-possible-rfe-ru)
|
|
let rf-ru = rfe-ru | rfi-ru
|
|
|
|
(* Final rf relation *)
|
|
let rf = rf | rf-lf | rf-ru
|
|
|
|
(* Generate all co relations, including LKW events but not UL *)
|
|
let co0 = co0 | ([IW] ; loc ; [LKW]) |
|
|
(([LKW] ; loc ; [UNMATCHED-LKW]) \ [UNMATCHED-LKW])
|
|
include "cos-opt.cat"
|
|
let W = W | UL
|
|
let M = R | W
|
|
|
|
(* Merge UL events into co *)
|
|
let co = (co | critical | (critical^-1 ; co))+
|
|
let coe = co & ext
|
|
let coi = co & int
|
|
|
|
(* Merge LKR events into rf *)
|
|
let rf = rf | ([IW | UL] ; singlestep(co) ; lk-rmw^-1)
|
|
let rfe = rf & ext
|
|
let rfi = rf & int
|
|
|
|
let fr = rf^-1 ; co
|
|
let fre = fr & ext
|
|
let fri = fr & int
|
|
|
|
show co,rf,fr
|