Skip to content

Commit 4060373

Browse files
committed
Merge tag 'lkmm.2023.04.07a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu
Pull Linux Kernel Memory Model updates from Paul McKenney "This improves LKMM diagnostic messages, unifies handling of the ordering produced by unlock/lock pairs, adds support for the smp_mb__after_srcu_read_unlock() macro, removes redundant members from the to-r relation, brings SRCU read-side semantics into alignment with Linux-kernel SRCU, makes ppo a subrelation of po, and improves documentation" * tag 'lkmm.2023.04.07a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu: Documentation: litmus-tests: Correct spelling tools/memory-model: Add documentation about SRCU read-side critical sections tools/memory-model: Make ppo a subrelation of po tools/memory-model: Provide exact SRCU semantics tools/memory-model: Restrict to-r to read-read address dependency tools/memory-model: Add smp_mb__after_srcu_read_unlock() tools/memory-model: Unify UNLOCK+LOCK pairings to po-unlock-lock-po tools/memory-model: Update some warning labels
2 parents 022e320 + 5737367 commit 4060373

File tree

6 files changed

+204
-39
lines changed

6 files changed

+204
-39
lines changed

Documentation/litmus-tests/README

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ a kernel test module based on a litmus test, please see
99
tools/memory-model/README.
1010

1111

12-
atomic (/atomic derectory)
12+
atomic (/atomic directory)
1313
--------------------------
1414

1515
Atomic-RMW+mb__after_atomic-is-stronger-than-acquire.litmus

tools/memory-model/Documentation/explanation.txt

+167-11
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ Explanation of the Linux-Kernel Memory Consistency Model
2828
20. THE HAPPENS-BEFORE RELATION: hb
2929
21. THE PROPAGATES-BEFORE RELATION: pb
3030
22. RCU RELATIONS: rcu-link, rcu-gp, rcu-rscsi, rcu-order, rcu-fence, and rb
31-
23. LOCKING
32-
24. PLAIN ACCESSES AND DATA RACES
33-
25. ODDS AND ENDS
31+
23. SRCU READ-SIDE CRITICAL SECTIONS
32+
24. LOCKING
33+
25. PLAIN ACCESSES AND DATA RACES
34+
26. ODDS AND ENDS
3435

3536

3637

@@ -1848,14 +1849,169 @@ section in P0 both starts before P1's grace period does and ends
18481849
before it does, and the critical section in P2 both starts after P1's
18491850
grace period does and ends after it does.
18501851

