March 8, 2019
This article was contributed by
Jade Alglave, Will Deacon, Boqun Feng, David Howells, Daniel
Lustig, Luc Maranget, Paul E. McKenney, Andrea Parri, Nicholas
Piggin, Alan Stern, Akira Yokosawa, and Peter Zijlstra
Introduction
The Linux-kernel memory model (LKMM) (described
here and
here)
was accepted into the Linux kernel in early 2018 and has seen
continued development since.
One item of development is the addition of
Sleepable RCU (SRCU),
as described in this article.
SRCU is similar to RCU in having read-side critical sections
(which begin with srcu_read_lock() and end with
srcu_read_unlock()) and grace periods (which may
be waited on by invoking synchronize_srcu()).
Although the most distinctive attribute of SRCU is the fact that SRCU
readers are allowed to block arbitrarily, LKMM does not model the
scheduler in general or blocking in particular.
Therefore, the most pertinent aspects of SRCU are:
(1) Passing the return value from srcu_read_lock() into
the corresponding srcu_read_unlock(), and
(2) The domains provided by the srcu_struct structure.
However, these two SRCU aspects are actually consequences of safe and
efficient support of sleeping readers, so we have come full circle.
The domain is specified by a pointer to an srcu_struct
structure that is passed to srcu_read_lock(),
srcu_read_unlock(), and synchronize_srcu().
Each call to synchronize_srcu() is required to wait only for
those SRCU read-side critical sections using the same srcu_struct
structure.
This means that an SRCU reader that blocks for an excessive time period
only impedes those grace periods in the same domain.
This in turn allows each domain to choose its own definition of
“excessively long”.
The need to pass the return value from srcu_read_lock()
to the matching srcu_read_unlock() stems from the
implementation-level need to avoid avoid explicitly tracking the
state of up to O(N*M) SRCU readers,
where N is the number of tasks and M is the number
of instances of srcu_struct structures.
Passing the return value from srcu_read_lock() to the
matching srcu_read_unlock() allows this state to be
stored on the stack, and to not be represented at all for tasks
not in an SRCU read-side critical section.
To the best of our knowledge, LKMM provides the first realistic automated
representation of Linux-kernel SRCU's ordering properties.
This article is organized as follows:
- Invoking LKMM
- Modeling SRCU
-
Multiple srcu_struct Structures
-
Approximating SRCU: Why and How?
- Adding SRCU to LKMM
- Conclusions
This is followed by the ineludible
answers to the quick quizzes.
LKMM was accepted into version v4.17 of the Linux kernel, so if you have
a recent Linux-kernel source tree, you have LKMM!
Actually running LKMM requires installing herdtools as described in
tools/memory-model/README.
Once you have these tools installed, given a litmus test in
“/path/to/file.litmus” and the Linux-kernel source
tree in “/path/to/Linux/kernel”, you can run that
litmus test as follows:
cd /path/to/Linux/kernel/tools/memory-model
herd7 -conf linux-kernel.cfg /path/to/file.litmus
This will result in output analyzing file.litmus similar
to that shown below.
SRCU is normally used in a manner similar to RCU, as illustrated
by this litmus test:
Litmus Test #1
1 C C-s1
2
3 {}
4
5 P0(struct srcu_struct *ssp1, int *a, int *b)
6 {
7 int i1;
8 int r0;
9
10 i1 = srcu_read_lock(ssp1);
11 r0 = READ_ONCE(*b);
12 WRITE_ONCE(*a, 1);
13 srcu_read_unlock(ssp1, i1);
14 }
15
16 P1(struct srcu_struct *ssp1, int *a, int *b)
17 {
18 int r1;
19
20 r1 = READ_ONCE(*a);
21 synchronize_srcu(ssp1);
22 WRITE_ONCE(*b, 1);
23 }
24
25 exists (0:r0=1 /\ 1:r1=1)
Here, P0() is the SRCU reader and P1() is
the SRCU updater, and as noted earlier, the usage is quite similar
to that of RCU.
Therefore, in the usual RCU manner, P0()'s SRCU read-side critical
section spanning lines 10-13 must be ordered either entirely
before the end of or after the beginning of P1()'s
grace period on line 21.
Because
Litmus Test #1
has (trivially) properly nested SRCU read-side critical sections and
a pointer to the same srcu_struct structure ssp1 used
everywhere, the exists clause cannot be satisfied:
Outcome for Litmus Test #1 (linux-kernel model)
1 Test C-s1 Allowed
2 States 3
3 0:r0=0; 1:r1=0;
4 0:r0=0; 1:r1=1;
5 0:r0=1; 1:r1=0;
6 No
7 Witnesses
8 Positive: 0 Negative: 3
9 Condition exists (0:r0=1 /\ 1:r1=1)
10 Observation C-s1 Never 0 3
11 Time C-s1 0.01
12 Hash=4e6ad6544498deb56ed8f5823f591bba
Assuming use of a single srcu_struct structure, the SRCU
guarantee can be extended to multiple SRCU read-side critical sections
and multiple grace periods in the same way
RCU's guarantee can be extended.
Quick Quiz 1:
What if a pair of SRCU read-side critical sections using the same
srcu_struct structure partially overlap instead of being
properly nested?
Answer
Suppose P0() and P1() use different srcu_struct
structures, as shown below:
Litmus Test #2
1 C C-s1-mismatch
2
3 {}
4
5 P0(struct srcu_struct *ssp1, int *a, int *b)
6 {
7 int i1;
8 int r0;
9
10 i1 = srcu_read_lock(ssp1);
11 r0 = READ_ONCE(*b);
12 WRITE_ONCE(*a, 1);
13 srcu_read_unlock(ssp1, i1);
14 }
15
16 P1(struct srcu_struct *ssp2, int *a, int *b)
17 {
18 int r1;
19
20 r1 = READ_ONCE(*a);
21 synchronize_srcu(ssp2);
22 WRITE_ONCE(*b, 1);
23 }
24
25 exists (0:r0=1 /\ 1:r1=1)
In this case, P0()'s SRCU read-side critical section has no
relation to P1()'s SRCU grace period, which means that the
exists clause can be satisfied:
Outcome for Litmus Test #2 (linux-kernel model)
1 Test C-s1-mismatch Allowed
2 States 4
3 0:r0=0; 1:r1=0;
4 0:r0=0; 1:r1=1;
5 0:r0=1; 1:r1=0;
6 0:r0=1; 1:r1=1;
7 Ok
8 Witnesses
9 Positive: 1 Negative: 3
10 Condition exists (0:r0=1 /\ 1:r1=1)
11 Observation C-s1-mismatch Sometimes 1 3
12 Time C-s1-mismatch 0.01
13 Hash=2d13cb3431b8cafa6f620b5e78c88150
Therefore, don't mix and match srcu_struct structures unless
you really mean to do so!
Here is one way to successfully combine two different srcu_struct
structures:
Litmus Test #3
1 C SRCU-42-A
2
3 {
4 }
5
6 P0(int *x0, int *x1, struct srcu_struct *ssp1)
7 {
8 int r0;
9 int r1;
10
11 r0 = srcu_read_lock(ssp1);
12 r1 = READ_ONCE(*x0);
13 WRITE_ONCE(*x1, 1);
14 srcu_read_unlock(ssp1, r0);
15 }
16
17 P1(int *x1, int *x2, struct srcu_struct *ssp1)
18 {
19 int r1;
20
21 r1 = READ_ONCE(*x1);
22 synchronize_srcu(ssp1);
23 WRITE_ONCE(*x2, 1);
24 }
25
26 P2(int *x2, int *x3, struct srcu_struct *ssp2)
27 {
28 int r0;
29 int r1;
30
31 r0 = srcu_read_lock(ssp2);
32 WRITE_ONCE(*x3, 1);
33 r1 = READ_ONCE(*x2);
34 srcu_read_unlock(ssp2, r0);
35 }
36
37 P3(int *x3, int *x0, struct srcu_struct *ssp2)
38 {
39 int r1;
40
41 r1 = READ_ONCE(*x3);
42 synchronize_srcu(ssp2);
43 WRITE_ONCE(*x0, 1);
44 }
45
46 exists
47 (0:r1=1 /\ 1:r1=1 /\ 2:r1=1 /\ 3:r1=1)
In this litmus test, P0() has an SRCU read-side critical section
for ssp1 and P1() has an SRCU grace period, also for
ssp1.
Similarly, P2() has an SRCU read-side critical section
for ssp2 and P3() has an SRCU grace period, also for
ssp2.
This combination causes the exists clause to be forbidden:
Outcome for Litmus Test #3 (linux-kernel model)
1 Test SRCU-42-A Allowed
2 States 15
3 0:r1=0; 1:r1=0; 2:r1=0; 3:r1=0;
4 0:r1=0; 1:r1=0; 2:r1=0; 3:r1=1;
5 0:r1=0; 1:r1=0; 2:r1=1; 3:r1=0;
6 0:r1=0; 1:r1=0; 2:r1=1; 3:r1=1;
7 0:r1=0; 1:r1=1; 2:r1=0; 3:r1=0;
8 0:r1=0; 1:r1=1; 2:r1=0; 3:r1=1;
9 0:r1=0; 1:r1=1; 2:r1=1; 3:r1=0;
10 0:r1=0; 1:r1=1; 2:r1=1; 3:r1=1;
11 0:r1=1; 1:r1=0; 2:r1=0; 3:r1=0;
12 0:r1=1; 1:r1=0; 2:r1=0; 3:r1=1;
13 0:r1=1; 1:r1=0; 2:r1=1; 3:r1=0;
14 0:r1=1; 1:r1=0; 2:r1=1; 3:r1=1;
15 0:r1=1; 1:r1=1; 2:r1=0; 3:r1=0;
16 0:r1=1; 1:r1=1; 2:r1=0; 3:r1=1;
17 0:r1=1; 1:r1=1; 2:r1=1; 3:r1=0;
18 No
19 Witnesses
20 Positive: 0 Negative: 15
21 Condition exists (0:r1=1 /\ 1:r1=1 /\ 2:r1=1 /\ 3:r1=1)
22 Observation SRCU-42-A Never 0 15
23 Time SRCU-42-A 0.04
24 Hash=e4a3edb6d5fb0f8ec03bffd98ca3f784
Quick Quiz 2:
Wouldn't different srcu_struct structures normally be used
independently, instead of in the strange in-tandem manner shown in
Litmus Test #3?
Answer
It is tempting to assume that you can mix and match srcu_struct
structures as long as the cycle contains at least as many SRCU grace periods
as SRCU read-side critical sections for each srcu_struct
structure (taking the lesson from
Litmus Test #2),
but this is not the case, as illustrated by this litmus test:
Litmus Test #4
1 C SRCU-42
2
3 {
4 }
5
6 P0(int *x0, int *x1, struct srcu_struct *ssp1)
7 {
8 int r0;
9 int r1;
10
11 r0 = srcu_read_lock(ssp1);
12 WRITE_ONCE(*x0, 1);
13 r1 = READ_ONCE(*x1);
14 srcu_read_unlock(ssp1, r0);
15 }
16
17 P1(int *x1, int *x2, struct srcu_struct *ssp2)
18 {
19 int r0;
20 int r1;
21
22 r0 = srcu_read_lock(ssp2);
23 WRITE_ONCE(*x1, 1);
24 r1 = READ_ONCE(*x2);
25 srcu_read_unlock(ssp2, r0);
26 }
27
28 P2(int *x2, int *x3, struct srcu_struct *ssp1)
29 {
30 int r1;
31
32 WRITE_ONCE(*x2, 1);
33 synchronize_srcu(ssp1);
34 r1 = READ_ONCE(*x3);
35 }
36
37 P3(int *x3, int *x0, struct srcu_struct *ssp2)
38 {
39 int r1;
40
41 WRITE_ONCE(*x3, 1);
42 synchronize_srcu(ssp2);
43 r1 = READ_ONCE(*x0);
44 }
45
46 exists
47 (0:r1=0 /\ 1:r1=0 /\ 2:r1=0 /\ 3:r1=0)
Here the exists clause really can be satisfied:
Outcome for Litmus Test #4 (linux-kernel model)
1 Test SRCU-42 Allowed
2 States 16
3 0:r1=0; 1:r1=0; 2:r1=0; 3:r1=0;
4 0:r1=0; 1:r1=0; 2:r1=0; 3:r1=1;
5 0:r1=0; 1:r1=0; 2:r1=1; 3:r1=0;
6 0:r1=0; 1:r1=0; 2:r1=1; 3:r1=1;
7 0:r1=0; 1:r1=1; 2:r1=0; 3:r1=0;
8 0:r1=0; 1:r1=1; 2:r1=0; 3:r1=1;
9 0:r1=0; 1:r1=1; 2:r1=1; 3:r1=0;
10 0:r1=0; 1:r1=1; 2:r1=1; 3:r1=1;
11 0:r1=1; 1:r1=0; 2:r1=0; 3:r1=0;
12 0:r1=1; 1:r1=0; 2:r1=0; 3:r1=1;
13 0:r1=1; 1:r1=0; 2:r1=1; 3:r1=0;
14 0:r1=1; 1:r1=0; 2:r1=1; 3:r1=1;
15 0:r1=1; 1:r1=1; 2:r1=0; 3:r1=0;
16 0:r1=1; 1:r1=1; 2:r1=0; 3:r1=1;
17 0:r1=1; 1:r1=1; 2:r1=1; 3:r1=0;
18 0:r1=1; 1:r1=1; 2:r1=1; 3:r1=1;
19 Ok
20 Witnesses
21 Positive: 1 Negative: 15
22 Condition exists (0:r1=0 /\ 1:r1=0 /\ 2:r1=0 /\ 3:r1=0)
23 Observation SRCU-42 Sometimes 1 15
24 Time SRCU-42 0.03
25 Hash=b53320b2e58dbd41d4d24f7df3f1689f
Although the counting rule still applies, it applies in a more elaborate way.
It is not enough for the total count of SRCU grace periods to exceed that
of SRCU read-side critical sections for each srcu_struct
structure.
In addition, in the simplest cases it must be possible to divide the
cycle into a series of contiguous segments, where each segment
obeys the counting rule and each process in the segment uses the same
srcu_struct structure.
(There are additional more-complex arrangements that also preserve
order; they are described by the cat code below.)
This per-segment counting rule is obeyed by
Litmus Test #3,
but it is violated by both P0() and
P1() of
Litmus Test #4,
each of which constitutes a single-process segment and each of which
has a SRCU read-side critical section but no SRCU grace periods.
Therefore, neither P0() nor P1() provide any ordering,
and the cycle specified by the exists clause on lines 46-47
is allowed.
Quick Quiz 3:
Given the complexity of the per-segment counting rule, isn't relying
on the interactions of SRCU read-side critical sections and SRCU grace
periods for multiple srcu_struct structures an accident waiting
to happen?
Answer
Quick Quiz 4:
What happens when you add RCU to the mix as well?
Answer
Up to this point, all litmus tests have had at most one SRCU read-side
critical section per process, rendering moot any questions about nesting.
This section takes a look at the interactions of multiple SRCU
read-side critical sections within the confines of a single process.
TL;DR: Don't misnest SRCU read-side critical sections for the same
srcu_struct structure.
Because SRCU requires that the return value from srcu_read_lock()
be passed to srcu_read_unlock(), it is possible to partially
overlap or misnest SRCU read-side critical sections.
An example is shown in the following litmus test:
Litmus Test #5
1 C C-SRCU-misnest
2
3 {
4 }
5
6 P0(int *x0, int *x1, struct srcu_struct *ssp1)
7 {
8 int r0;
9 int r1;
10 int r2;
11
12 r0 = srcu_read_lock(ssp1);
13 r1 = READ_ONCE(*x1);
14 r2 = srcu_read_lock(ssp1);
15 srcu_read_unlock(ssp1, r0);
16 WRITE_ONCE(*x0, 1);
17 srcu_read_unlock(ssp1, r2);
18 }
19
20 P1(int *x0, int *x1, struct srcu_struct *ssp1)
21 {
22 int r0;
23 int r1;
24
25 WRITE_ONCE(*x1, 1);
26 synchronize_srcu(ssp1);
27 r1 = READ_ONCE(*x0);
28 }
29
30 exists
31 (0:r1=0 /\ 1:r1=0)
The return value from the srcu_read_lock() on line 12
is passed to the srcu_read_unlock() on line 15, so that
this SRCU read-side critical section overlaps that of the
srcu_read_lock() on line 14 and the srcu_read_unlock()
on line 17.
In theory, this is quite straightforward: Any SRCU grace period
starting after P0() reaches line 12 cannot complete until
some time after P0() reaches line 15, and any
SRCU grace period starting after P0() reaches line 14
cannot complete until some time after P0() reaches line 17,
because grace periods must wait for pre-existing critical sections.
However, in practice, the only known use cases for overlapping SRCU
read-side critical sections have been to
hide subtle bugs.
LKMM therefore discourages this practice, as shown on line 9
of the outcome:
Outcome for Litmus Test #5 (linux-kernel model)
1 Test C-SRCU-misnest Allowed
2 States 3
3 0:r1=0; 1:r1=1;
4 0:r1=1; 1:r1=0;
5 0:r1=1; 1:r1=1;
6 No
7 Witnesses
8 Positive: 0 Negative: 3
9 Flag srcu-bad-nesting
10 Condition exists (0:r1=0 /\ 1:r1=0)
11 Observation C-SRCU-misnest Never 0 3
12 Time C-SRCU-misnest 0.01
13 Hash=ade0dd8e2f787f74d8a7eecc26a4bbc4
The “
Flag srcu-bad-nesting” is LKMM complaining about
the bug-prone misnesting of SRCU read-side critical sections in this
litmus test.
Quick Quiz 5:
But how do you know that there are no valid use cases for overlapping
SRCU read-side critical sections?
Answer
Because LKMM flags misnested SRCU read-side critical sections, it
can legitimately approximate such critical sections by acting as if
they were properly nested.
This can of course cause misleading outcomes (as is the case for
Litmus Test #5)
but such cases will
be flagged with “Flag srcu-bad-nesting”
(as is also the case for
Litmus Test #5), alerting
the user to the problem.
Quick Quiz 6:
How can
Litmus Test #5
satisfy its exists clause when running in the Linux kernel?
Answer
Quick Quiz 7:
Does LKMM catch all instances of misnested SRCU read-side critical sections?
Answer
In short, LKMM flags any litmus tests with misnested SRCU read-side
critical sections and analyzes them as if those critical sections
were properly nested.
Quick Quiz 8:
Why doesn't LKMM simply accept misnested SRCU read-side critical sections
and handle them the same way as the Linux kernel does?
Answer
These approximations allow LKMM's SRCU model to be quite similar to that
of RCU.
The following lines are added to the
linux-kernel.bell file:
1 (* Compute matching pairs of nested Srcu-lock and Srcu-unlock *)
2 let srcu-rscs = let rec
3 unmatched-locks = Srcu-lock \ domain(matched)
4 and unmatched-unlocks = Srcu-unlock \ range(matched)
5 and unmatched = unmatched-locks | unmatched-unlocks
6 and unmatched-po = ([unmatched] ; po ; [unmatched]) & loc
7 and unmatched-locks-to-unlocks =
8 ([unmatched-locks] ; po ; [unmatched-unlocks]) & loc
9 and matched = matched | (unmatched-locks-to-unlocks \
10 (unmatched-po ; unmatched-po))
11 in matched
12
13 (* Validate nesting *)
14 flag ~empty Srcu-lock \ domain(srcu-rscs) as unbalanced-srcu-locking
15 flag ~empty Srcu-unlock \ range(srcu-rscs) as unbalanced-srcu-locking
16
17 (* Check for use of synchronize_srcu() inside an RCU critical section *)
18 flag ~empty rcu-rscs & (po ; [Sync-srcu] ; po) as invalid-sleep
19
20 (* Validate SRCU dynamic match *)
21 flag ~empty different-values(srcu-rscs) as srcu-bad-nesting
These lines are quite similar to their RCU counterparts.
Differences include:
- Identifiers containing “rcu” substitute
“srcu” and, similarly,
identifiers containing “Rcu” substitute
“Srcu”.
- Lines 6 and 8 add “& loc”
to ensure that srcu_read_lock() and
srcu_read_unlock() invocations are properly segregated
by srcu_struct structure.
- Line 18 complains if synchronize_srcu() appears
within an RCU read-side critical section, on the grounds that RCU
read-side critical sections are not allowed to block.
- Line 21 complains if SRCU read-side critical sections
for a given srcu_struct are misnested.
In the
linux-kernel.cat
file, the SRCU support is interleaved into that of RCU:
1 let rcu-gp = [Sync-rcu]
2 let srcu-gp = [Sync-srcu]
3 let rcu-rscsi = rcu-rscs^-1
4 let srcu-rscsi = srcu-rscs^-1
5
6 let rcu-link = po? ; hb* ; pb* ; prop ; po
7
8 let rec rcu-fence = rcu-gp | srcu-gp |
9 (rcu-gp ; rcu-link ; rcu-rscsi) |
10 ((srcu-gp ; rcu-link ; srcu-rscsi) & loc) |
11 (rcu-rscsi ; rcu-link ; rcu-gp) |
12 ((srcu-rscsi ; rcu-link ; srcu-gp) & loc) |
13 (rcu-gp ; rcu-link ; rcu-fence ; rcu-link ; rcu-rscsi) |
14 ((srcu-gp ; rcu-link ; rcu-fence ; rcu-link ; srcu-rscsi) & loc) |
15 (rcu-rscsi ; rcu-link ; rcu-fence ; rcu-link ; rcu-gp) |
16 ((srcu-rscsi ; rcu-link ; rcu-fence ; rcu-link ; srcu-gp) & loc) |
17 (rcu-fence ; rcu-link ; rcu-fence)
18
19 let rb = prop ; po ; rcu-fence ; po? ; hb* ; pb*
20
21 irreflexive rb as rcu
Quick Quiz 9:
That is some serious mutually assured recursion there!
So did Alan write this?
Answer
Lines 2, 4, 8, 10, 12, 14, and 16 add the SRCU support.
As with the .bell file, any expressions involving multiple SRCU
primitives use “& loc” to segregate these
primitives by srcu_struct instance.
And we are now in a position to state SRCU's more-elaborate
forbidden-cycle counting rule in its full glory.
As you might expect, the definition is recursive:
- A linked sequence of threads is ordered if each entry in the
sequence contains a call to smp_mb() or
synchronize_rcu() or synchronize_srcu().
- Matching read-side critical sections and grace periods:
- A linked sequence of two threads is ordered if one thread
contains an RCU read-side critical section and the other calls
synchronize_rcu().
- A linked sequence of two threads is ordered if one thread
contains an SRCU read-side critical section and the
other calls synchronize_srcu() using the same
srcu_struct structure.
- Extending with read-side critical sections and grace periods:
- An ordered sequence can be extended by linking a thread
to its start and a thread to its end, where one of the
new threads contains an RCU read-side critical section
and the other calls synchronize_rcu().
- An ordered sequence can be extended by linking a thread
to its start and a thread to its end, where one of the
new threads contains an SRCU read-side critical section
and the other calls synchronize_srcu() using
the same srcu_struct structure.
- Lastly, the linked concatenation of two ordered sequences is
also ordered.
LKMM says that a linked sequence of threads ordered in this way must not form
a cycle.
Since the definition always requires a synchronize_rcu() call
to be added along with each RCU read-side critical section, and a
synchronize_srcu() call to be added along with each SRCU read-side
critical section, you can see how the counting rule falls out.
We have demonstrated LKMM's versatility by adding SRCU support.
As before, we hope will be useful
for education, concurrent design, and for inclusion in other tooling.
As far as we know, this is the first realistic formal memory model that
includes SRCU ordering properties.
As
before,
this model is not set in stone, but subject to change with the evolution
of hardware and of Linux-kernel use cases.
And in fact, in not quite one year about 40 patches have been applied to
the memory model. However, many of these patches provided tests and
documentation.
Only 18, less than half, have modified the memory model itself, and most
of these 18 have been non-substantive changes for code style and
commenting.
The limitations called out in the
2017 article
still apply and bear repeating:
- These memory models do not constitute official statements by the
various CPU vendors on their respective architectures.
For example, any of these vendors might report bugs at any time
against any version of this memory model.
This memory model is therefore not a substitute for a carefully
designed and vigorously executed validation regime.
In addition, this memory model is under active development and
might change at any time.
- It is quite possible that this memory model will disagree with
CPU architectures or with real hardware.
For example, the model might well choose to allow behavior that
all CPUs forbid if forbidding that behavior would render the
model excessively complex.
On the other hand, any situation where the model forbids behavior
that some CPU allows constitutes a bug, either in the model or
in the CPU.
- This tool is exponential in nature.
Litmus tests that seem quite small compared to the entire Linux kernel
might well take geologic time for the herd tool to
analyze.
That said, this tool can be extremely effective in exhaustively
analyzing the code at the core of a synchronization primitive.
- The herd tool can only detect problems for which you
have coded an assertion. This weakness is common to all formal
methods, and is one reason that we expect testing to continue
to be important.
In the immortal words of Donald Knuth: "Beware of bugs in the
above code; I have only proved it correct, not tried it."
On the other hand, one advantage of formal memory models is that tools based
on them can detect any problem that might occur, even if the probability
of occurrence is vanishingly small, in fact, even if current hardware
is incapable of making that problem happen.
Use of tools based on this memory model is therefore an excellent way to
future-proof your code.
Acknowledgments
We owe thanks to the LWN editors for their review of this document.
We are also grateful to Jessica Murillo, Mark Figley, and Kara Todd
for their support of this effort.
This work represents the views of the authors and does not necessarily
represent the views of University College London, ARM Ltd., INRIA Paris,
Amarula Solutions, Harvard University, Intel, Red Hat, Nvidia,
or IBM Corporation.
Linux is a registered trademark of Linus Torvalds.
Other company, product, and service names may be trademarks or
service marks of others.
Quick Quiz 1:
What if a pair of SRCU read-side critical sections using the same
srcu_struct structure partially overlap instead of being
properly nested?
Answer:
That story is told
later.
Back to Quick Quiz 1.
Quick Quiz 2:
Wouldn't different srcu_struct structures normally be used
independently, instead of in the strange in-tandem manner shown in
Litmus Test #3?
Answer:
You are quite correct, different srcu_struct structures
would in fact normally be used independently.
We therefore cordially invite you to construct a litmus test demonstrating
this use case.
Back to Quick Quiz 2.
Quick Quiz 3:
Given the complexity of the per-segment counting rule, isn't relying
on the interactions of SRCU read-side critical sections and SRCU grace
periods for multiple srcu_struct structures an accident waiting
to happen?
Answer:
Quite possibly.
So if you find yourself relying on such interactions, you should step
back and take a harder look at the problem you are trying to solve.
But if you do find a need for this sort of thing, please let us know!
This of course raises the question “Why bother modeling such
interactions?”
The answer is that we do need a mathematically complete model.
Back to Quick Quiz 3.
Quick Quiz 4:
What happens when you add RCU to the mix as well?
Answer:
This is covered
later in this article.
Back to Quick Quiz 4.
Quick Quiz 5:
But how do you know that there are no valid use cases for overlapping
SRCU read-side critical sections?
Answer:
We don't.
But the better question is “Can you come up with a real
use case for overlapping SRCU read-side critical sections?”
If good use cases are found, LKMM will be appropriately adjusted.
Back to Quick Quiz 5.
Quick Quiz 6:
How can
Litmus Test #5
satisfy its exists clause when running in the Linux kernel?
Answer:
The entirety of P1() might execute between lines 13
and 16 of P0(), which would result in both
P0()'s and P1()'s instances of r1
having final values of zero, as specified in the exists
clause.
Such an outcome is admittedly unlikely, given the relatively high
overhead of synchronize_srcu(), but it could happen
given interrupts or preemption at just the wrong place during
P0()'s execution.
Back to Quick Quiz 6.
Quick Quiz 7:
Does LKMM catch all instances of misnested SRCU read-side critical sections?
Answer:
No, only those that are actually reached by an execution.
For example, consider this litmus test:
Litmus Test #6
1 C C-SRCU-misnest-not
2
3 {
4 }
5
6 P0(int *x0, int *x1, int *x2, struct srcu_struct *ssp1)
7 {
8 int r0;
9 int r1;
10 int r2;
11 int r3;
12
13 r0 = READ_ONCE(*x2);
14 r1 = srcu_read_lock(ssp1);
15 r2 = READ_ONCE(*x1);
16 if (r0)
17 r3 = srcu_read_lock(ssp1);
18 srcu_read_unlock(ssp1, r1);
19 WRITE_ONCE(*x0, 1);
20 if (r0)
21 srcu_read_unlock(ssp1, r3);
22 }
23
24 P1(int *x0, int *x1, struct srcu_struct *ssp1)
25 {
26 int r0;
27 int r1;
28
29 WRITE_ONCE(*x1, 1);
30 synchronize_srcu(ssp1);
31 r1 = READ_ONCE(*x0);
32 }
33
34 exists
35 (0:r2=0 /\ 1:r1=0)
Here, the srcu_read_lock() on line 17 and the
srcu_read_unlock() on line 21 are never executed, so LKMM
doesn't flag the misnesting:
Outcome for Litmus Test #6 (linux-kernel model)
1 Test C-SRCU-misnest-not Allowed
2 States 4
3 0:r2=0; 1:r1=0;
4 0:r2=0; 1:r1=1;
5 0:r2=1; 1:r1=0;
6 0:r2=1; 1:r1=1;
7 Ok
8 Witnesses
9 Positive: 1 Negative: 3
10 Condition exists (0:r2=0 /\ 1:r1=0)
11 Observation C-SRCU-misnest-not Sometimes 1 3
12 Time C-SRCU-misnest-not 0.01
13 Hash=3152306568bd9d54fc1f62e0604124c8
On the other hand, one can reasonably ask whether something that is
never executed is really a problem.
In addition, it is usually best to avoid adding never-executed code
to the Linux kernel, with carefully conceived debugging code being the
exception that proves this rule.
Besides, in this case LKMM does correctly analyze this litmus test,
correctly noting that the exists clause can in fact be
satisfied.
Back to Quick Quiz 7.
Quick Quiz 8:
Why doesn't LKMM simply accept misnested SRCU read-side critical sections
and handle them the same way as the Linux kernel does?
Answer:
At the time SRCU support was first added to LKML, herd did not possess
any capability of matching the return value from one function call with
the argument to another, so there was no choice but to assume that the
nesting was correct.
Later on, when this capability was added along with the
srcu-bad-nesting flag, nobody modified the cat code
to interpret misnested calls correctly.
Back to Quick Quiz 8.
Quick Quiz 9:
That is some serious mutually assured recursion there!
So did Alan write this?
Answer:
Indeed he did!
Luc wrote an earlier version and also provided some herd features needed
to support SRCU.
The rest of us provided some interesting litmus tests, some documentation,
but were otherwise smart enough to stay out of the way.
Back to Quick Quiz 9.