core27 min studyLesson 139 in Engineering

Choose locks from execution context and invariants

Choose protection from execution context, distinguish local exclusion from cross-CPU exclusion, and account for PREEMPT_RT semantics.

On this page

What you’ll understand

  • Identify which execution contexts can concurrently access an invariant.
  • Distinguish mutual exclusion, interrupt masking, preemption control, and lock ordering.
  • Explain the specific PREEMPT_RT distinction between spinlock_t and raw_spinlock_t.

Protecting kernel state starts with a question about who can run, not with a preference for a particular lock API. Another CPU, a preempting task, or a local interrupt handler may enter the same update through different paths. The primitive must preserve the invariant against all relevant paths while respecting the context’s restrictions. We will reason about those paths, examine what changes on PREEMPT_RT kernels, and connect correctness to the time other work spends waiting.

Specify the invariant and every access context

Suppose a queue stores both a linked list and a count. The invariant might require the count to equal the number of linked nodes whenever another context observes it. Making only the count increment atomic does not automatically make the combined list-and-count update atomic. Enumerate readers, writers, removal paths, error cleanup, and object destruction before choosing protection. Include interrupt and deferred-work access as well as process-context calls. The relevant failure is not merely two writes at the same instant; a reader may see a partly completed update or retain a reference after the object’s lifetime ends. Correct exclusion and correct lifetime management are related but separate obligations.

Disabling preemption prevents the current task from being involuntarily replaced by another task on that CPU within the applicable region; it does not stop other CPUs from touching shared memory. Disabling local interrupts likewise is not a system-wide lock. Conversely, mutual exclusion without handling local interrupt re-entry can deadlock if an interrupt tries to acquire a lock already held by the task it interrupted. Draw the possible execution edges and ask which mechanism removes each edge. On a non-PREEMPT_RT kernel, an appropriate spin-lock variant can combine cross-CPU exclusion with the needed local protection, but blindly disabling everything broadens latency and hides the intended concurrency contract.

count=∣reachable queue nodes∣\mathrm{count}=\lvert\mathrm{reachable\ queue\ nodes}\rvert
This example invariant must hold at externally observable points under the queue’s synchronization contract. Individual atomic updates do not by themselves maintain the compound invariant.↔ Scroll wide equations horizontally.

Sleeping rules and PREEMPT_RT change the choice

A kernel mutex is a sleeping lock and requires a context where its acquisition may sleep. A hard interrupt handler cannot use it merely because the lock is usually uncontended. In a genuinely atomic section, including one holding a raw spinlock, do not invoke operations that may sleep. Memory allocation flags, user-memory access, and nested helper calls deserve particular scrutiny because a harmless-looking call may have a blocking path. Shorten the region by preparing work before acquiring the lock and deferring blocking work until after release, while preserving ownership and revalidating any assumptions that can change outside protection. Short code is useful, but an audited context contract is the actual correctness criterion.

PREEMPT_RT changes spinlock_t into a sleeping, priority-inheritance-aware implementation based on RT mutex mechanisms. Its acquisition does not disable preemption, and its irq-suffixed operations do not provide the same hardware-interrupt masking semantics as their non-RT counterparts. Raw_spinlock_t remains a strict spinning primitive across configurations and is reserved for cases that truly need those low-level semantics. Do not infer from the RT implementation that arbitrary sleeping calls are now portable while holding a regular spinlock: code must obey the lock’s documented cross-configuration usage rules. Review nesting too; acquiring an RT sleeping lock from a region made atomic by a raw lock or explicit interrupt disabling can be invalid.

Tserialized≥∑i=1mhiT_{\mathrm{serialized}}\geq\sum_{i=1}^{m}h_i
For m non-overlapping critical sections protected by one exclusive lock, h_i is each hold duration in seconds. The elapsed serialized interval is at least their total hold time; acquisition, scheduling, and gaps can add more.↔ Scroll wide equations horizontally.

Use ordering evidence and latency evidence together

Multiple locks introduce an ordering problem beyond the correctness of each individual critical section. If one path holds A while acquiring B and another holds B while acquiring A, both may wait permanently. State a consistent order or redesign ownership so the cycle cannot occur. Lockdep records lock-class relationships and execution-context information to identify dangerous dependency patterns, often before a particular full deadlock interleaving happens. Its reasoning depends on the locking paths exercised and its model; a quiet test run does not establish that every lifetime race or unexecuted path is safe. Assertions about required lock ownership help make the contract visible at helper boundaries.

