123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875 |
- Crossrelease
- ============
- Started by Byungchul Park <byungchul.park@lge.com>
- Contents:
- (*) Background
- - What causes deadlock
- - How lockdep works
- (*) Limitation
- - Limit lockdep
- - Pros from the limitation
- - Cons from the limitation
- - Relax the limitation
- (*) Crossrelease
- - Introduce crossrelease
- - Introduce commit
- (*) Implementation
- - Data structures
- - How crossrelease works
- (*) Optimizations
- - Avoid duplication
- - Lockless for hot paths
- (*) APPENDIX A: What lockdep does to work aggresively
- (*) APPENDIX B: How to avoid adding false dependencies
- ==========
- Background
- ==========
- What causes deadlock
- --------------------
- A deadlock occurs when a context is waiting for an event to happen,
- which is impossible because another (or the) context who can trigger the
- event is also waiting for another (or the) event to happen, which is
- also impossible due to the same reason.
- For example:
- A context going to trigger event C is waiting for event A to happen.
- A context going to trigger event A is waiting for event B to happen.
- A context going to trigger event B is waiting for event C to happen.
- A deadlock occurs when these three wait operations run at the same time,
- because event C cannot be triggered if event A does not happen, which in
- turn cannot be triggered if event B does not happen, which in turn
- cannot be triggered if event C does not happen. After all, no event can
- be triggered since any of them never meets its condition to wake up.
- A dependency might exist between two waiters and a deadlock might happen
- due to an incorrect releationship between dependencies. Thus, we must
- define what a dependency is first. A dependency exists between them if:
- 1. There are two waiters waiting for each event at a given time.
- 2. The only way to wake up each waiter is to trigger its event.
- 3. Whether one can be woken up depends on whether the other can.
- Each wait in the example creates its dependency like:
- Event C depends on event A.
- Event A depends on event B.
- Event B depends on event C.
- NOTE: Precisely speaking, a dependency is one between whether a
- waiter for an event can be woken up and whether another waiter for
- another event can be woken up. However from now on, we will describe
- a dependency as if it's one between an event and another event for
- simplicity.
- And they form circular dependencies like:
- -> C -> A -> B -
- / \
- \ /
- ----------------
- where 'A -> B' means that event A depends on event B.
- Such circular dependencies lead to a deadlock since no waiter can meet
- its condition to wake up as described.
- CONCLUSION
- Circular dependencies cause a deadlock.
- How lockdep works
- -----------------
- Lockdep tries to detect a deadlock by checking dependencies created by
- lock operations, acquire and release. Waiting for a lock corresponds to
- waiting for an event, and releasing a lock corresponds to triggering an
- event in the previous section.
- In short, lockdep does:
- 1. Detect a new dependency.
- 2. Add the dependency into a global graph.
- 3. Check if that makes dependencies circular.
- 4. Report a deadlock or its possibility if so.
- For example, consider a graph built by lockdep that looks like:
- A -> B -
- \
- -> E
- /
- C -> D -
- where A, B,..., E are different lock classes.
- Lockdep will add a dependency into the graph on detection of a new
- dependency. For example, it will add a dependency 'E -> C' when a new
- dependency between lock E and lock C is detected. Then the graph will be:
- A -> B -
- \
- -> E -
- / \
- -> C -> D - \
- / /
- \ /
- ------------------
- where A, B,..., E are different lock classes.
- This graph contains a subgraph which demonstrates circular dependencies:
- -> E -
- / \
- -> C -> D - \
- / /
- \ /
- ------------------
- where C, D and E are different lock classes.
- This is the condition under which a deadlock might occur. Lockdep
- reports it on detection after adding a new dependency. This is the way
- how lockdep works.
- CONCLUSION
- Lockdep detects a deadlock or its possibility by checking if circular
- dependencies were created after adding each new dependency.
- ==========
- Limitation
- ==========
- Limit lockdep
- -------------
- Limiting lockdep to work on only typical locks e.g. spin locks and
- mutexes, which are released within the acquire context, the
- implementation becomes simple but its capacity for detection becomes
- limited. Let's check pros and cons in next section.
- Pros from the limitation
- ------------------------
- Given the limitation, when acquiring a lock, locks in a held_locks
- cannot be released if the context cannot acquire it so has to wait to
- acquire it, which means all waiters for the locks in the held_locks are
- stuck. It's an exact case to create dependencies between each lock in
- the held_locks and the lock to acquire.
- For example:
- CONTEXT X
- ---------
- acquire A
- acquire B /* Add a dependency 'A -> B' */
- release B
- release A
- where A and B are different lock classes.
- When acquiring lock A, the held_locks of CONTEXT X is empty thus no
- dependency is added. But when acquiring lock B, lockdep detects and adds
- a new dependency 'A -> B' between lock A in the held_locks and lock B.
- They can be simply added whenever acquiring each lock.
- And data required by lockdep exists in a local structure, held_locks
- embedded in task_struct. Forcing to access the data within the context,
- lockdep can avoid racy problems without explicit locks while handling
- the local data.
- Lastly, lockdep only needs to keep locks currently being held, to build
- a dependency graph. However, relaxing the limitation, it needs to keep
- even locks already released, because a decision whether they created
- dependencies might be long-deferred.
- To sum up, we can expect several advantages from the limitation:
- 1. Lockdep can easily identify a dependency when acquiring a lock.
- 2. Races are avoidable while accessing local locks in a held_locks.
- 3. Lockdep only needs to keep locks currently being held.
- CONCLUSION
- Given the limitation, the implementation becomes simple and efficient.
- Cons from the limitation
- ------------------------
- Given the limitation, lockdep is applicable only to typical locks. For
- example, page locks for page access or completions for synchronization
- cannot work with lockdep.
- Can we detect deadlocks below, under the limitation?
- Example 1:
- CONTEXT X CONTEXT Y CONTEXT Z
- --------- --------- ----------
- mutex_lock A
- lock_page B
- lock_page B
- mutex_lock A /* DEADLOCK */
- unlock_page B held by X
- unlock_page B
- mutex_unlock A
- mutex_unlock A
- where A and B are different lock classes.
- No, we cannot.
- Example 2:
- CONTEXT X CONTEXT Y
- --------- ---------
- mutex_lock A
- mutex_lock A
- wait_for_complete B /* DEADLOCK */
- complete B
- mutex_unlock A
- mutex_unlock A
- where A is a lock class and B is a completion variable.
- No, we cannot.
- CONCLUSION
- Given the limitation, lockdep cannot detect a deadlock or its
- possibility caused by page locks or completions.
- Relax the limitation
- --------------------
- Under the limitation, things to create dependencies are limited to
- typical locks. However, synchronization primitives like page locks and
- completions, which are allowed to be released in any context, also
- create dependencies and can cause a deadlock. So lockdep should track
- these locks to do a better job. We have to relax the limitation for
- these locks to work with lockdep.
- Detecting dependencies is very important for lockdep to work because
- adding a dependency means adding an opportunity to check whether it
- causes a deadlock. The more lockdep adds dependencies, the more it
- thoroughly works. Thus Lockdep has to do its best to detect and add as
- many true dependencies into a graph as possible.
- For example, considering only typical locks, lockdep builds a graph like:
- A -> B -
- \
- -> E
- /
- C -> D -
- where A, B,..., E are different lock classes.
- On the other hand, under the relaxation, additional dependencies might
- be created and added. Assuming additional 'FX -> C' and 'E -> GX' are
- added thanks to the relaxation, the graph will be:
- A -> B -
- \
- -> E -> GX
- /
- FX -> C -> D -
- where A, B,..., E, FX and GX are different lock classes, and a suffix
- 'X' is added on non-typical locks.
- The latter graph gives us more chances to check circular dependencies
- than the former. However, it might suffer performance degradation since
- relaxing the limitation, with which design and implementation of lockdep
- can be efficient, might introduce inefficiency inevitably. So lockdep
- should provide two options, strong detection and efficient detection.
- Choosing efficient detection:
- Lockdep works with only locks restricted to be released within the
- acquire context. However, lockdep works efficiently.
- Choosing strong detection:
- Lockdep works with all synchronization primitives. However, lockdep
- suffers performance degradation.
- CONCLUSION
- Relaxing the limitation, lockdep can add additional dependencies giving
- additional opportunities to check circular dependencies.
- ============
- Crossrelease
- ============
- Introduce crossrelease
- ----------------------
- In order to allow lockdep to handle additional dependencies by what
- might be released in any context, namely 'crosslock', we have to be able
- to identify those created by crosslocks. The proposed 'crossrelease'
- feature provoides a way to do that.
- Crossrelease feature has to do:
- 1. Identify dependencies created by crosslocks.
- 2. Add the dependencies into a dependency graph.
- That's all. Once a meaningful dependency is added into graph, then
- lockdep would work with the graph as it did. The most important thing
- crossrelease feature has to do is to correctly identify and add true
- dependencies into the global graph.
- A dependency e.g. 'A -> B' can be identified only in the A's release
- context because a decision required to identify the dependency can be
- made only in the release context. That is to decide whether A can be
- released so that a waiter for A can be woken up. It cannot be made in
- other than the A's release context.
- It's no matter for typical locks because each acquire context is same as
- its release context, thus lockdep can decide whether a lock can be
- released in the acquire context. However for crosslocks, lockdep cannot
- make the decision in the acquire context but has to wait until the
- release context is identified.
- Therefore, deadlocks by crosslocks cannot be detected just when it
- happens, because those cannot be identified until the crosslocks are
- released. However, deadlock possibilities can be detected and it's very
- worth. See 'APPENDIX A' section to check why.
- CONCLUSION
- Using crossrelease feature, lockdep can work with what might be released
- in any context, namely crosslock.
- Introduce commit
- ----------------
- Since crossrelease defers the work adding true dependencies of
- crosslocks until they are actually released, crossrelease has to queue
- all acquisitions which might create dependencies with the crosslocks.
- Then it identifies dependencies using the queued data in batches at a
- proper time. We call it 'commit'.
- There are four types of dependencies:
- 1. TT type: 'typical lock A -> typical lock B'
- Just when acquiring B, lockdep can see it's in the A's release
- context. So the dependency between A and B can be identified
- immediately. Commit is unnecessary.
- 2. TC type: 'typical lock A -> crosslock BX'
- Just when acquiring BX, lockdep can see it's in the A's release
- context. So the dependency between A and BX can be identified
- immediately. Commit is unnecessary, too.
- 3. CT type: 'crosslock AX -> typical lock B'
- When acquiring B, lockdep cannot identify the dependency because
- there's no way to know if it's in the AX's release context. It has
- to wait until the decision can be made. Commit is necessary.
- 4. CC type: 'crosslock AX -> crosslock BX'
- When acquiring BX, lockdep cannot identify the dependency because
- there's no way to know if it's in the AX's release context. It has
- to wait until the decision can be made. Commit is necessary.
- But, handling CC type is not implemented yet. It's a future work.
- Lockdep can work without commit for typical locks, but commit step is
- necessary once crosslocks are involved. Introducing commit, lockdep
- performs three steps. What lockdep does in each step is:
- 1. Acquisition: For typical locks, lockdep does what it originally did
- and queues the lock so that CT type dependencies can be checked using
- it at the commit step. For crosslocks, it saves data which will be
- used at the commit step and increases a reference count for it.
- 2. Commit: No action is reauired for typical locks. For crosslocks,
- lockdep adds CT type dependencies using the data saved at the
- acquisition step.
- 3. Release: No changes are required for typical locks. When a crosslock
- is released, it decreases a reference count for it.
- CONCLUSION
- Crossrelease introduces commit step to handle dependencies of crosslocks
- in batches at a proper time.
- ==============
- Implementation
- ==============
- Data structures
- ---------------
- Crossrelease introduces two main data structures.
- 1. hist_lock
- This is an array embedded in task_struct, for keeping lock history so
- that dependencies can be added using them at the commit step. Since
- it's local data, it can be accessed locklessly in the owner context.
- The array is filled at the acquisition step and consumed at the
- commit step. And it's managed in circular manner.
- 2. cross_lock
- One per lockdep_map exists. This is for keeping data of crosslocks
- and used at the commit step.
- How crossrelease works
- ----------------------
- It's the key of how crossrelease works, to defer necessary works to an
- appropriate point in time and perform in at once at the commit step.
- Let's take a look with examples step by step, starting from how lockdep
- works without crossrelease for typical locks.
- acquire A /* Push A onto held_locks */
- acquire B /* Push B onto held_locks and add 'A -> B' */
- acquire C /* Push C onto held_locks and add 'B -> C' */
- release C /* Pop C from held_locks */
- release B /* Pop B from held_locks */
- release A /* Pop A from held_locks */
- where A, B and C are different lock classes.
- NOTE: This document assumes that readers already understand how
- lockdep works without crossrelease thus omits details. But there's
- one thing to note. Lockdep pretends to pop a lock from held_locks
- when releasing it. But it's subtly different from the original pop
- operation because lockdep allows other than the top to be poped.
- In this case, lockdep adds 'the top of held_locks -> the lock to acquire'
- dependency every time acquiring a lock.
- After adding 'A -> B', a dependency graph will be:
- A -> B
- where A and B are different lock classes.
- And after adding 'B -> C', the graph will be:
- A -> B -> C
- where A, B and C are different lock classes.
- Let's performs commit step even for typical locks to add dependencies.
- Of course, commit step is not necessary for them, however, it would work
- well because this is a more general way.
- acquire A
- /*
- * Queue A into hist_locks
- *
- * In hist_locks: A
- * In graph: Empty
- */
- acquire B
- /*
- * Queue B into hist_locks
- *
- * In hist_locks: A, B
- * In graph: Empty
- */
- acquire C
- /*
- * Queue C into hist_locks
- *
- * In hist_locks: A, B, C
- * In graph: Empty
- */
- commit C
- /*
- * Add 'C -> ?'
- * Answer the following to decide '?'
- * What has been queued since acquire C: Nothing
- *
- * In hist_locks: A, B, C
- * In graph: Empty
- */
- release C
- commit B
- /*
- * Add 'B -> ?'
- * Answer the following to decide '?'
- * What has been queued since acquire B: C
- *
- * In hist_locks: A, B, C
- * In graph: 'B -> C'
- */
- release B
- commit A
- /*
- * Add 'A -> ?'
- * Answer the following to decide '?'
- * What has been queued since acquire A: B, C
- *
- * In hist_locks: A, B, C
- * In graph: 'B -> C', 'A -> B', 'A -> C'
- */
- release A
- where A, B and C are different lock classes.
- In this case, dependencies are added at the commit step as described.
- After commits for A, B and C, the graph will be:
- A -> B -> C
- where A, B and C are different lock classes.
- NOTE: A dependency 'A -> C' is optimized out.
- We can see the former graph built without commit step is same as the
- latter graph built using commit steps. Of course the former way leads to
- earlier finish for building the graph, which means we can detect a
- deadlock or its possibility sooner. So the former way would be prefered
- when possible. But we cannot avoid using the latter way for crosslocks.
- Let's look at how commit steps work for crosslocks. In this case, the
- commit step is performed only on crosslock AX as real. And it assumes
- that the AX release context is different from the AX acquire context.
- BX RELEASE CONTEXT BX ACQUIRE CONTEXT
- ------------------ ------------------
- acquire A
- /*
- * Push A onto held_locks
- * Queue A into hist_locks
- *
- * In held_locks: A
- * In hist_locks: A
- * In graph: Empty
- */
- acquire BX
- /*
- * Add 'the top of held_locks -> BX'
- *
- * In held_locks: A
- * In hist_locks: A
- * In graph: 'A -> BX'
- */
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- It must be guaranteed that the following operations are seen after
- acquiring BX globally. It can be done by things like barrier.
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- acquire C
- /*
- * Push C onto held_locks
- * Queue C into hist_locks
- *
- * In held_locks: C
- * In hist_locks: C
- * In graph: 'A -> BX'
- */
- release C
- /*
- * Pop C from held_locks
- *
- * In held_locks: Empty
- * In hist_locks: C
- * In graph: 'A -> BX'
- */
- acquire D
- /*
- * Push D onto held_locks
- * Queue D into hist_locks
- * Add 'the top of held_locks -> D'
- *
- * In held_locks: A, D
- * In hist_locks: A, D
- * In graph: 'A -> BX', 'A -> D'
- */
- acquire E
- /*
- * Push E onto held_locks
- * Queue E into hist_locks
- *
- * In held_locks: E
- * In hist_locks: C, E
- * In graph: 'A -> BX', 'A -> D'
- */
- release E
- /*
- * Pop E from held_locks
- *
- * In held_locks: Empty
- * In hist_locks: D, E
- * In graph: 'A -> BX', 'A -> D'
- */
- release D
- /*
- * Pop D from held_locks
- *
- * In held_locks: A
- * In hist_locks: A, D
- * In graph: 'A -> BX', 'A -> D'
- */
- commit BX
- /*
- * Add 'BX -> ?'
- * What has been queued since acquire BX: C, E
- *
- * In held_locks: Empty
- * In hist_locks: D, E
- * In graph: 'A -> BX', 'A -> D',
- * 'BX -> C', 'BX -> E'
- */
- release BX
- /*
- * In held_locks: Empty
- * In hist_locks: D, E
- * In graph: 'A -> BX', 'A -> D',
- * 'BX -> C', 'BX -> E'
- */
- release A
- /*
- * Pop A from held_locks
- *
- * In held_locks: Empty
- * In hist_locks: A, D
- * In graph: 'A -> BX', 'A -> D',
- * 'BX -> C', 'BX -> E'
- */
- where A, BX, C,..., E are different lock classes, and a suffix 'X' is
- added on crosslocks.
- Crossrelease considers all acquisitions after acqiuring BX are
- candidates which might create dependencies with BX. True dependencies
- will be determined when identifying the release context of BX. Meanwhile,
- all typical locks are queued so that they can be used at the commit step.
- And then two dependencies 'BX -> C' and 'BX -> E' are added at the
- commit step when identifying the release context.
- The final graph will be, with crossrelease:
- -> C
- /
- -> BX -
- / \
- A - -> E
- \
- -> D
- where A, BX, C,..., E are different lock classes, and a suffix 'X' is
- added on crosslocks.
- However, the final graph will be, without crossrelease:
- A -> D
- where A and D are different lock classes.
- The former graph has three more dependencies, 'A -> BX', 'BX -> C' and
- 'BX -> E' giving additional opportunities to check if they cause
- deadlocks. This way lockdep can detect a deadlock or its possibility
- caused by crosslocks.
- CONCLUSION
- We checked how crossrelease works with several examples.
- =============
- Optimizations
- =============
- Avoid duplication
- -----------------
- Crossrelease feature uses a cache like what lockdep already uses for
- dependency chains, but this time it's for caching CT type dependencies.
- Once that dependency is cached, the same will never be added again.
- Lockless for hot paths
- ----------------------
- To keep all locks for later use at the commit step, crossrelease adopts
- a local array embedded in task_struct, which makes access to the data
- lockless by forcing it to happen only within the owner context. It's
- like how lockdep handles held_locks. Lockless implmentation is important
- since typical locks are very frequently acquired and released.
- =================================================
- APPENDIX A: What lockdep does to work aggresively
- =================================================
- A deadlock actually occurs when all wait operations creating circular
- dependencies run at the same time. Even though they don't, a potential
- deadlock exists if the problematic dependencies exist. Thus it's
- meaningful to detect not only an actual deadlock but also its potential
- possibility. The latter is rather valuable. When a deadlock occurs
- actually, we can identify what happens in the system by some means or
- other even without lockdep. However, there's no way to detect possiblity
- without lockdep unless the whole code is parsed in head. It's terrible.
- Lockdep does the both, and crossrelease only focuses on the latter.
- Whether or not a deadlock actually occurs depends on several factors.
- For example, what order contexts are switched in is a factor. Assuming
- circular dependencies exist, a deadlock would occur when contexts are
- switched so that all wait operations creating the dependencies run
- simultaneously. Thus to detect a deadlock possibility even in the case
- that it has not occured yet, lockdep should consider all possible
- combinations of dependencies, trying to:
- 1. Use a global dependency graph.
- Lockdep combines all dependencies into one global graph and uses them,
- regardless of which context generates them or what order contexts are
- switched in. Aggregated dependencies are only considered so they are
- prone to be circular if a problem exists.
- 2. Check dependencies between classes instead of instances.
- What actually causes a deadlock are instances of lock. However,
- lockdep checks dependencies between classes instead of instances.
- This way lockdep can detect a deadlock which has not happened but
- might happen in future by others but the same class.
- 3. Assume all acquisitions lead to waiting.
- Although locks might be acquired without waiting which is essential
- to create dependencies, lockdep assumes all acquisitions lead to
- waiting since it might be true some time or another.
- CONCLUSION
- Lockdep detects not only an actual deadlock but also its possibility,
- and the latter is more valuable.
- ==================================================
- APPENDIX B: How to avoid adding false dependencies
- ==================================================
- Remind what a dependency is. A dependency exists if:
- 1. There are two waiters waiting for each event at a given time.
- 2. The only way to wake up each waiter is to trigger its event.
- 3. Whether one can be woken up depends on whether the other can.
- For example:
- acquire A
- acquire B /* A dependency 'A -> B' exists */
- release B
- release A
- where A and B are different lock classes.
- A depedency 'A -> B' exists since:
- 1. A waiter for A and a waiter for B might exist when acquiring B.
- 2. Only way to wake up each is to release what it waits for.
- 3. Whether the waiter for A can be woken up depends on whether the
- other can. IOW, TASK X cannot release A if it fails to acquire B.
- For another example:
- TASK X TASK Y
- ------ ------
- acquire AX
- acquire B /* A dependency 'AX -> B' exists */
- release B
- release AX held by Y
- where AX and B are different lock classes, and a suffix 'X' is added
- on crosslocks.
- Even in this case involving crosslocks, the same rule can be applied. A
- depedency 'AX -> B' exists since:
- 1. A waiter for AX and a waiter for B might exist when acquiring B.
- 2. Only way to wake up each is to release what it waits for.
- 3. Whether the waiter for AX can be woken up depends on whether the
- other can. IOW, TASK X cannot release AX if it fails to acquire B.
- Let's take a look at more complicated example:
- TASK X TASK Y
- ------ ------
- acquire B
- release B
- fork Y
- acquire AX
- acquire C /* A dependency 'AX -> C' exists */
- release C
- release AX held by Y
- where AX, B and C are different lock classes, and a suffix 'X' is
- added on crosslocks.
- Does a dependency 'AX -> B' exist? Nope.
- Two waiters are essential to create a dependency. However, waiters for
- AX and B to create 'AX -> B' cannot exist at the same time in this
- example. Thus the dependency 'AX -> B' cannot be created.
- It would be ideal if the full set of true ones can be considered. But
- we can ensure nothing but what actually happened. Relying on what
- actually happens at runtime, we can anyway add only true ones, though
- they might be a subset of true ones. It's similar to how lockdep works
- for typical locks. There might be more true dependencies than what
- lockdep has detected in runtime. Lockdep has no choice but to rely on
- what actually happens. Crossrelease also relies on it.
- CONCLUSION
- Relying on what actually happens, lockdep can avoid adding false
- dependencies.
|