1851-
Addendum: The LKMM now supports SRCU (Sleepable Read-Copy-Update) in
1852-
addition to normal RCU. The ideas involved are much the same as
1853-
above, with new relations srcu-gp and srcu-rscsi added to represent
1854-
SRCU grace periods and read-side critical sections. There is a
1855-
restriction on the srcu-gp and srcu-rscsi links that can appear in an
1856-
rcu-order sequence (the srcu-rscsi links must be paired with srcu-gp
1857-
links having the same SRCU domain with proper nesting); the details
1858-
are relatively unimportant.
1852+
The LKMM supports SRCU (Sleepable Read-Copy-Update) in addition to
1853+
normal RCU. The ideas involved are much the same as above, with new
1854+
relations srcu-gp and srcu-rscsi added to represent SRCU grace periods
1855+
and read-side critical sections. However, there are some significant
1856+
differences between RCU read-side critical sections and their SRCU
1857+
counterparts, as described in the next section.
1858+
1859+
1860+
SRCU READ-SIDE CRITICAL SECTIONS
1861+
--------------------------------
1862+
1863+
The LKMM uses the srcu-rscsi relation to model SRCU read-side critical
1864+
sections. They differ from RCU read-side critical sections in the
1865+
following respects:
1866+
1867+
1. Unlike the analogous RCU primitives, synchronize_srcu(),
1868+
srcu_read_lock(), and srcu_read_unlock() take a pointer to a
1869+
struct srcu_struct as an argument. This structure is called
1870+
an SRCU domain, and calls linked by srcu-rscsi must have the
1871+
same domain. Read-side critical sections and grace periods
1872+
associated with different domains are independent of one
1873+
another; the SRCU version of the RCU Guarantee applies only
1874+
to pairs of critical sections and grace periods having the
1875+
same domain.
1876+
1877+
2. srcu_read_lock() returns a value, called the index, which must
1878+
be passed to the matching srcu_read_unlock() call. Unlike
1879+
rcu_read_lock() and rcu_read_unlock(), an srcu_read_lock()
1880+
call does not always have to match the next unpaired
1881+
srcu_read_unlock(). In fact, it is possible for two SRCU
1882+
read-side critical sections to overlap partially, as in the
1883+
following example (where s is an srcu_struct and idx1 and idx2
1884+
are integer variables):
1885+
1886+
idx1 = srcu_read_lock(&s); // Start of first RSCS
1887+
idx2 = srcu_read_lock(&s); // Start of second RSCS
1888+
srcu_read_unlock(&s, idx1); // End of first RSCS
1889+
srcu_read_unlock(&s, idx2); // End of second RSCS
1890+
1891+
The matching is determined entirely by the domain pointer and
1892+
index value. By contrast, if the calls had been
1893+
rcu_read_lock() and rcu_read_unlock() then they would have
1894+
created two nested (fully overlapping) read-side critical
1895+
sections: an inner one and an outer one.
1896+
1897+
3. The srcu_down_read() and srcu_up_read() primitives work
1898+
exactly like srcu_read_lock() and srcu_read_unlock(), except
1899+
that matching calls don't have to execute on the same CPU.
1900+
(The names are meant to be suggestive of operations on
1901+
semaphores.) Since the matching is determined by the domain
1902+
pointer and index value, these primitives make it possible for
1903+
an SRCU read-side critical section to start on one CPU and end
1904+
on another, so to speak.
1905+
1906+
In order to account for these properties of SRCU, the LKMM models
1907+
srcu_read_lock() as a special type of load event (which is
1908+
appropriate, since it takes a memory location as argument and returns
1909+
a value, just as a load does) and srcu_read_unlock() as a special type
1910+
of store event (again appropriate, since it takes as arguments a
1911+
memory location and a value). These loads and stores are annotated as
1912+
belonging to the "srcu-lock" and "srcu-unlock" event classes
1913+
respectively.
1914+
1915+
This approach allows the LKMM to tell whether two events are
1916+
associated with the same SRCU domain, simply by checking whether they
1917+
access the same memory location (i.e., they are linked by the loc
1918+
relation). It also gives a way to tell which unlock matches a
1919+
particular lock, by checking for the presence of a data dependency
1920+
from the load (srcu-lock) to the store (srcu-unlock). For example,
1921+
given the situation outlined earlier (with statement labels added):
1922+
1923+
A: idx1 = srcu_read_lock(&s);
1924+
B: idx2 = srcu_read_lock(&s);
1925+
C: srcu_read_unlock(&s, idx1);
1926+
D: srcu_read_unlock(&s, idx2);
1927+
1928+
the LKMM will treat A and B as loads from s yielding values saved in
1929+
idx1 and idx2 respectively. Similarly, it will treat C and D as
1930+
though they stored the values from idx1 and idx2 in s. The end result
1931+
is much as if we had written:
1932+
1933+
A: idx1 = READ_ONCE(s);
1934+
B: idx2 = READ_ONCE(s);
1935+
C: WRITE_ONCE(s, idx1);
1936+
D: WRITE_ONCE(s, idx2);
1937+
1938+
except for the presence of the special srcu-lock and srcu-unlock
1939+
annotations. You can see at once that we have A ->data C and
1940+
B ->data D. These dependencies tell the LKMM that C is the
1941+
srcu-unlock event matching srcu-lock event A, and D is the
1942+
srcu-unlock event matching srcu-lock event B.
1943+
1944+
This approach is admittedly a hack, and it has the potential to lead
1945+
to problems. For example, in:
1946+
1947+
idx1 = srcu_read_lock(&s);
1948+
srcu_read_unlock(&s, idx1);
1949+
idx2 = srcu_read_lock(&s);
1950+
srcu_read_unlock(&s, idx2);
1951+
1952+
the LKMM will believe that idx2 must have the same value as idx1,
1953+
since it reads from the immediately preceding store of idx1 in s.
1954+
Fortunately this won't matter, assuming that litmus tests never do
1955+
anything with SRCU index values other than pass them to
1956+
srcu_read_unlock() or srcu_up_read() calls.
1957+
1958+
However, sometimes it is necessary to store an index value in a
1959+
shared variable temporarily. In fact, this is the only way for
1960+
srcu_down_read() to pass the index it gets to an srcu_up_read() call
1961+
on a different CPU. In more detail, we might have soething like:
1962+
1963+
struct srcu_struct s;
1964+
int x;
1965+
1966+
P0()
1967+
{
1968+
int r0;
1969+
1970+
A: r0 = srcu_down_read(&s);
1971+
B: WRITE_ONCE(x, r0);
1972+
}
1973+
1974+
P1()
1975+
{
1976+
int r1;
1977+
1978+
C: r1 = READ_ONCE(x);
1979+
D: srcu_up_read(&s, r1);
1980+
}
1981+
1982+
Assuming that P1 executes after P0 and does read the index value
1983+
stored in x, we can write this (using brackets to represent event
1984+
annotations) as:
1985+
1986+
A[srcu-lock] ->data B[once] ->rf C[once] ->data D[srcu-unlock].
1987+
1988+
The LKMM defines a carry-srcu-data relation to express this pattern;
1989+
it permits an arbitrarily long sequence of
1990+
1991+
data ; rf
1992+
1993+
pairs (that is, a data link followed by an rf link) to occur between
1994+
an srcu-lock event and the final data dependency leading to the
1995+
matching srcu-unlock event. carry-srcu-data is complicated by the
1996+
need to ensure that none of the intermediate store events in this
1997+
sequence are instances of srcu-unlock. This is necessary because in a
1998+
pattern like the one above:
1999+
2000+
A: idx1 = srcu_read_lock(&s);
2001+
B: srcu_read_unlock(&s, idx1);
2002+
C: idx2 = srcu_read_lock(&s);
2003+
D: srcu_read_unlock(&s, idx2);
2004+
2005+
the LKMM treats B as a store to the variable s and C as a load from
2006+
that variable, creating an undesirable rf link from B to C:
2007+
2008+
A ->data B ->rf C ->data D.
2009+
2010+
This would cause carry-srcu-data to mistakenly extend a data
2011+
dependency from A to D, giving the impression that D was the
2012+
srcu-unlock event matching A's srcu-lock. To avoid such problems,
2013+
carry-srcu-data does not accept sequences in which the ends of any of
2014+
the intermediate ->data links (B above) is an srcu-unlock event.
18592015