Once correctness is established, measure contention and hold time separately. A long wait can come from a long owner hold, many short contenders, preemption of an owner where permitted, or unrelated scheduling delay. Lock statistics and targeted traces help separate those causes, but instrumentation itself adds cost. Consider a design with one short shared update and expensive work outside the lock rather than assuming a faster primitive fixes excessive serialization. Per-CPU state can reduce shared contention, yet aggregating it and accessing it safely introduce migration and lifetime questions. Keep the model explicit: a throughput bound from hold durations is not a promise about fairness, priority inheritance, interrupt response, or tail latency on an actual configured kernel.

A serialized bottleneck has a measurable floor

Four successful critical sections use one exclusive lock. Their measured hold durations are 3, 5, 4, and 8 microseconds. Assume they do not overlap. A particular contender begins waiting when a holder has 6 microseconds of protected work remaining; ignore all other overhead for that lower-bound calculation.

  1. Add the exclusive hold durations: 3 + 5 + 4 + 8 = 20 microseconds. No execution can overlap these four holds under the same exclusive lock.

  2. The serialized elapsed interval is at least 20 microseconds; real intervals can be longer because of acquisition overhead, gaps, or scheduling effects.

  3. The specified contender cannot acquire before the present holder releases, so its wait has a lower bound of 6 microseconds. This does not guarantee it acquires immediately afterward or establish a general worst-case bound.

Tserialized≥20 μs,Wcontender≥6 μsT_{\mathrm{serialized}}\geq20\ \mu\mathrm{s},\qquad W_{\mathrm{contender}}\geq6\ \mu\mathrm{s}
These are lower bounds, not predicted latency distributions or hard real-time response guarantees.↔ Scroll wide equations horizontally.
The result

Total protected execution requires at least 20 microseconds of serialized time, and the particular wait is at least 6 microseconds under the stated assumptions.

Explore the model

Interactive lab

Interactive lab · queue

Explore pressure on a serialized service

Interpret arrival λ as hypothetical critical-section requests and service μ as assumed processing capacity, in requests per second. Compare separate fixed-rate runs at λ=4, μ=2 and λ=2, μ=4. For each: Reset, re-set both rates, predict the direction from four requests, then Play or Step. Editing λ retains elapsed time; it does not drain the previous run's accumulated backlog under a live rate change.

Explore pressure on a serialized serviceFluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Current readings: Fluid backlog: 4; Net rate · λ − μ: −1 /s; M/M/1 mean residence: 1 s. All parameters are adjustable with the labeled controls below.02.557.51001.534.56Elapsed time · sFluid backlogλ < μ
Fluid backlogCurrent time
Fluid backlog
4
Net rate · λ − μ
−1 /s
M/M/1 mean residence
1 s
t = 0 s

Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forThe fluid backlog is an analogy for pressure at one serialized resource. It does not model a spinlock, CPU scheduling, interrupt masking, fairness, or PREEMPT_RT; the separate M/M/1 mean residence time requires Poisson arrivals and exponential service in its stable regime.

Check your understanding

Which statement correctly distinguishes lock behavior on PREEMPT_RT kernels?

Try it yourself

A shared lock protects 100 operations, each with a 2-microsecond hold. Ignoring all overhead, what is the minimum total serialized hold time? Does splitting the data into two locks automatically prove correctness or halve observed latency?

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Add exclusive hold times first. Then distinguish a possible performance redesign from proof of the invariants spanning both partitions.

Compare with the solution

The holds require 200 microseconds in total under the one lock. Two locks might permit overlap for independent state, but compound invariants, ordering, lifetimes, and actual load distribution must be re-established. Neither correctness nor a precise latency improvement follows just from the lock count.

∑i=11002 μs=200 μs\sum_{i=1}^{100}2\ \mu\mathrm{s}=200\ \mu\mathrm{s}
This arithmetic concerns protected execution under one exclusive lock and excludes acquisition and scheduling overhead.↔ Scroll wide equations horizontally.

Take it with you

  • Enumerate concurrent contexts and the invariant before choosing a lock.
  • Local preemption or interrupt control does not replace cross-CPU exclusion; atomic regions must not sleep.
  • PREEMPT_RT changes regular spinlock semantics, while ordering analysis and measured contention remain essential.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.