1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108 |
- Linux-Kernel Memory Model Litmus Tests
- ======================================
- This file describes the LKMM litmus-test format by example, describes
- some tricks and traps, and finally outlines LKMM's limitations. Earlier
- versions of this material appeared in a number of LWN articles, including:
- https://lwn.net/Articles/720550/
- A formal kernel memory-ordering model (part 2)
- https://lwn.net/Articles/608550/
- Axiomatic validation of memory barriers and atomic instructions
- https://lwn.net/Articles/470681/
- Validating Memory Barriers and Atomic Instructions
- This document presents information in decreasing order of applicability,
- so that, where possible, the information that has proven more commonly
- useful is shown near the beginning.
- For information on installing LKMM, including the underlying "herd7"
- tool, please see tools/memory-model/README.
- Copy-Pasta
- ==========
- As with other software, it is often better (if less macho) to adapt an
- existing litmus test than it is to create one from scratch. A number
- of litmus tests may be found in the kernel source tree:
- tools/memory-model/litmus-tests/
- Documentation/litmus-tests/
- Several thousand more example litmus tests are available on github
- and kernel.org:
- https://github.com/paulmckrcu/litmus
- https://git.kernel.org/pub/scm/linux/kernel/git/paulmck/perfbook.git/tree/CodeSamples/formal/herd
- https://git.kernel.org/pub/scm/linux/kernel/git/paulmck/perfbook.git/tree/CodeSamples/formal/litmus
- The -l and -L arguments to "git grep" can be quite helpful in identifying
- existing litmus tests that are similar to the one you need. But even if
- you start with an existing litmus test, it is still helpful to have a
- good understanding of the litmus-test format.
- Examples and Format
- ===================
- This section describes the overall format of litmus tests, starting
- with a small example of the message-passing pattern and moving on to
- more complex examples that illustrate explicit initialization and LKMM's
- minimalistic set of flow-control statements.
- Message-Passing Example
- -----------------------
- This section gives an overview of the format of a litmus test using an
- example based on the common message-passing use case. This use case
- appears often in the Linux kernel. For example, a flag (modeled by "y"
- below) indicates that a buffer (modeled by "x" below) is now completely
- filled in and ready for use. It would be very bad if the consumer saw the
- flag set, but, due to memory misordering, saw old values in the buffer.
- This example asks whether smp_store_release() and smp_load_acquire()
- suffices to avoid this bad outcome:
- 1 C MP+pooncerelease+poacquireonce
- 2
- 3 {}
- 4
- 5 P0(int *x, int *y)
- 6 {
- 7 WRITE_ONCE(*x, 1);
- 8 smp_store_release(y, 1);
- 9 }
- 10
- 11 P1(int *x, int *y)
- 12 {
- 13 int r0;
- 14 int r1;
- 15
- 16 r0 = smp_load_acquire(y);
- 17 r1 = READ_ONCE(*x);
- 18 }
- 19
- 20 exists (1:r0=1 /\ 1:r1=0)
- Line 1 starts with "C", which identifies this file as being in the
- LKMM C-language format (which, as we will see, is a small fragment
- of the full C language). The remainder of line 1 is the name of
- the test, which by convention is the filename with the ".litmus"
- suffix stripped. In this case, the actual test may be found in
- tools/memory-model/litmus-tests/MP+pooncerelease+poacquireonce.litmus
- in the Linux-kernel source tree.
- Mechanically generated litmus tests will often have an optional
- double-quoted comment string on the second line. Such strings are ignored
- when running the test. Yes, you can add your own comments to litmus
- tests, but this is a bit involved due to the use of multiple parsers.
- For now, you can use C-language comments in the C code, and these comments
- may be in either the "/* */" or the "//" style. A later section will
- cover the full litmus-test commenting story.
- Line 3 is the initialization section. Because the default initialization
- to zero suffices for this test, the "{}" syntax is used, which mean the
- initialization section is empty. Litmus tests requiring non-default
- initialization must have non-empty initialization sections, as in the
- example that will be presented later in this document.
- Lines 5-9 show the first process and lines 11-18 the second process. Each
- process corresponds to a Linux-kernel task (or kthread, workqueue, thread,
- and so on; LKMM discussions often use these terms interchangeably).
- The name of the first process is "P0" and that of the second "P1".
- You can name your processes anything you like as long as the names consist
- of a single "P" followed by a number, and as long as the numbers are
- consecutive starting with zero. This can actually be quite helpful,
- for example, a .litmus file matching "^P1(" but not matching "^P2("
- must contain a two-process litmus test.
- The argument list for each function are pointers to the global variables
- used by that function. Unlike normal C-language function parameters, the
- names are significant. The fact that both P0() and P1() have a formal
- parameter named "x" means that these two processes are working with the
- same global variable, also named "x". So the "int *x, int *y" on P0()
- and P1() mean that both processes are working with two shared global
- variables, "x" and "y". Global variables are always passed to processes
- by reference, hence "P0(int *x, int *y)", but *never* "P0(int x, int y)".
- P0() has no local variables, but P1() has two of them named "r0" and "r1".
- These names may be freely chosen, but for historical reasons stemming from
- other litmus-test formats, it is conventional to use names consisting of
- "r" followed by a number as shown here. A common bug in litmus tests
- is forgetting to add a global variable to a process's parameter list.
- This will sometimes result in an error message, but can also cause the
- intended global to instead be silently treated as an undeclared local
- variable.
- Each process's code is similar to Linux-kernel C, as can be seen on lines
- 7-8 and 13-17. This code may use many of the Linux kernel's atomic
- operations, some of its exclusive-lock functions, and some of its RCU
- and SRCU functions. An approximate list of the currently supported
- functions may be found in the linux-kernel.def file.
- The P0() process does "WRITE_ONCE(*x, 1)" on line 7. Because "x" is a
- pointer in P0()'s parameter list, this does an unordered store to global
- variable "x". Line 8 does "smp_store_release(y, 1)", and because "y"
- is also in P0()'s parameter list, this does a release store to global
- variable "y".
- The P1() process declares two local variables on lines 13 and 14.
- Line 16 does "r0 = smp_load_acquire(y)" which does an acquire load
- from global variable "y" into local variable "r0". Line 17 does a
- "r1 = READ_ONCE(*x)", which does an unordered load from "*x" into local
- variable "r1". Both "x" and "y" are in P1()'s parameter list, so both
- reference the same global variables that are used by P0().
- Line 20 is the "exists" assertion expression to evaluate the final state.
- This final state is evaluated after the dust has settled: both processes
- have completed and all of their memory references and memory barriers
- have propagated to all parts of the system. The references to the local
- variables "r0" and "r1" in line 24 must be prefixed with "1:" to specify
- which process they are local to.
- Note that the assertion expression is written in the litmus-test
- language rather than in C. For example, single "=" is an equality
- operator rather than an assignment. The "/\" character combination means
- "and". Similarly, "\/" stands for "or". Both of these are ASCII-art
- representations of the corresponding mathematical symbols. Finally,
- "~" stands for "logical not", which is "!" in C, and not to be confused
- with the C-language "~" operator which instead stands for "bitwise not".
- Parentheses may be used to override precedence.
- The "exists" assertion on line 20 is satisfied if the consumer sees the
- flag ("y") set but the buffer ("x") as not yet filled in, that is, if P1()
- loaded a value from "x" that was equal to 1 but loaded a value from "y"
- that was still equal to zero.
- This example can be checked by running the following command, which
- absolutely must be run from the tools/memory-model directory and from
- this directory only:
- herd7 -conf linux-kernel.cfg litmus-tests/MP+pooncerelease+poacquireonce.litmus
- The output is the result of something similar to a full state-space
- search, and is as follows:
- 1 Test MP+pooncerelease+poacquireonce Allowed
- 2 States 3
- 3 1:r0=0; 1:r1=0;
- 4 1:r0=0; 1:r1=1;
- 5 1:r0=1; 1:r1=1;
- 6 No
- 7 Witnesses
- 8 Positive: 0 Negative: 3
- 9 Condition exists (1:r0=1 /\ 1:r1=0)
- 10 Observation MP+pooncerelease+poacquireonce Never 0 3
- 11 Time MP+pooncerelease+poacquireonce 0.00
- 12 Hash=579aaa14d8c35a39429b02e698241d09
- The most pertinent line is line 10, which contains "Never 0 3", which
- indicates that the bad result flagged by the "exists" clause never
- happens. This line might instead say "Sometimes" to indicate that the
- bad result happened in some but not all executions, or it might say
- "Always" to indicate that the bad result happened in all executions.
- (The herd7 tool doesn't judge, so it is only an LKMM convention that the
- "exists" clause indicates a bad result. To see this, invert the "exists"
- clause's condition and run the test.) The numbers ("0 3") at the end
- of this line indicate the number of end states satisfying the "exists"
- clause (0) and the number not not satisfying that clause (3).
- Another important part of this output is shown in lines 2-5, repeated here:
- 2 States 3
- 3 1:r0=0; 1:r1=0;
- 4 1:r0=0; 1:r1=1;
- 5 1:r0=1; 1:r1=1;
- Line 2 gives the total number of end states, and each of lines 3-5 list
- one of these states, with the first ("1:r0=0; 1:r1=0;") indicating that
- both of P1()'s loads returned the value "0". As expected, given the
- "Never" on line 10, the state flagged by the "exists" clause is not
- listed. This full list of states can be helpful when debugging a new
- litmus test.
- The rest of the output is not normally needed, either due to irrelevance
- or due to being redundant with the lines discussed above. However, the
- following paragraph lists them for the benefit of readers possessed of
- an insatiable curiosity. Other readers should feel free to skip ahead.
- Line 1 echos the test name, along with the "Test" and "Allowed". Line 6's
- "No" says that the "exists" clause was not satisfied by any execution,
- and as such it has the same meaning as line 10's "Never". Line 7 is a
- lead-in to line 8's "Positive: 0 Negative: 3", which lists the number
- of end states satisfying and not satisfying the "exists" clause, just
- like the two numbers at the end of line 10. Line 9 repeats the "exists"
- clause so that you don't have to look it up in the litmus-test file.
- The number at the end of line 11 (which begins with "Time") gives the
- time in seconds required to analyze the litmus test. Small tests such
- as this one complete in a few milliseconds, so "0.00" is quite common.
- Line 12 gives a hash of the contents for the litmus-test file, and is used
- by tooling that manages litmus tests and their output. This tooling is
- used by people modifying LKMM itself, and among other things lets such
- people know which of the several thousand relevant litmus tests were
- affected by a given change to LKMM.
- Initialization
- --------------
- The previous example relied on the default zero initialization for
- "x" and "y", but a similar litmus test could instead initialize them
- to some other value:
- 1 C MP+pooncerelease+poacquireonce
- 2
- 3 {
- 4 x=42;
- 5 y=42;
- 6 }
- 7
- 8 P0(int *x, int *y)
- 9 {
- 10 WRITE_ONCE(*x, 1);
- 11 smp_store_release(y, 1);
- 12 }
- 13
- 14 P1(int *x, int *y)
- 15 {
- 16 int r0;
- 17 int r1;
- 18
- 19 r0 = smp_load_acquire(y);
- 20 r1 = READ_ONCE(*x);
- 21 }
- 22
- 23 exists (1:r0=1 /\ 1:r1=42)
- Lines 3-6 now initialize both "x" and "y" to the value 42. This also
- means that the "exists" clause on line 23 must change "1:r1=0" to
- "1:r1=42".
- Running the test gives the same overall result as before, but with the
- value 42 appearing in place of the value zero:
- 1 Test MP+pooncerelease+poacquireonce Allowed
- 2 States 3
- 3 1:r0=1; 1:r1=1;
- 4 1:r0=42; 1:r1=1;
- 5 1:r0=42; 1:r1=42;
- 6 No
- 7 Witnesses
- 8 Positive: 0 Negative: 3
- 9 Condition exists (1:r0=1 /\ 1:r1=42)
- 10 Observation MP+pooncerelease+poacquireonce Never 0 3
- 11 Time MP+pooncerelease+poacquireonce 0.02
- 12 Hash=ab9a9b7940a75a792266be279a980156
- It is tempting to avoid the open-coded repetitions of the value "42"
- by defining another global variable "initval=42" and replacing all
- occurrences of "42" with "initval". This will not, repeat *not*,
- initialize "x" and "y" to 42, but instead to the address of "initval"
- (try it!). See the section below on linked lists to learn more about
- why this approach to initialization can be useful.
- Control Structures
- ------------------
- LKMM supports the C-language "if" statement, which allows modeling of
- conditional branches. In LKMM, conditional branches can affect ordering,
- but only if you are *very* careful (compilers are surprisingly able
- to optimize away conditional branches). The following example shows
- the "load buffering" (LB) use case that is used in the Linux kernel to
- synchronize between ring-buffer producers and consumers. In the example
- below, P0() is one side checking to see if an operation may proceed and
- P1() is the other side completing its update.
- 1 C LB+fencembonceonce+ctrlonceonce
- 2
- 3 {}
- 4
- 5 P0(int *x, int *y)
- 6 {
- 7 int r0;
- 8
- 9 r0 = READ_ONCE(*x);
- 10 if (r0)
- 11 WRITE_ONCE(*y, 1);
- 12 }
- 13
- 14 P1(int *x, int *y)
- 15 {
- 16 int r0;
- 17
- 18 r0 = READ_ONCE(*y);
- 19 smp_mb();
- 20 WRITE_ONCE(*x, 1);
- 21 }
- 22
- 23 exists (0:r0=1 /\ 1:r0=1)
- P1()'s "if" statement on line 10 works as expected, so that line 11 is
- executed only if line 9 loads a non-zero value from "x". Because P1()'s
- write of "1" to "x" happens only after P1()'s read from "y", one would
- hope that the "exists" clause cannot be satisfied. LKMM agrees:
- 1 Test LB+fencembonceonce+ctrlonceonce Allowed
- 2 States 2
- 3 0:r0=0; 1:r0=0;
- 4 0:r0=1; 1:r0=0;
- 5 No
- 6 Witnesses
- 7 Positive: 0 Negative: 2
- 8 Condition exists (0:r0=1 /\ 1:r0=1)
- 9 Observation LB+fencembonceonce+ctrlonceonce Never 0 2
- 10 Time LB+fencembonceonce+ctrlonceonce 0.00
- 11 Hash=e5260556f6de495fd39b556d1b831c3b
- However, there is no "while" statement due to the fact that full
- state-space search has some difficulty with iteration. However, there
- are tricks that may be used to handle some special cases, which are
- discussed below. In addition, loop-unrolling tricks may be applied,
- albeit sparingly.
- Tricks and Traps
- ================
- This section covers extracting debug output from herd7, emulating
- spin loops, handling trivial linked lists, adding comments to litmus tests,
- emulating call_rcu(), and finally tricks to improve herd7 performance
- in order to better handle large litmus tests.
- Debug Output
- ------------
- By default, the herd7 state output includes all variables mentioned
- in the "exists" clause. But sometimes debugging efforts are greatly
- aided by the values of other variables. Consider this litmus test
- (tools/memory-order/litmus-tests/SB+rfionceonce-poonceonces.litmus but
- slightly modified), which probes an obscure corner of hardware memory
- ordering:
- 1 C SB+rfionceonce-poonceonces
- 2
- 3 {}
- 4
- 5 P0(int *x, int *y)
- 6 {
- 7 int r1;
- 8 int r2;
- 9
- 10 WRITE_ONCE(*x, 1);
- 11 r1 = READ_ONCE(*x);
- 12 r2 = READ_ONCE(*y);
- 13 }
- 14
- 15 P1(int *x, int *y)
- 16 {
- 17 int r3;
- 18 int r4;
- 19
- 20 WRITE_ONCE(*y, 1);
- 21 r3 = READ_ONCE(*y);
- 22 r4 = READ_ONCE(*x);
- 23 }
- 24
- 25 exists (0:r2=0 /\ 1:r4=0)
- The herd7 output is as follows:
- 1 Test SB+rfionceonce-poonceonces Allowed
- 2 States 4
- 3 0:r2=0; 1:r4=0;
- 4 0:r2=0; 1:r4=1;
- 5 0:r2=1; 1:r4=0;
- 6 0:r2=1; 1:r4=1;
- 7 Ok
- 8 Witnesses
- 9 Positive: 1 Negative: 3
- 10 Condition exists (0:r2=0 /\ 1:r4=0)
- 11 Observation SB+rfionceonce-poonceonces Sometimes 1 3
- 12 Time SB+rfionceonce-poonceonces 0.01
- 13 Hash=c7f30fe0faebb7d565405d55b7318ada
- (This output indicates that CPUs are permitted to "snoop their own
- store buffers", which all of Linux's CPU families other than s390 will
- happily do. Such snooping results in disagreement among CPUs on the
- order of stores from different CPUs, which is rarely an issue.)
- But the herd7 output shows only the two variables mentioned in the
- "exists" clause. Someone modifying this test might wish to know the
- values of "x", "y", "0:r1", and "0:r3" as well. The "locations"
- statement on line 25 shows how to cause herd7 to display additional
- variables:
- 1 C SB+rfionceonce-poonceonces
- 2
- 3 {}
- 4
- 5 P0(int *x, int *y)
- 6 {
- 7 int r1;
- 8 int r2;
- 9
- 10 WRITE_ONCE(*x, 1);
- 11 r1 = READ_ONCE(*x);
- 12 r2 = READ_ONCE(*y);
- 13 }
- 14
- 15 P1(int *x, int *y)
- 16 {
- 17 int r3;
- 18 int r4;
- 19
- 20 WRITE_ONCE(*y, 1);
- 21 r3 = READ_ONCE(*y);
- 22 r4 = READ_ONCE(*x);
- 23 }
- 24
- 25 locations [0:r1; 1:r3; x; y]
- 26 exists (0:r2=0 /\ 1:r4=0)
- The herd7 output then displays the values of all the variables:
- 1 Test SB+rfionceonce-poonceonces Allowed
- 2 States 4
- 3 0:r1=1; 0:r2=0; 1:r3=1; 1:r4=0; x=1; y=1;
- 4 0:r1=1; 0:r2=0; 1:r3=1; 1:r4=1; x=1; y=1;
- 5 0:r1=1; 0:r2=1; 1:r3=1; 1:r4=0; x=1; y=1;
- 6 0:r1=1; 0:r2=1; 1:r3=1; 1:r4=1; x=1; y=1;
- 7 Ok
- 8 Witnesses
- 9 Positive: 1 Negative: 3
- 10 Condition exists (0:r2=0 /\ 1:r4=0)
- 11 Observation SB+rfionceonce-poonceonces Sometimes 1 3
- 12 Time SB+rfionceonce-poonceonces 0.01
- 13 Hash=40de8418c4b395388f6501cafd1ed38d
- What if you would like to know the value of a particular global variable
- at some particular point in a given process's execution? One approach
- is to use a READ_ONCE() to load that global variable into a new local
- variable, then add that local variable to the "locations" clause.
- But be careful: In some litmus tests, adding a READ_ONCE() will change
- the outcome! For one example, please see the C-READ_ONCE.litmus and
- C-READ_ONCE-omitted.litmus tests located here:
- https://github.com/paulmckrcu/litmus/blob/master/manual/kernel/
- Spin Loops
- ----------
- The analysis carried out by herd7 explores full state space, which is
- at best of exponential time complexity. Adding processes and increasing
- the amount of code in a give process can greatly increase execution time.
- Potentially infinite loops, such as those used to wait for locks to
- become available, are clearly problematic.
- Fortunately, it is possible to avoid state-space explosion by specially
- modeling such loops. For example, the following litmus tests emulates
- locking using xchg_acquire(), but instead of enclosing xchg_acquire()
- in a spin loop, it instead excludes executions that fail to acquire the
- lock using a herd7 "filter" clause. Note that for exclusive locking, you
- are better off using the spin_lock() and spin_unlock() that LKMM directly
- models, if for no other reason that these are much faster. However, the
- techniques illustrated in this section can be used for other purposes,
- such as emulating reader-writer locking, which LKMM does not yet model.
- 1 C C-SB+l-o-o-u+l-o-o-u-X
- 2
- 3 {
- 4 }
- 5
- 6 P0(int *sl, int *x0, int *x1)
- 7 {
- 8 int r2;
- 9 int r1;
- 10
- 11 r2 = xchg_acquire(sl, 1);
- 12 WRITE_ONCE(*x0, 1);
- 13 r1 = READ_ONCE(*x1);
- 14 smp_store_release(sl, 0);
- 15 }
- 16
- 17 P1(int *sl, int *x0, int *x1)
- 18 {
- 19 int r2;
- 20 int r1;
- 21
- 22 r2 = xchg_acquire(sl, 1);
- 23 WRITE_ONCE(*x1, 1);
- 24 r1 = READ_ONCE(*x0);
- 25 smp_store_release(sl, 0);
- 26 }
- 27
- 28 filter (0:r2=0 /\ 1:r2=0)
- 29 exists (0:r1=0 /\ 1:r1=0)
- This litmus test may be found here:
- https://git.kernel.org/pub/scm/linux/kernel/git/paulmck/perfbook.git/tree/CodeSamples/formal/herd/C-SB+l-o-o-u+l-o-o-u-X.litmus
- This test uses two global variables, "x1" and "x2", and also emulates a
- single global spinlock named "sl". This spinlock is held by whichever
- process changes the value of "sl" from "0" to "1", and is released when
- that process sets "sl" back to "0". P0()'s lock acquisition is emulated
- on line 11 using xchg_acquire(), which unconditionally stores the value
- "1" to "sl" and stores either "0" or "1" to "r2", depending on whether
- the lock acquisition was successful or unsuccessful (due to "sl" already
- having the value "1"), respectively. P1() operates in a similar manner.
- Rather unconventionally, execution appears to proceed to the critical
- section on lines 12 and 13 in either case. Line 14 then uses an
- smp_store_release() to store zero to "sl", thus emulating lock release.
- The case where xchg_acquire() fails to acquire the lock is handled by
- the "filter" clause on line 28, which tells herd7 to keep only those
- executions in which both "0:r2" and "1:r2" are zero, that is to pay
- attention only to those executions in which both locks are actually
- acquired. Thus, the bogus executions that would execute the critical
- sections are discarded and any effects that they might have had are
- ignored. Note well that the "filter" clause keeps those executions
- for which its expression is satisfied, that is, for which the expression
- evaluates to true. In other words, the "filter" clause says what to
- keep, not what to discard.
- The result of running this test is as follows:
- 1 Test C-SB+l-o-o-u+l-o-o-u-X Allowed
- 2 States 2
- 3 0:r1=0; 1:r1=1;
- 4 0:r1=1; 1:r1=0;
- 5 No
- 6 Witnesses
- 7 Positive: 0 Negative: 2
- 8 Condition exists (0:r1=0 /\ 1:r1=0)
- 9 Observation C-SB+l-o-o-u+l-o-o-u-X Never 0 2
- 10 Time C-SB+l-o-o-u+l-o-o-u-X 0.03
- The "Never" on line 9 indicates that this use of xchg_acquire() and
- smp_store_release() really does correctly emulate locking.
- Why doesn't the litmus test take the simpler approach of using a spin loop
- to handle failed spinlock acquisitions, like the kernel does? The key
- insight behind this litmus test is that spin loops have no effect on the
- possible "exists"-clause outcomes of program execution in the absence
- of deadlock. In other words, given a high-quality lock-acquisition
- primitive in a deadlock-free program running on high-quality hardware,
- each lock acquisition will eventually succeed. Because herd7 already
- explores the full state space, the length of time required to actually
- acquire the lock does not matter. After all, herd7 already models all
- possible durations of the xchg_acquire() statements.
- Why not just add the "filter" clause to the "exists" clause, thus
- avoiding the "filter" clause entirely? This does work, but is slower.
- The reason that the "filter" clause is faster is that (in the common case)
- herd7 knows to abandon an execution as soon as the "filter" expression
- fails to be satisfied. In contrast, the "exists" clause is evaluated
- only at the end of time, thus requiring herd7 to waste time on bogus
- executions in which both critical sections proceed concurrently. In
- addition, some LKMM users like the separation of concerns provided by
- using the both the "filter" and "exists" clauses.
- Readers lacking a pathological interest in odd corner cases should feel
- free to skip the remainder of this section.
- But what if the litmus test were to temporarily set "0:r2" to a non-zero
- value? Wouldn't that cause herd7 to abandon the execution prematurely
- due to an early mismatch of the "filter" clause?
- Why not just try it? Line 4 of the following modified litmus test
- introduces a new global variable "x2" that is initialized to "1". Line 23
- of P1() reads that variable into "1:r2" to force an early mismatch with
- the "filter" clause. Line 24 does a known-true "if" condition to avoid
- and static analysis that herd7 might do. Finally the "exists" clause
- on line 32 is updated to a condition that is alway satisfied at the end
- of the test.
- 1 C C-SB+l-o-o-u+l-o-o-u-X
- 2
- 3 {
- 4 x2=1;
- 5 }
- 6
- 7 P0(int *sl, int *x0, int *x1)
- 8 {
- 9 int r2;
- 10 int r1;
- 11
- 12 r2 = xchg_acquire(sl, 1);
- 13 WRITE_ONCE(*x0, 1);
- 14 r1 = READ_ONCE(*x1);
- 15 smp_store_release(sl, 0);
- 16 }
- 17
- 18 P1(int *sl, int *x0, int *x1, int *x2)
- 19 {
- 20 int r2;
- 21 int r1;
- 22
- 23 r2 = READ_ONCE(*x2);
- 24 if (r2)
- 25 r2 = xchg_acquire(sl, 1);
- 26 WRITE_ONCE(*x1, 1);
- 27 r1 = READ_ONCE(*x0);
- 28 smp_store_release(sl, 0);
- 29 }
- 30
- 31 filter (0:r2=0 /\ 1:r2=0)
- 32 exists (x1=1)
- If the "filter" clause were to check each variable at each point in the
- execution, running this litmus test would display no executions because
- all executions would be filtered out at line 23. However, the output
- is instead as follows:
- 1 Test C-SB+l-o-o-u+l-o-o-u-X Allowed
- 2 States 1
- 3 x1=1;
- 4 Ok
- 5 Witnesses
- 6 Positive: 2 Negative: 0
- 7 Condition exists (x1=1)
- 8 Observation C-SB+l-o-o-u+l-o-o-u-X Always 2 0
- 9 Time C-SB+l-o-o-u+l-o-o-u-X 0.04
- 10 Hash=080bc508da7f291e122c6de76c0088e3
- Line 3 shows that there is one execution that did not get filtered out,
- so the "filter" clause is evaluated only on the last assignment to
- the variables that it checks. In this case, the "filter" clause is a
- disjunction, so it might be evaluated twice, once at the final (and only)
- assignment to "0:r2" and once at the final assignment to "1:r2".
- Linked Lists
- ------------
- LKMM can handle linked lists, but only linked lists in which each node
- contains nothing except a pointer to the next node in the list. This is
- of course quite restrictive, but there is nevertheless quite a bit that
- can be done within these confines, as can be seen in the litmus test
- at tools/memory-model/litmus-tests/MP+onceassign+derefonce.litmus:
- 1 C MP+onceassign+derefonce
- 2
- 3 {
- 4 y=z;
- 5 z=0;
- 6 }
- 7
- 8 P0(int *x, int **y)
- 9 {
- 10 WRITE_ONCE(*x, 1);
- 11 rcu_assign_pointer(*y, x);
- 12 }
- 13
- 14 P1(int *x, int **y)
- 15 {
- 16 int *r0;
- 17 int r1;
- 18
- 19 rcu_read_lock();
- 20 r0 = rcu_dereference(*y);
- 21 r1 = READ_ONCE(*r0);
- 22 rcu_read_unlock();
- 23 }
- 24
- 25 exists (1:r0=x /\ 1:r1=0)
- Line 4's "y=z" may seem odd, given that "z" has not yet been initialized.
- But "y=z" does not set the value of "y" to that of "z", but instead
- sets the value of "y" to the *address* of "z". Lines 4 and 5 therefore
- create a simple linked list, with "y" pointing to "z" and "z" having a
- NULL pointer. A much longer linked list could be created if desired,
- and circular singly linked lists can also be created and manipulated.
- The "exists" clause works the same way, with the "1:r0=x" comparing P1()'s
- "r0" not to the value of "x", but again to its address. This term of the
- "exists" clause therefore tests whether line 20's load from "y" saw the
- value stored by line 11, which is in fact what is required in this case.
- P0()'s line 10 initializes "x" to the value 1 then line 11 links to "x"
- from "y", replacing "z".
- P1()'s line 20 loads a pointer from "y", and line 21 dereferences that
- pointer. The RCU read-side critical section spanning lines 19-22 is just
- for show in this example. Note that the address used for line 21's load
- depends on (in this case, "is exactly the same as") the value loaded by
- line 20. This is an example of what is called an "address dependency".
- This particular address dependency extends from the load on line 20 to the
- load on line 21. Address dependencies provide a weak form of ordering.
- Running this test results in the following:
- 1 Test MP+onceassign+derefonce Allowed
- 2 States 2
- 3 1:r0=x; 1:r1=1;
- 4 1:r0=z; 1:r1=0;
- 5 No
- 6 Witnesses
- 7 Positive: 0 Negative: 2
- 8 Condition exists (1:r0=x /\ 1:r1=0)
- 9 Observation MP+onceassign+derefonce Never 0 2
- 10 Time MP+onceassign+derefonce 0.00
- 11 Hash=49ef7a741563570102448a256a0c8568
- The only possible outcomes feature P1() loading a pointer to "z"
- (which contains zero) on the one hand and P1() loading a pointer to "x"
- (which contains the value one) on the other. This should be reassuring
- because it says that RCU readers cannot see the old preinitialization
- values when accessing a newly inserted list node. This undesirable
- scenario is flagged by the "exists" clause, and would occur if P1()
- loaded a pointer to "x", but obtained the pre-initialization value of
- zero after dereferencing that pointer.
- Comments
- --------
- Different portions of a litmus test are processed by different parsers,
- which has the charming effect of requiring different comment syntax in
- different portions of the litmus test. The C-syntax portions use
- C-language comments (either "/* */" or "//"), while the other portions
- use Ocaml comments "(* *)".
- The following litmus test illustrates the comment style corresponding
- to each syntactic unit of the test:
- 1 C MP+onceassign+derefonce (* A *)
- 2
- 3 (* B *)
- 4
- 5 {
- 6 y=z; (* C *)
- 7 z=0;
- 8 } // D
- 9
- 10 // E
- 11
- 12 P0(int *x, int **y) // F
- 13 {
- 14 WRITE_ONCE(*x, 1); // G
- 15 rcu_assign_pointer(*y, x);
- 16 }
- 17
- 18 // H
- 19
- 20 P1(int *x, int **y)
- 21 {
- 22 int *r0;
- 23 int r1;
- 24
- 25 rcu_read_lock();
- 26 r0 = rcu_dereference(*y);
- 27 r1 = READ_ONCE(*r0);
- 28 rcu_read_unlock();
- 29 }
- 30
- 31 // I
- 32
- 33 exists (* J *) (1:r0=x /\ (* K *) 1:r1=0) (* L *)
- In short, use C-language comments in the C code and Ocaml comments in
- the rest of the litmus test.
- On the other hand, if you prefer C-style comments everywhere, the
- C preprocessor is your friend.
- Asynchronous RCU Grace Periods
- ------------------------------
- The following litmus test is derived from the example show in
- Documentation/litmus-tests/rcu/RCU+sync+free.litmus, but converted to
- emulate call_rcu():
- 1 C RCU+sync+free
- 2
- 3 {
- 4 int x = 1;
- 5 int *y = &x;
- 6 int z = 1;
- 7 }
- 8
- 9 P0(int *x, int *z, int **y)
- 10 {
- 11 int *r0;
- 12 int r1;
- 13
- 14 rcu_read_lock();
- 15 r0 = rcu_dereference(*y);
- 16 r1 = READ_ONCE(*r0);
- 17 rcu_read_unlock();
- 18 }
- 19
- 20 P1(int *z, int **y, int *c)
- 21 {
- 22 rcu_assign_pointer(*y, z);
- 23 smp_store_release(*c, 1); // Emulate call_rcu().
- 24 }
- 25
- 26 P2(int *x, int *z, int **y, int *c)
- 27 {
- 28 int r0;
- 29
- 30 r0 = smp_load_acquire(*c); // Note call_rcu() request.
- 31 synchronize_rcu(); // Wait one grace period.
- 32 WRITE_ONCE(*x, 0); // Emulate the RCU callback.
- 33 }
- 34
- 35 filter (2:r0=1) (* Reject too-early starts. *)
- 36 exists (0:r0=x /\ 0:r1=0)
- Lines 4-6 initialize a linked list headed by "y" that initially contains
- "x". In addition, "z" is pre-initialized to prepare for P1(), which
- will replace "x" with "z" in this list.
- P0() on lines 9-18 enters an RCU read-side critical section, loads the
- list header "y" and dereferences it, leaving the node in "0:r0" and
- the node's value in "0:r1".
- P1() on lines 20-24 updates the list header to instead reference "z",
- then emulates call_rcu() by doing a release store into "c".
- P2() on lines 27-33 emulates the behind-the-scenes effect of doing a
- call_rcu(). Line 30 first does an acquire load from "c", then line 31
- waits for an RCU grace period to elapse, and finally line 32 emulates
- the RCU callback, which in turn emulates a call to kfree().
- Of course, it is possible for P2() to start too soon, so that the
- value of "2:r0" is zero rather than the required value of "1".
- The "filter" clause on line 35 handles this possibility, rejecting
- all executions in which "2:r0" is not equal to the value "1".
- Performance
- -----------
- LKMM's exploration of the full state-space can be extremely helpful,
- but it does not come for free. The price is exponential computational
- complexity in terms of the number of processes, the average number
- of statements in each process, and the total number of stores in the
- litmus test.
- So it is best to start small and then work up. Where possible, break
- your code down into small pieces each representing a core concurrency
- requirement.
- That said, herd7 is quite fast. On an unprepossessing x86 laptop, it
- was able to analyze the following 10-process RCU litmus test in about
- six seconds.
- https://github.com/paulmckrcu/litmus/blob/master/auto/C-RW-R+RW-R+RW-G+RW-G+RW-G+RW-G+RW-R+RW-R+RW-R+RW-R.litmus
- One way to make herd7 run faster is to use the "-speedcheck true" option.
- This option prevents herd7 from generating all possible end states,
- instead causing it to focus solely on whether or not the "exists"
- clause can be satisfied. With this option, herd7 evaluates the above
- litmus test in about 300 milliseconds, for more than an order of magnitude
- improvement in performance.
- Larger 16-process litmus tests that would normally consume 15 minutes
- of time complete in about 40 seconds with this option. To be fair,
- you do get an extra 65,535 states when you leave off the "-speedcheck
- true" option.
- https://github.com/paulmckrcu/litmus/blob/master/auto/C-RW-R+RW-R+RW-G+RW-G+RW-G+RW-G+RW-R+RW-R+RW-R+RW-R+RW-G+RW-G+RW-G+RW-G+RW-R+RW-R.litmus
- Nevertheless, litmus-test analysis really is of exponential complexity,
- whether with or without "-speedcheck true". Increasing by just three
- processes to a 19-process litmus test requires 2 hours and 40 minutes
- without, and about 8 minutes with "-speedcheck true". Each of these
- results represent roughly an order of magnitude slowdown compared to the
- 16-process litmus test. Again, to be fair, the multi-hour run explores
- no fewer than 524,287 additional states compared to the shorter one.
- https://github.com/paulmckrcu/litmus/blob/master/auto/C-RW-R+RW-R+RW-G+RW-G+RW-G+RW-G+RW-R+RW-R+RW-R+RW-R+RW-R+RW-R+RW-G+RW-G+RW-G+RW-G+RW-R+RW-R+RW-R.litmus
- If you don't like command-line arguments, you can obtain a similar speedup
- by adding a "filter" clause with exactly the same expression as your
- "exists" clause.
- However, please note that seeing the full set of states can be extremely
- helpful when developing and debugging litmus tests.
- LIMITATIONS
- ===========
- Limitations of the Linux-kernel memory model (LKMM) include:
- 1. Compiler optimizations are not accurately modeled. Of course,
- the use of READ_ONCE() and WRITE_ONCE() limits the compiler's
- ability to optimize, but under some circumstances it is possible
- for the compiler to undermine the memory model. For more
- information, see Documentation/explanation.txt (in particular,
- the "THE PROGRAM ORDER RELATION: po AND po-loc" and "A WARNING"
- sections).
- Note that this limitation in turn limits LKMM's ability to
- accurately model address, control, and data dependencies.
- For example, if the compiler can deduce the value of some variable
- carrying a dependency, then the compiler can break that dependency
- by substituting a constant of that value.
- Conversely, LKMM will sometimes overestimate the amount of
- reordering compilers and CPUs can carry out, leading it to miss
- some pretty obvious cases of ordering. A simple example is:
- r1 = READ_ONCE(x);
- if (r1 == 0)
- smp_mb();
- WRITE_ONCE(y, 1);
- The WRITE_ONCE() does not depend on the READ_ONCE(), and as a
- result, LKMM does not claim ordering. However, even though no
- dependency is present, the WRITE_ONCE() will not be executed before
- the READ_ONCE(). There are two reasons for this:
- The presence of the smp_mb() in one of the branches
- prevents the compiler from moving the WRITE_ONCE()
- up before the "if" statement, since the compiler has
- to assume that r1 will sometimes be 0 (but see the
- comment below);
- CPUs do not execute stores before po-earlier conditional
- branches, even in cases where the store occurs after the
- two arms of the branch have recombined.
- It is clear that it is not dangerous in the slightest for LKMM to
- make weaker guarantees than architectures. In fact, it is
- desirable, as it gives compilers room for making optimizations.
- For instance, suppose that a 0 value in r1 would trigger undefined
- behavior elsewhere. Then a clever compiler might deduce that r1
- can never be 0 in the if condition. As a result, said clever
- compiler might deem it safe to optimize away the smp_mb(),
- eliminating the branch and any ordering an architecture would
- guarantee otherwise.
- 2. Multiple access sizes for a single variable are not supported,
- and neither are misaligned or partially overlapping accesses.
- 3. Exceptions and interrupts are not modeled. In some cases,
- this limitation can be overcome by modeling the interrupt or
- exception with an additional process.
- 4. I/O such as MMIO or DMA is not supported.
- 5. Self-modifying code (such as that found in the kernel's
- alternatives mechanism, function tracer, Berkeley Packet Filter
- JIT compiler, and module loader) is not supported.
- 6. Complete modeling of all variants of atomic read-modify-write
- operations, locking primitives, and RCU is not provided.
- For example, call_rcu() and rcu_barrier() are not supported.
- However, a substantial amount of support is provided for these
- operations, as shown in the linux-kernel.def file.
- Here are specific limitations:
- a. When rcu_assign_pointer() is passed NULL, the Linux
- kernel provides no ordering, but LKMM models this
- case as a store release.
- b. The "unless" RMW operations are not currently modeled:
- atomic_long_add_unless(), atomic_inc_unless_negative(),
- and atomic_dec_unless_positive(). These can be emulated
- in litmus tests, for example, by using atomic_cmpxchg().
- One exception of this limitation is atomic_add_unless(),
- which is provided directly by herd7 (so no corresponding
- definition in linux-kernel.def). atomic_add_unless() is
- modeled by herd7 therefore it can be used in litmus tests.
- c. The call_rcu() function is not modeled. As was shown above,
- it can be emulated in litmus tests by adding another
- process that invokes synchronize_rcu() and the body of the
- callback function, with (for example) a release-acquire
- from the site of the emulated call_rcu() to the beginning
- of the additional process.
- d. The rcu_barrier() function is not modeled. It can be
- emulated in litmus tests emulating call_rcu() via
- (for example) a release-acquire from the end of each
- additional call_rcu() process to the site of the
- emulated rcu-barrier().
- e. Although sleepable RCU (SRCU) is now modeled, there
- are some subtle differences between its semantics and
- those in the Linux kernel. For example, the kernel
- might interpret the following sequence as two partially
- overlapping SRCU read-side critical sections:
- 1 r1 = srcu_read_lock(&my_srcu);
- 2 do_something_1();
- 3 r2 = srcu_read_lock(&my_srcu);
- 4 do_something_2();
- 5 srcu_read_unlock(&my_srcu, r1);
- 6 do_something_3();
- 7 srcu_read_unlock(&my_srcu, r2);
- In contrast, LKMM will interpret this as a nested pair of
- SRCU read-side critical sections, with the outer critical
- section spanning lines 1-7 and the inner critical section
- spanning lines 3-5.
- This difference would be more of a concern had anyone
- identified a reasonable use case for partially overlapping
- SRCU read-side critical sections. For more information
- on the trickiness of such overlapping, please see:
- https://paulmck.livejournal.com/40593.html
- f. Reader-writer locking is not modeled. It can be
- emulated in litmus tests using atomic read-modify-write
- operations.
- The fragment of the C language supported by these litmus tests is quite
- limited and in some ways non-standard:
- 1. There is no automatic C-preprocessor pass. You can of course
- run it manually, if you choose.
- 2. There is no way to create functions other than the Pn() functions
- that model the concurrent processes.
- 3. The Pn() functions' formal parameters must be pointers to the
- global shared variables. Nothing can be passed by value into
- these functions.
- 4. The only functions that can be invoked are those built directly
- into herd7 or that are defined in the linux-kernel.def file.
- 5. The "switch", "do", "for", "while", and "goto" C statements are
- not supported. The "switch" statement can be emulated by the
- "if" statement. The "do", "for", and "while" statements can
- often be emulated by manually unrolling the loop, or perhaps by
- enlisting the aid of the C preprocessor to minimize the resulting
- code duplication. Some uses of "goto" can be emulated by "if",
- and some others by unrolling.
- 6. Although you can use a wide variety of types in litmus-test
- variable declarations, and especially in global-variable
- declarations, the "herd7" tool understands only int and
- pointer types. There is no support for floating-point types,
- enumerations, characters, strings, arrays, or structures.
- 7. Parsing of variable declarations is very loose, with almost no
- type checking.
- 8. Initializers differ from their C-language counterparts.
- For example, when an initializer contains the name of a shared
- variable, that name denotes a pointer to that variable, not
- the current value of that variable. For example, "int x = y"
- is interpreted the way "int x = &y" would be in C.
- 9. Dynamic memory allocation is not supported, although this can
- be worked around in some cases by supplying multiple statically
- allocated variables.
- Some of these limitations may be overcome in the future, but others are
- more likely to be addressed by incorporating the Linux-kernel memory model
- into other tools.
- Finally, please note that LKMM is subject to change as hardware, use cases,
- and compilers evolve.
|