18602016

18612017
LOCKING

tools/memory-model/linux-kernel.bell

+12-18
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ enum Barriers = 'wmb (*smp_wmb*) ||
3131
'before-atomic (*smp_mb__before_atomic*) ||
3232
'after-atomic (*smp_mb__after_atomic*) ||
3333
'after-spinlock (*smp_mb__after_spinlock*) ||
34-
'after-unlock-lock (*smp_mb__after_unlock_lock*)
34+
'after-unlock-lock (*smp_mb__after_unlock_lock*) ||
35+
'after-srcu-read-unlock (*smp_mb__after_srcu_read_unlock*)
3536
instructions F[Barriers]
3637

3738
(* SRCU *)
@@ -53,38 +54,31 @@ let rcu-rscs = let rec
5354
in matched
5455

5556
(* Validate nesting *)
56-
flag ~empty Rcu-lock \ domain(rcu-rscs) as unbalanced-rcu-locking
57-
flag ~empty Rcu-unlock \ range(rcu-rscs) as unbalanced-rcu-locking
57+
flag ~empty Rcu-lock \ domain(rcu-rscs) as unmatched-rcu-lock
58+
flag ~empty Rcu-unlock \ range(rcu-rscs) as unmatched-rcu-unlock
5859

5960
(* Compute matching pairs of nested Srcu-lock and Srcu-unlock *)
60-
let srcu-rscs = let rec
61-
unmatched-locks = Srcu-lock \ domain(matched)
62-
and unmatched-unlocks = Srcu-unlock \ range(matched)
63-
and unmatched = unmatched-locks | unmatched-unlocks
64-
and unmatched-po = ([unmatched] ; po ; [unmatched]) & loc
65-
and unmatched-locks-to-unlocks =
66-
([unmatched-locks] ; po ; [unmatched-unlocks]) & loc
67-
and matched = matched | (unmatched-locks-to-unlocks \
68-
(unmatched-po ; unmatched-po))
69-
in matched
61+
let carry-srcu-data = (data ; [~ Srcu-unlock] ; rf)*
62+
let srcu-rscs = ([Srcu-lock] ; carry-srcu-data ; data ; [Srcu-unlock]) & loc
7063

