lkmm: Fix corner-case locking bug and improve documentation

A simple but odd single-process litmus test acquires and immediately
 releases a lock, then calls spin_is_locked().  LKMM acts if it was
 a deadlock due to an assumption that spin_is_locked() will follow a
 spin_lock() or some other process's spin_unlock().  This litmus test
 manages to violate this assumption because the spin_is_locked() follows
 the same process's spin_unlock().
 
 This series fixes this bug, reorganizes and optimizes the lock.cat model,
 and updates documentation.
 -----BEGIN PGP SIGNATURE-----
 
 iQJHBAABCgAxFiEEbK7UrM+RBIrCoViJnr8S83LZ+4wFAmaRwSUTHHBhdWxtY2tA
 a2VybmVsLm9yZwAKCRCevxLzctn7jNmcD/0dhWCkytKMLwVke6oLP++HTFK/Uzrb
 XsXkq5t0Y+xzf7KKca9XuqEMRnaenLM07Swx0ujn2fH7FrXCNlzHD6R4T2e8r+L5
 9TVa1ewRfpjH8tM6YLsMbiH4yRah47F9p6CulRHJiHtyU2pLgThIF0/KrhiYKn2P
 aBF4zclt0TJkTgJDaoJ45z6YTWNFMv+DopRwWT9vm0CNS4jaxHrn8CaBtiv1arWe
 9CMArXtPS6eJjbZ9RPLSVlNUufLD7QnyPXRPjd7hwBHReEgcfvpAYKfsAZanhSqv
 vTp9PRQlu1j9pcjrSCzZ2baBXd0XhuX2wtjNBZXcb2yzIj6V2t5rADKKEG7IONTj
 eV0ZpMm5bF6lUZBtr1YowgNPwKktdqIVavXk1Sl/fiUs/B8thKZI/pekOMLwq5An
 SdcCO2m6tY5fYvsvIpfRExvx3TGrRHw+64C/jZrGPhLL6d87tTr0uuyZe1XDxv2+
 yGkDuKHUGg7lAXbd6sPvPLOej6pOFbb43qUa7AwBtXBrUs9nF1AuegTM8ux+5eCe
 qbA6GrvPyeSXOwsy0XD83oOQ4o93f0GdpMnIgQNK32uaEvQJf1MenTcTjXD8RfGv
 FqxSqhSGNQSXjmBCRFHujR2lR7aWSEUWTEdV+hnSd8O0q4IG2LMwK+TzbtJXJTl8
 KZgRYz7lne7sQw==
 =kUWy
 -----END PGP SIGNATURE-----

Merge tag 'lkmm.2024.07.12a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu

Pull memory model updates from Paul McKenney:
 "lkmm: Fix corner-case locking bug and improve documentation

  A simple but odd single-process litmus test acquires and immediately
  releases a lock, then calls spin_is_locked(). LKMM acts if it was a
  deadlock due to an assumption that spin_is_locked() will follow a
  spin_lock() or some other process's spin_unlock(). This litmus test
  manages to violate this assumption because the spin_is_locked()
  follows the same process's spin_unlock().

  This series fixes this bug, reorganizes and optimizes the lock.cat
  model, and updates documentation"

* tag 'lkmm.2024.07.12a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu:
  tools/memory-model: Code reorganization in lock.cat
  tools/memory-model: Fix bug in lock.cat
  tools/memory-model: Add access-marking.txt to README
  tools/memory-model: Add KCSAN LF mentorship session citation
This commit is contained in:
Linus Torvalds 2024-07-15 15:22:35 -07:00
commit 253e1e9818
3 changed files with 48 additions and 26 deletions

View file

@ -47,6 +47,10 @@ DESCRIPTION OF FILES
README
This file.
access-marking.txt
Guidelines for marking intentionally concurrent accesses to
shared memory.
cheatsheet.txt
Quick-reference guide to the Linux-kernel memory model.

View file

@ -6,7 +6,8 @@ normal accesses to shared memory, that is "normal" as in accesses that do
not use read-modify-write atomic operations. It also describes how to
document these accesses, both with comments and with special assertions
processed by the Kernel Concurrency Sanitizer (KCSAN). This discussion
builds on an earlier LWN article [1].
builds on an earlier LWN article [1] and Linux Foundation mentorship
session [2].
ACCESS-MARKING OPTIONS
@ -31,7 +32,7 @@ example:
WRITE_ONCE(a, b + data_race(c + d) + READ_ONCE(e));
Neither plain C-language accesses nor data_race() (#1 and #2 above) place
any sort of constraint on the compiler's choice of optimizations [2].
any sort of constraint on the compiler's choice of optimizations [3].
In contrast, READ_ONCE() and WRITE_ONCE() (#3 and #4 above) restrict the
compiler's use of code-motion and common-subexpression optimizations.
Therefore, if a given access is involved in an intentional data race,
@ -594,5 +595,8 @@ REFERENCES
[1] "Concurrency bugs should fear the big bad data-race detector (part 2)"
https://lwn.net/Articles/816854/
[2] "Who's afraid of a big bad optimizing compiler?"
[2] "The Kernel Concurrency Sanitizer"
https://www.linuxfoundation.org/webinars/the-kernel-concurrency-sanitizer
[3] "Who's afraid of a big bad optimizing compiler?"
https://lwn.net/Articles/793253/

View file

@ -54,6 +54,12 @@ flag ~empty LKR \ domain(lk-rmw) as unpaired-LKR
*)
empty ([LKW] ; po-loc ; [LKR]) \ (po-loc ; [UL] ; po-loc) as lock-nest
(*
* In the same way, spin_is_locked() inside a critical section must always
* return True (no RU events can be in a critical section for the same lock).
*)
empty ([LKW] ; po-loc ; [RU]) \ (po-loc ; [UL] ; po-loc) as nested-is-locked
(* The final value of a spinlock should not be tested *)
flag ~empty [FW] ; loc ; [ALL-LOCKS] as lock-final
@ -79,42 +85,50 @@ 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 *)
(* Utility macro to convert a single pair to a single-edge relation *)
let pair-to-relation p = p ++ 0
(*
* If a given LF event e is outside a critical section, it cannot read
* internally but it may read from an LKW event in another thread.
* Compute the relation containing these possible edges.
*)
let possible-rfe-noncrit-lf e = (LKW * {e}) & loc & ext
(* Compute set of sets of possible rfe edges 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.
* Convert the possible-rfe-noncrit-lf relation for e
* to a set of single edges
*)
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))
let set-of-singleton-rfe-lf e =
map pair-to-relation (possible-rfe-noncrit-lf e)
(* Do this for each LF event e that isn't in rfi-lf *)
in map set-of-singleton-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.
* A given RU event e may read internally from the last po-previous UL,
* or it may read from a UL event in another thread or the initial write.
* Compute the relation containing these possible edges.
*)
let possible-rf-ru e = (((UL * {e}) & po-loc) \
([UL] ; po-loc ; [UL] ; po-loc)) |
(((UL | IW) * {e}) & loc & ext)
(* 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
(* Compute set of sets of possible rf edges for RU events *)
let all-possible-rf-ru =
(* Convert the possible-rf-ru relation for e to a set of single edges *)
let set-of-singleton-rf-ru e =
map pair-to-relation (possible-rf-ru e)
(* Do this for each RU event e *)
in map set-of-singleton-rf-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
with rf-ru from cross(all-possible-rf-ru)
(* Final rf relation *)
let rf = rf | rf-lf | rf-ru