7164
(* Validate nesting *)
72-
flag ~empty Srcu-lock \ domain(srcu-rscs) as unbalanced-srcu-locking
73-
flag ~empty Srcu-unlock \ range(srcu-rscs) as unbalanced-srcu-locking
65+
flag ~empty Srcu-lock \ domain(srcu-rscs) as unmatched-srcu-lock
66+
flag ~empty Srcu-unlock \ range(srcu-rscs) as unmatched-srcu-unlock
67+
flag ~empty (srcu-rscs^-1 ; srcu-rscs) \ id as multiple-srcu-matches
7468

7569
(* Check for use of synchronize_srcu() inside an RCU critical section *)
7670
flag ~empty rcu-rscs & (po ; [Sync-srcu] ; po) as invalid-sleep
7771

7872
(* Validate SRCU dynamic match *)
79-
flag ~empty different-values(srcu-rscs) as srcu-bad-nesting
73+
flag ~empty different-values(srcu-rscs) as srcu-bad-value-match
8074

8175
(* Compute marked and plain memory accesses *)
8276
let Marked = (~M) | IW | Once | Release | Acquire | domain(rmw) | range(rmw) |
83-
LKR | LKW | UL | LF | RL | RU
77+
LKR | LKW | UL | LF | RL | RU | Srcu-lock | Srcu-unlock
8478
let Plain = M \ Marked
8579

8680
(* Redefine dependencies to include those carried through plain accesses *)
87-
let carry-dep = (data ; rfi)*
81+
let carry-dep = (data ; [~ Srcu-unlock] ; rfi)*
8882
let addr = carry-dep ; addr
8983
let ctrl = carry-dep ; ctrl
9084
let data = carry-dep ; data

tools/memory-model/linux-kernel.cat

+16-4
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,20 @@ let mb = ([M] ; fencerel(Mb) ; [M]) |
3737
([M] ; fencerel(Before-atomic) ; [RMW] ; po? ; [M]) |
3838
([M] ; po? ; [RMW] ; fencerel(After-atomic) ; [M]) |
3939
([M] ; po? ; [LKW] ; fencerel(After-spinlock) ; [M]) |
40-
([M] ; po ; [UL] ; (co | po) ; [LKW] ;
41-
fencerel(After-unlock-lock) ; [M])
40+
(*
41+
* Note: The po-unlock-lock-po relation only passes the lock to the direct
42+
* successor, perhaps giving the impression that the ordering of the
43+
* smp_mb__after_unlock_lock() fence only affects a single lock handover.
44+
* However, in a longer sequence of lock handovers, the implicit
45+
* A-cumulative release fences of lock-release ensure that any stores that
46+
* propagate to one of the involved CPUs before it hands over the lock to
47+
* the next CPU will also propagate to the final CPU handing over the lock
48+
* to the CPU that executes the fence. Therefore, all those stores are
49+
* also affected by the fence.
50+
*)
51+
([M] ; po-unlock-lock-po ;
52+
[After-unlock-lock] ; po ; [M]) |
53+
([M] ; po? ; [Srcu-unlock] ; fencerel(After-srcu-read-unlock) ; [M])
4254
let gp = po ; [Sync-rcu | Sync-srcu] ; po?
4355
let strong-fence = mb | gp
4456

@@ -69,8 +81,8 @@ let dep = addr | data
6981
let rwdep = (dep | ctrl) ; [W]
7082
let overwrite = co | fr
7183
let to-w = rwdep | (overwrite & int) | (addr ; [Plain] ; wmb)
72-
let to-r = addr | (dep ; [Marked] ; rfi)
73-
let ppo = to-r | to-w | fence | (po-unlock-lock-po & int)
84+
let to-r = (addr ; [R]) | (dep ; [Marked] ; rfi)
85+
let ppo = to-r | to-w | (fence & int) | (po-unlock-lock-po & int)
7486

7587
(* Propagation: Ordering from release operations and strong fences. *)
7688
let A-cumul(r) = (rfe ; [Marked])? ; r

tools/memory-model/linux-kernel.def

+5-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ smp_mb__before_atomic() { __fence{before-atomic}; }
2424
smp_mb__after_atomic() { __fence{after-atomic}; }
2525
smp_mb__after_spinlock() { __fence{after-spinlock}; }
2626
smp_mb__after_unlock_lock() { __fence{after-unlock-lock}; }
27+
smp_mb__after_srcu_read_unlock() { __fence{after-srcu-read-unlock}; }
2728
barrier() { __fence{barrier}; }
2829

2930
// Exchange
@@ -49,8 +50,10 @@ synchronize_rcu() { __fence{sync-rcu}; }
4950
synchronize_rcu_expedited() { __fence{sync-rcu}; }
5051

5152
// SRCU
52-
srcu_read_lock(X) __srcu{srcu-lock}(X)
53-
srcu_read_unlock(X,Y) { __srcu{srcu-unlock}(X,Y); }
53+
srcu_read_lock(X) __load{srcu-lock}(*X)
54+
srcu_read_unlock(X,Y) { __store{srcu-unlock}(*X,Y); }
55+
srcu_down_read(X) __load{srcu-lock}(*X)
56+
srcu_up_read(X,Y) { __store{srcu-unlock}(*X,Y); }
5457
synchronize_srcu(X) { __srcu{sync-srcu}(X); }
5558
synchronize_srcu_expedited(X) { __srcu{sync-srcu}(X); }
5659

tools/memory-model/lock.cat

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ let RU = try RU with emptyset
3636
(* Treat RL as a kind of LF: a read with no ordering properties *)
3737
let LF = LF | RL
3838

39-
(* There should be no ordinary R or W accesses to spinlocks *)
40-
let ALL-LOCKS = LKR | LKW | UL | LF | RU
41-
flag ~empty [M \ IW] ; loc ; [ALL-LOCKS] as mixed-lock-accesses
39+
(* There should be no ordinary R or W accesses to spinlocks or SRCU structs *)
40+
let ALL-LOCKS = LKR | LKW | UL | LF | RU | Srcu-lock | Srcu-unlock | Sync-srcu
41+
flag ~empty [M \ IW \ ALL-LOCKS] ; loc ; [ALL-LOCKS] as mixed-lock-accesses
4242

4343
(* Link Lock-Reads to their RMW-partner Lock-Writes *)
4444
let lk-rmw = ([LKR] ; po-loc ; [LKW]) \ (po ; po)

0 commit comments

Comments
 (0)