Read-Copy-Update Fundamentals
Read-Copy-Update (RCU) is Linux’s synchronization mechanism for read-mostly data — structures read constantly from many CPUs but written rarely — and its defining property is that readers take no locks at all. A reader enters a critical section with
rcu_read_lock(), dereferences a shared pointer, and leaves withrcu_read_unlock(); on a typical non-preemptible kernel those two calls “compile to essentially nothing” (whatisRCU.rst, v6.12). Writers never modify data in place where a reader could see a half-written state. Instead they read the old version, copy it, update the copy, and publish the new version with a single atomic pointer store — and it is precisely “allowing concurrent reads while making a copy to perform an update” that “gives RCU (read-copy update) its name” (listRCU.rst, v6.12). The old version is not freed immediately; its reclamation is deferred until a grace period has elapsed — the moment at which every reader that could still hold a reference to the old version has finished. The result is the fundamental guarantee: a reader always sees either the old or the new version, never a torn mixture, and the old version stays valid for as long as any pre-existing reader might be using it.
This note is the overview and hub for RCU: the three fundamental mechanisms that make it work, the exact conditions under which the “zero-cost reader” claim is true, the family of wait-for-readers APIs and how to choose among them, the flavour consolidation of v4.20–v5.1 that turned three RCU APIs into one, and — importantly — the honest accounting of what RCU costs. It deliberately stays at the level of what RCU is and when to reach for it: the detailed mechanics of the read side live in RCU Read-Side Critical Sections, how grace periods are actually detected lives in RCU Grace Periods and Tree RCU, the memory-ordering “publish” handshake lives in The publish-subscribe Pattern in RCU, and the asynchronous-callback machinery in call_rcu and Deferred Reclamation. RCU is the crown jewel of the read-mostly section of Linux Kernel Synchronization MOC; if you remember one thing, remember that readers pay nothing and the cost is shifted entirely onto writers, who must publish carefully, wait out a grace period before freeing, and accept a multi-millisecond reclamation delay in exchange.
Version pin
Every code excerpt, default value, and configuration name in this note was read from the v6.12 source tree (a maintained long-term-support release; mainline had moved into the 7.x series by the time of writing, 2026-09-04). Where a fact is historical — notably the flavour consolidation — the specific release boundary is stated and was verified by reading the same header at several tags, not inferred from changelogs.
Mental Model
The cleanest way to think about RCU is as versioned data with deferred deletion, like a janitor who waits until a room is empty before clearing it out. Suppose a global pointer gbl_foo names the “current” version of some structure. Readers grab the pointer and use whatever version it pointed at when they grabbed it — they never coordinate with anyone. A writer who wants to change the structure does not edit it under the readers’ feet. It allocates a fresh copy, edits the copy, and then swings gbl_foo to point at the new copy in one atomic store. From that instant, new readers see the new version; readers who grabbed the old pointer a moment earlier keep happily using the old version, which is still intact. The old version can only be freed once every reader who might still be holding it has let go — and RCU’s job is to detect exactly when that “everyone who was reading is now done” moment (the grace period) has passed. Until then, the old version lingers, untouched, like a room the janitor refuses to clear while anyone is still inside.
The single most important picture in all of RCU is the timeline of readers against a grace period. It is what makes the otherwise slippery phrase “wait for pre-existing readers” concrete, and it is the picture to reconstruct from memory whenever an RCU question is confusing:
gantt title Readers, the removal point, and one grace period dateFormat X axisFormat %s section Updater Removal - last pointer to OLD is gone :crit, rem, 3, 4 Grace period - synchronize_rcu waits :active, gp, 4, 11 kfree of OLD is safe from here :done, fr, 11, 14 section CPU 0 Reader A - began BEFORE removal, holds OLD :a, 1, 6 Reader D - began AFTER removal, sees NEW :d, 7, 13 section CPU 1 Reader B - began BEFORE removal, holds OLD :b, 2, 9 section CPU 2 Reader C - spans the removal, straggler :c, 0, 11
One grace period drawn against the readers it must outlast. What it shows: the grace period opens at the moment the updater removes the last pointer to the old version (t=3) and cannot close until Readers A, B and C — every reader that was already running at that moment — have all finished. Reader C is the straggler, still running at t=10, so the grace period cannot close until t=11 and kfree() is safe only from there. Reader D started after the removal, so it can only have obtained the new pointer and the grace period does not wait for it. The insight to take: a grace period is defined by when readers started, not by what they read. RCU never inspects which pointer a reader is holding — it only needs the far cheaper fact “every read-side critical section that overlapped the removal has now ended”, which is why readers can get away with recording nothing at all. It also explains why RCU’s cost does not grow without bound: each update waits for the bounded set of readers in flight at one instant, not for all future readers.
A second view of the same event, this time following the code rather than the clock:
sequenceDiagram participant R1 as Reader A (pre-existing) participant W as Writer participant R2 as Reader B (new) participant GP as Grace-period machinery R1->>R1: rcu_read_lock() R1->>R1: p = rcu_dereference(gbl_foo) -- sees OLD W->>W: new = kmalloc(), copy old into new, edit the copy W->>W: rcu_assign_pointer(gbl_foo, new) -- PUBLISH R2->>R2: rcu_read_lock() R2->>R2: q = rcu_dereference(gbl_foo) -- sees NEW W->>GP: synchronize_rcu() -- wait for grace period R1->>R1: rcu_read_unlock() -- last pre-existing reader done GP-->>W: grace period elapsed W->>W: kfree(old) -- now safe
A single RCU update, as the code executes it. What it shows: Reader A grabbed the pointer before the publish, so it sees the old version and keeps using it safely even after the writer publishes the new one; Reader B, arriving after the publish, sees the new version. The writer cannot free the old version until synchronize_rcu() confirms every reader that could hold the old pointer has finished. The insight to take: correctness rests on a timing fact, not a lock — the grace period is the guarantee that no one still references the old version, so the writer never needs the readers to cooperate, signal, or take any lock. Note also what the writer does not do: it never touches the old structure’s contents, because a reader may be walking them right now.
The Three Fundamental Mechanisms
Paul McKenney — RCU’s author, and the maintainer of the Linux implementation — decomposes RCU into exactly three mechanisms, “the first being used for insertion, the second being used for deletion, and the third being used to allow readers to tolerate concurrent insertions and deletions” (McKenney, What is RCU, Fundamentally?, LWN 2007). Every RCU idiom in the kernel is some combination of these three. Learning them in this order is the fastest route to a working mental model, because each answers a question the previous one raises.
- Publish–subscribe (for insertion). How does a reader see a new structure without ever seeing it half-built? The writer initialises the structure privately, then publishes it with
rcu_assign_pointer(), which carries release semantics; the reader subscribes withrcu_dereference(), which carries the matching dependency ordering. This is the mechanism that makes concurrent insertion safe. - Wait for pre-existing readers (for deletion). Once a structure has been unlinked, how does the writer know nobody is still walking it? It waits out a grace period. This is the mechanism that makes concurrent deletion safe.
- Maintain multiple versions (for readers). What does a reader see while an update is in flight? Because the old version is not freed until the grace period ends, old and new versions coexist for a bounded window; a reader is guaranteed a self-consistent version, just not necessarily the newest one. This is the mechanism that lets readers tolerate concurrent updates without retrying.
flowchart TD subgraph INS["1 - Publish / subscribe (insertion)"] A1["Writer: kmalloc + initialise<br/>the object privately"] --> A2["rcu_assign_pointer(gp, p)<br/>= smp_store_release"] A2 --> A3["Reader: rcu_dereference(gp)<br/>dependency-ordered load"] A3 --> A4["Reader is guaranteed to see<br/>a FULLY initialised object"] end subgraph DEL["2 - Wait for pre-existing readers (deletion)"] B1["Writer: unlink<br/>(list_del_rcu / assign NULL)"] --> B2["Grace period:<br/>every reader running at B1 finishes"] B2 --> B3["kfree(old) is now safe -<br/>no reference can survive"] end subgraph VER["3 - Multiple versions (readers)"] C1["Old version still allocated<br/>until the grace period ends"] --> C2["Readers hold EITHER old OR new,<br/>never a torn mixture"] C2 --> C3["No reader ever retries,<br/>blocks, or writes shared state"] end INS --> VER DEL --> VER
RCU’s three fundamental mechanisms and how they compose. What it shows: insertion safety and deletion safety are solved by two different mechanisms — ordering (a release/dependency pair) for insertion, and time (a grace period) for deletion — and the third property, multiple coexisting versions, is not a separate feature but the consequence of deferring reclamation. The insight to take: when RCU code is wrong, it is almost always wrong in exactly one of these three boxes: a plain store instead of rcu_assign_pointer() (box 1), a kfree() that is not gated on a grace period (box 2), or an algorithm that cannot tolerate reading a slightly stale version (box 3). Diagnosing RCU bugs is largely the discipline of asking which box failed.
The third mechanism is the one that trips up newcomers, because it is a semantic constraint on the algorithm, not a coding rule. RCU hands the reader a consistent snapshot of one version; it does not give the reader an atomic view across several independent updates. whatisRCU.rst states the caveat plainly when discussing conversion from reader-writer locking: “the read-side and update-side critical sections can now run concurrently… if multiple independent list updates must be seen as a single atomic update, converting to RCU will require special care.” If your correctness argument needs “no update may occur between my two reads,” RCU is the wrong tool, or you need an explicit version counter on top of it.
The Triad in Code: Readers, Publish, Reclaim
Readers — rcu_read_lock() / rcu_read_unlock()
A reader brackets its access to RCU-protected data in an RCU read-side critical section:
int foo_get_a(void)
{
int retval;
rcu_read_lock();
retval = rcu_dereference(gbl_foo)->a;
rcu_read_unlock();
return retval;
}(whatisRCU.rst). The two bracketing calls are temporal markers, not mutual-exclusion locks: they tell the reclaimer “a reader is in progress here.” They do not exclude writers, do not exclude other readers, and do not spin or sleep. On a non-preemptible kernel, rcu_read_lock() reduces to disabling preemption and rcu_read_unlock() to re-enabling it — the v6.12 rcupdate.h non-PREEMPT_RCU implementation is literally:
static inline void __rcu_read_lock(void)
{
preempt_disable();
}
static inline void __rcu_read_unlock(void)
{
preempt_enable();
if (IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD))
rcu_read_unlock_strict();
}(rcupdate.h, v6.12). That is the whole “lock” — no atomic instruction, no shared write, no cache-line contention. The one rule the reader must obey is captured by the header’s own guidance: “don’t put anything in an rcu_read_lock() RCU read-side critical section that would block in a !PREEMPTION kernel.” In non-preemptible RCU “it is illegal to block while in an RCU read-side critical section”; preemptible RCU (CONFIG_PREEMPT_RCU) relaxes this to allow the section to be preempted, but explicit blocking is still illegal. (Readers that genuinely must sleep need Sleepable RCU and SRCU.) The deep mechanics — nesting, preemption interaction, lockdep checks — are in RCU Read-Side Critical Sections.
The “zero-cost reader” claim, stated precisely
“RCU readers are free” is the most-repeated and most-abused claim about RCU, so it is worth pinning down exactly when it holds. The kernel’s own requirements document is careful about this: “in non-preemptible environments, rcu_read_lock() and rcu_read_unlock() should have exactly zero overhead,” while “in preemptible environments, in the case where the RCU read-side critical section was not preempted… they should have minimal overhead. In particular, they should not contain atomic read-modify-write operations, memory-barrier instructions, preemption disabling, interrupt disabling, or backwards branches” (Requirements.rst, v6.12). Note the escape hatch in the second sentence: if the critical section was preempted, “rcu_read_unlock() may acquire spinlocks and disable interrupts.”
| Build | rcu_read_lock() compiles to | Cost | Caveat |
|---|---|---|---|
CONFIG_PREEMPTION=n (Tree RCU, non-preemptible) | preempt_disable(), which is itself a no-op — preempt_count does not exist | Literally zero instructions | Reader must never block; a long loop can stall grace periods for the whole system |
CONFIG_PREEMPTION=n + CONFIG_PREEMPT_COUNT=y (e.g. PREEMPT_VOLUNTARY with debug) | A per-CPU counter increment | One non-atomic, non-shared increment | No cross-CPU traffic; still not free at the instruction level |
CONFIG_PREEMPT_RCU=y (any preemptible build, incl. PREEMPT_RT) | rcu_preempt_read_enter() — an increment of current->rcu_read_lock_nesting | One increment of a task-local field | If the section was preempted, the unlock takes the slow path: rcu_read_unlock_special(), which may take rnp->lock and disable interrupts |
CONFIG_TINY_RCU (uniprocessor, !PREEMPT_RCU && !SMP) | Nothing; grace periods are vacuous | Zero | Only valid on genuinely single-CPU systems |
Verified against v6.12 include/linux/rcupdate.h, kernel/rcu/tree_plugin.h, and kernel/rcu/Kconfig. The PREEMPT_RCU symbol is default y if PREEMPTION and selects TREE_RCU; TINY_RCU is default y if !PREEMPT_RCU && !SMP.
The zero-cost-reader claim, decomposed by kernel configuration. What it shows: “compiles to nothing” is true, but only for the non-preemptible build; the preemptible build — which is what a desktop, an Android phone, or any PREEMPT_RT system runs — pays one task-local increment on entry and, in the uncommon case where the reader actually got preempted, a genuinely expensive slow path on exit. The insight to take: what is always true across every row is the property that actually matters for scaling — no row involves an atomic read-modify-write, a memory barrier, or a write to memory shared between CPUs. That, not the literal instruction count, is why RCU readers scale. The corollary from Requirements.rst is a real tuning rule: “it is better to nest an RCU read-side critical section within a preempt-disable region than vice versa,” because that removes the preempted-reader slow path entirely.
Publish — rcu_assign_pointer()
A writer publishes the new version through rcu_assign_pointer(), which does two jobs: it makes the pointer store, and it inserts the memory barrier that guarantees a reader who sees the new pointer also sees the fully-initialized structure it points at. The macro (v6.12) is:
#define rcu_assign_pointer(p, v) \
do { \
uintptr_t _r_a_p__v = (uintptr_t)(v); \
rcu_check_sparse(p, __rcu); \
\
if (__builtin_constant_p(v) && (_r_a_p__v) == (uintptr_t)NULL) \
WRITE_ONCE((p), (typeof(p))(_r_a_p__v)); \
else \
smp_store_release(&p, RCU_INITIALIZER((typeof(p))_r_a_p__v)); \
} while (0)The smp_store_release() is the load-bearing piece — a release store, in the sense walked through in Acquire Release and Fence Semantics. The header documents it as ensuring “that any concurrent RCU readers will see any prior initialization. Inserts memory barriers on architectures that require them (which is most of them), and also prevents the compiler from reordering the code that initializes the structure after the pointer assignment” (rcupdate.h). The special case in the macro is worth noticing: publishing a compile-time constant NULL needs only WRITE_ONCE(), because there is no pointed-to object whose initialisation could be reordered past the store.
On the reader side, rcu_dereference() is the matching “subscribe” half — it fetches the pointer with the dependency ordering that pairs with the release store. The hazard if you skip either half is not theoretical. McKenney’s LWN walk-through gives the two independent failure routes: the DEC Alpha CPU could genuinely reorder the dependent load ahead of the pointer load, and — on any architecture — “value-speculation compiler optimizations… guess the value of p, fetch p->a, p->b, and p->c, then fetch the actual value of p in order to check whether its guess was correct… This sort of optimization is quite aggressive, perhaps insanely so, but does actually occur in the context of profile-driven optimization” (LWN 262464). The full symbol-by-symbol treatment, including why RCU uses dependency ordering rather than a full acquire load and how the Linux Kernel Memory Model formalises it, is in The publish-subscribe Pattern in RCU; the underlying hardware reason a store can become visible out of order at all is Cache Coherence and the Store Buffer.
Reclaim — deferred until a grace period
The third leg is what makes the first two safe. Because a reader who grabbed the old pointer keeps using the old structure, the writer must not free it immediately. It must wait until the grace period — the interval after the removal during which every pre-existing reader finishes. The synchronous form blocks the writer:
void foo_update_a(int new_a)
{
struct foo *new_fp;
struct foo *old_fp;
new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);
spin_lock(&foo_mutex);
old_fp = rcu_dereference_protected(gbl_foo,
lockdep_is_held(&foo_mutex));
*new_fp = *old_fp; /* COPY */
new_fp->a = new_a; /* UPDATE the copy */
rcu_assign_pointer(gbl_foo, new_fp); /* PUBLISH */
spin_unlock(&foo_mutex);
synchronize_rcu(); /* wait out the grace period */
kfree(old_fp); /* now no reader can hold old_fp */
}(whatisRCU.rst). Four details in this eleven-line function carry the whole discipline, and each is a place real code gets it wrong:
- The writer still takes an ordinary lock. RCU removes reader-side locking, not writer-side.
checklist.rstopens its rules with exactly this: “RCU does allow readers to run (almost) naked, but writers must still use some sort of mutual exclusion, such as: a. locking, b. atomic operations, or c. restricting updates to a single task.” rcu_dereference_protected(), notrcu_dereference(). The writer is not in a read-side critical section; it is protected byfoo_mutex. The_protectedvariant takes a lockdep expression and, in aCONFIG_PROVE_RCUbuild, asserts that the claimed lock really is held — turning a documentation comment into a runtime check.rcu_assign_pointer()happens inside the lock,synchronize_rcu()outside it. Waiting for a grace period while holding a spinlock is a way to hang the machine: other CPUs spinning on that lock cannot reach a quiescent state, so the grace period the holder is waiting for can never end.- The
kfree()is the last statement, gated on the wait. This is the whole point; freeing before the grace period is a use-after-free that, perlistRCU.rst, makes “concurrent readers fail spectacularly.”
When the writer cannot block — it is holding other locks, or it is a hot path that must not stall — it registers an asynchronous callback with call_rcu() instead, which “invokes func(head) after a grace period has elapsed. This invocation might happen from either softirq or process context, so the function is not permitted to block.” That machinery is covered in call_rcu and Deferred Reclamation. Either way the writer splits its work into the documented two phases: a removal phase that “can run concurrently with readers,” and a reclamation phase that “must not start until readers no longer hold references.”
Why a Grace Period Means “All Readers Done”
The grace period is the conceptual heart, and the classic (toy) implementation explains it with disarming simplicity. The rule that readers may not block inside a critical section has a consequence: if a CPU performs a context switch, it cannot have been in the middle of an RCU read-side critical section — it must have completed every critical section it had started. So in classic RCU a grace period is simply “wait until every CPU has gone through at least one context switch.” The toy synchronize_rcu() literally schedules itself onto each CPU in turn:
void synchronize_rcu(void)
{
int cpu;
for_each_possible_cpu(cpu)
run_on(cpu);
}The reasoning, verbatim: “Remember that it is illegal to block while in an RCU read-side critical section. Therefore, if a given CPU executes a context switch, we know that it must have completed all preceding RCU read-side critical sections” (whatisRCU.rst). A point in a CPU’s execution where it is provably not in a read-side critical section is called a quiescent state; a grace period ends once every CPU has passed through a quiescent state since the grace period began.
The crucial and often-missed subtlety is that what counts as a quiescent state is exactly what distinguishes one RCU flavour from another. The flavours are not different algorithms; they are the same algorithm parameterised by “which events prove a reader is not running”:
| Flavour (v6.12) | Read-side marker | Events that count as a quiescent state |
|---|---|---|
RCU, non-preemptible (PREEMPTION=n) | rcu_read_lock() → preempt_disable() | Context switch, idle loop, user-mode execution, offline CPU, scheduling-clock interrupt taken outside a reader |
RCU, preemptible (PREEMPT_RCU=y) | rcu_read_lock() → increment current->rcu_read_lock_nesting | The same events, plus the requirement that every task blocked inside a reader (queued on the rcu_node’s ->blkd_tasks list) has exited its outermost critical section |
| RCU-bh (historical; read side survives) | rcu_read_lock_bh() → local_bh_disable() | Historically added “the transition from one type of softirq processing to another” as a quiescent state, so grace periods could end even on a CPU permanently in softirq |
| RCU-sched (historical; read side survives) | rcu_read_lock_sched() → preempt_disable() | Classic RCU’s set; notably a grace period also waits out pre-existing interrupt and NMI handlers |
| SRCU | srcu_read_lock(&sp) returns an index | Per-domain, per-CPU counters are sampled; there is no “event” — readers explicitly increment and decrement, which is why they may sleep |
| RCU-Tasks | none (any code that does not voluntarily context-switch) | A voluntary context switch, idle, or user-mode execution by every task |
| RCU-Tasks-Trace | rcu_read_lock_trace() | Per-task state sampled with IPIs and memory barriers; readers may sleep |
Quiescent state, by flavour. What it shows: the grace-period machinery is one mechanism; each flavour is a different answer to “what observable event proves this CPU (or task) is not inside a reader of my kind?” The insight to take: this is why the flavours had to exist at all before v4.20 — RCU-bh was created because a CPU pinned in softirq under a network denial-of-service flood never context-switches, so classic RCU’s grace periods “could never end. The result was an out-of-memory condition and a system hang” (Requirements.rst). It is also why SRCU is structurally different rather than merely a tuning: sleeping readers make event-based inference impossible, so SRCU pays for explicit counters instead.
Real Linux does not literally migrate a task to every CPU. Production Tree RCU organizes per-CPU quiescent-state reporting into a combining tree so that detection scales to hundreds of cores without every CPU contending on one lock; the tree’s fanout is RCU_FANOUT (default 64 on 64-bit) with a leaf fanout RCU_FANOUT_LEAF (default 16), both from kernel/rcu/Kconfig v6.12. The reporting path a single CPU walks looks like this:
stateDiagram-v2 [*] --> NeedsQS: new grace period starts;<br/>this CPU's bit is set in the leaf qsmask NeedsQS --> NeedsQS: CPU running in a reader<br/>(no QS can be reported) NeedsQS --> QSObserved: context switch / idle /<br/>user mode / tick outside a reader QSObserved --> BlockedTaskWait: under PREEMPT_RCU a reader was<br/>preempted and queued on the blocked-tasks list BlockedTaskWait --> QSObserved: last pre-existing blocked reader<br/>finishes its outermost reader QSObserved --> ReportedToLeaf: rcu_report_qs_rdp clears<br/>this CPU's bit in the leaf qsmask ReportedToLeaf --> PropagatingUp: leaf qsmask became zero ReportedToLeaf --> Waiting: siblings still outstanding Waiting --> PropagatingUp: last sibling reports PropagatingUp --> PropagatingUp: rcu_report_qs_rnp walks up<br/>the rcu_node tree PropagatingUp --> RootReached: root qsmask became zero RootReached --> [*]: rcu_report_qs_rsp wakes the<br/>GP kthread; grace period ends
A single CPU’s journey through one grace period, as a state machine. What it shows: a CPU does not tell RCU “I am done” directly; it clears one bit in its leaf rcu_node structure, and only when the last CPU under that node clears its bit does the clearing propagate one level up. Function names are from v6.12 kernel/rcu/tree.c: rcu_report_qs_rdp() reports one CPU, rcu_report_qs_rnp() “walks up the rcu_node hierarchy,” and rcu_report_qs_rsp() finally sets RCU_GP_FLAG_FQS and calls rcu_gp_kthread_wake(). The insight to take: the whole point of the tree is that the common case — one CPU reporting while its siblings have not yet — touches only one leaf lock, so N CPUs reporting cost N uncontended lock acquisitions rather than N contended ones on a single global lock. The BlockedTaskWait state is the preemptible-RCU addition and the one that makes real-time kernels harder: a preempted reader keeps the grace period open no matter which CPU it is eventually resumed on. The full walk is in Tree RCU and RCU Grace Periods.
One further subtlety the header is careful about: RCU callbacks “are permitted to run concurrently with new RCU read-side critical sections.” A grace period only waits for pre-existing readers — readers that started after the removal cannot possibly hold a reference to the old version, so the writer need not wait for them. Requirements.rst gives this its own heading, “Updaters Only Wait For Old Readers,” and it is the reason RCU’s overhead does not grow without bound.
Finally, a grace period carries a memory-ordering guarantee beyond the mere fact of waiting, and this is what makes it composable with the rest of the kernel’s synchronization. The v6.12 synchronize_rcu() kerneldoc spells it out: “when synchronize_rcu() returns, each CPU is guaranteed to have executed a full memory barrier since the end of its last RCU read-side critical section whose beginning preceded the call to synchronize_rcu()… Note that these guarantees include CPUs that are offline, idle, or executing in user mode” (kernel/rcu/tree.c, v6.12). In other words a grace period is not only a delay; it is a system-wide fence. Anything a reader did before its critical section ended is visible to the updater after synchronize_rcu() returns, without either side executing an explicit barrier.
Choosing How to Wait: synchronize_rcu(), call_rcu(), kfree_rcu()
RCU offers not one but a family of ways to say “do this after a grace period,” and picking the wrong one is a common source of latency bugs and out-of-memory incidents. The choice is driven by three questions in order: can this context block?, is the deferred work nothing more than a free?, and is the update rate bounded?
flowchart TD START["Updater has removed the old<br/>version and must reclaim it"] --> BLOCK{"Can this context block?<br/>(not holding a spinlock,<br/>not in interrupt/softirq)"} BLOCK -->|No| ASYNC{"Is the deferred work<br/>ONLY kfree/kvfree?"} BLOCK -->|Yes| LAT{"Is multi-millisecond<br/>latency acceptable here?"} LAT -->|Yes| SYNC["synchronize_rcu()<br/>simplest, self-limiting"] LAT -->|"No, and the caller<br/>is not latency-critical<br/>to the whole system"| EXP["synchronize_rcu_expedited()<br/>tens of microseconds,<br/>but IPIs every online CPU"] ASYNC -->|Yes| KFREE["kfree_rcu(ptr, rhf)<br/>no callback function needed"] ASYNC -->|"No - cleanup work<br/>beyond freeing"| CALL["call_rcu with your own callback<br/>callback runs in softirq;<br/>MUST NOT block"] KFREE --> RATE CALL --> RATE{"Is the update rate<br/>bounded?"} RATE -->|Yes| DONE["Done"] RATE -->|"No / attacker-influenced"| LIMIT["Add explicit rate limiting:<br/>count outstanding callbacks,<br/>stall on the update mutex,<br/>or periodic rcu_barrier()"] LIMIT --> DONE SYNC --> DONE EXP --> DONE
How to pick a deferral primitive. What it shows: the first branch is a hard correctness constraint (a blocking wait inside a spinlock or an interrupt handler is a bug, not a slow path), while every branch below it is a latency-versus-complexity trade. The insight to take: the branch most often skipped is the last one. checklist.rst calls out that synchronize_rcu() “automatically self-limits: if grace periods are delayed for whatever reason, then the synchronize_rcu() primitive will correspondingly delay updates. In contrast, code using call_rcu() should explicitly limit update rate in cases where grace periods are delayed, as failing to do so can result in excessive realtime latencies or even OOM conditions” (checklist.rst v6.12). Choosing call_rcu() for speed silently transfers a back-pressure responsibility onto you.
| Primitive | Blocks the caller? | Latency (order of magnitude) | Extra storage | When it is right |
|---|---|---|---|---|
synchronize_rcu() | Yes | Several milliseconds plus the longest reader | none | The default. Simplest code; automatically self-limiting; batches so that one grace period can serve “more than 1,000 separate invocations” |
synchronize_rcu_expedited() | Yes | A few tens of microseconds on small systems | none | Latency genuinely visible to userspace. Costs an IPI storm and “modest degradation of real-time latency on non-idle online CPUs” — roughly one scheduling-clock interrupt’s worth |
call_rcu() | No | Grace period, then softirq invocation | one rcu_head (2 pointers) in the object | Updater cannot block. You now own rate limiting |
kfree_rcu(ptr, rhf) | No | As call_rcu() | one rcu_head, at an offset < 4096 bytes into the object | The deferred work is literally just kfree(). Simplest of all — no callback to write, and no module-unload hazard |
kfree_rcu_mightsleep(ptr) | Rarely — falls back to synchronize_rcu() on allocation failure | Usually as call_rcu() | none — no rcu_head field at all | Size-critical structures where an rcu_head cannot be afforded and an occasional sleep is tolerable |
cond_synchronize_rcu(oldstate) / poll_state_synchronize_rcu() | Only if needed | Zero if a grace period already elapsed | one unsigned long cookie | Snapshot the grace-period counter with get_state_synchronize_rcu() at removal time, then wait only if one has not passed since |
rcu_barrier() | Yes | Until all already-queued callbacks have run | none | Module unload. It waits for callbacks, not for a grace period — a distinction covered in call_rcu and Deferred Reclamation |
All latency figures are the kernel’s own characterisations from Requirements.rst v6.12, not measurements taken here; the < 4096 offset limit and the kfree_rcu_mightsleep() fallback behaviour are read from include/linux/rcupdate.h v6.12.
The wait-to-finish family, side by side. What it shows: the four common primitives differ along two independent axes — whether the caller waits, and whether the object must carry an rcu_head. The insight to take: kfree_rcu() should be the reflex for the overwhelmingly common “unlink then free” case, and checklist.rst says so directly: it usually results “in even simpler code than does synchronize_rcu() without synchronize_rcu()’s multi-millisecond latency. So please take advantage of kfree_rcu()’s and kvfree_rcu()’s ‘fire and forget’ memory-freeing capabilities where it applies.” Reaching for call_rcu() when all you do is free is extra code, an extra failure mode (a module unloaded before its callback ran), and no benefit.
Two details in that table repay a closer look. First, kfree_rcu() in v6.12 is not a distinct implementation at all — it is #define kfree_rcu(ptr, rhf) kvfree_rcu_arg_2(ptr, rhf), sharing machinery with kvfree_rcu(); the 4095-byte offset ceiling exists because the implementation encodes the rcu_head’s offset rather than storing a separate pointer, and the header warns that “the allowable offset might decrease in the future” (rcupdate.h v6.12). Second, callback invocation can be delayed far beyond the grace period on purpose: CONFIG_RCU_LAZY (default n, and requiring rcu_nocbs=all) batches callbacks “to save power,” and the rcupdate_wait.h header cautions that with it enabled “the delay between the invocation of call_rcu() and that of the corresponding RCU callback can be multiple seconds.” Code that needs promptness must use call_rcu_hurry(). This interacts directly with callback offloading, covered in RCU and NOCB Offloaded Callbacks.
Flavours, and the Consolidation of v4.20
For most of RCU’s life in Linux there were three separate RCU implementations running side by side — vanilla RCU, RCU-bh, and RCU-sched — each with its own grace-period state machine, its own callback lists, and its own update-side API (synchronize_rcu() / synchronize_rcu_bh() / synchronize_sched(), and the matching call_rcu() / call_rcu_bh() / call_rcu_sched()). A great deal of older RCU writing, and a great many Stack Overflow answers, still describe this world. It is gone. Presenting three current update-side flavours is a staleness bug, and this section pins the boundary precisely.
Each flavour existed for a concrete reason, both of which are worth understanding because the problems have not gone away even though the separate APIs have:
- RCU-bh was created by Dipankar Sarma to survive “the network-based denial-of-service attacks researched by Robert Olsson. These attacks placed so much networking load on the system that some of the CPUs never exited softirq execution, which in turn prevented those CPUs from ever executing a context switch, which, in the RCU implementation of that time, prevented grace periods from ever ending. The result was an out-of-memory condition and a system hang” (Requirements.rst v6.12). RCU-bh’s fix was to add “the transition from one type of softirq processing to another” as an additional quiescent state, so grace periods could complete on a permanently-softirqing CPU.
- RCU-sched was created because preemptible RCU broke a property classic RCU had for free: “before preemptible RCU, waiting for an RCU grace period had the side effect of also waiting for all pre-existing interrupt and NMI handlers.” Code that relied on that — much of tracing, and anything patching code that interrupt handlers might be executing — needed a flavour that still made the guarantee.
The version boundary, verified by reading the source at each tag
Rather than trust a changelog, the boundary can be established by reading the same two files at successive tags and observing what exists:
| Tag | kernel/rcu/tree.c | include/linux/rcupdate.h update-side API | Meaning |
|---|---|---|---|
| v4.19 | Three state machines: rcu_bh_state, rcu_sched_state, and rcu_state_p | call_rcu_bh(), call_rcu_sched(), synchronize_sched() declared as real out-of-line functions | Three genuinely independent flavours |
| v4.20 | One struct rcu_state rcu_state — zero occurrences of rcu_bh_state or rcu_sched_state | The old names survive only as static inline aliases: synchronize_sched() → synchronize_rcu(), call_rcu_bh() → call_rcu() | The consolidation. One grace-period machine; old names are compatibility shims |
| v5.0 | unchanged | unchanged | The release from which rcupdate.h dates the guarantee that synchronize_rcu() “also wait[s] for regions of code with preemption disabled, including regions of code with interrupts or softirqs disabled” |
| v5.1 | unchanged | synchronize_sched(), synchronize_rcu_bh(), call_rcu_bh(): all removed, zero occurrences | The shims are deleted; source using them no longer compiles |
| v6.12 | one rcu_state | The names appear only inside comments, describing the history | Current state |
timeline title RCU update-side API consolidation, verified tag by tag v4.19 and earlier : three independent grace-period state machines : call_rcu, call_rcu_bh, call_rcu_sched : synchronize_rcu, synchronize_rcu_bh, synchronize_sched v4.20 : one struct rcu_state - the flavours merge : old update-side names become static inline aliases : checklist.rst - a given kernel implements only one RCU flavor v5.0 : synchronize_rcu documented to wait for preempt-disabled, irq-disabled and softirq-disabled regions too v5.1 : compatibility shims deleted outright : out-of-tree code using them stops compiling v6.12 LTS : one update-side API : rcu_read_lock_bh and rcu_read_lock_sched survive as read-side markers only
The consolidation, dated by source inspection rather than by changelog. What it shows: the change happened in two steps a couple of releases apart — a semantic merge in v4.20 (one state machine; old names redirected) followed by a source-compatibility break in v5.1 (names deleted). The insight to take: any RCU text that presents call_rcu_bh() or synchronize_sched() as things you might call is describing a kernel at least seven years out of date at the time of writing. But note the last row carefully — the read-side markers rcu_read_lock_bh() and rcu_read_lock_sched() were not removed and remain valid, useful, and lockdep-checked in v6.12. What was consolidated is which grace period you wait for, not how you mark a reader.
Uncertain
Verify: whether the consolidation should be dated v4.20 or v5.0. Reason: the two in-tree documents disagree in emphasis.
checklist.rst(v6.12) says “As of v4.20, a given kernel implements only one RCU flavor, which is RCU-sched forPREEMPTION=nand RCU-preempt forPREEMPTION=y”, while thercu_read_lock()kerneldoc inrcupdate.h(v6.12) says “In v5.0 and later kernels,synchronize_rcu()andcall_rcu()also wait for regions of code with preemption disabled”. Direct source inspection supports v4.20 for the mechanical merge (v4.19’stree.chasrcu_bh_stateandrcu_sched_state; v4.20’s has neither and declares a singlestruct rcu_state rcu_state), and v5.1 for the removal of the compatibility shims. The residual doubt is whether some part of the guarantee genuinely only became true in v5.0. To resolve: read the RCU pull requests for the v4.20 and v5.0 merge windows onlore.kernel.org, or bisectsynchronize_rcu()’s behaviour inkernel/rcu/tree_plugin.hbetween those tags. This note uses v4.20 for the merge, v5.1 for the removal, both directly verified. uncertain
What the flavours are today
In v6.12 there are five API families, and Requirements.rst lists them under the heading “Other RCU Flavors” with two of the five explicitly marked (Historical):
| Family | Status in v6.12 | Reader may sleep? | Reach for it when |
|---|---|---|---|
RCU (rcu_read_lock(), synchronize_rcu(), call_rcu()) | Current, the only update-side API | No | Almost always |
| RCU-bh | Read side current; update side removed in v5.1 | No | Never as a separate flavour. Use rcu_read_lock_bh() when you want softirq disabled and an RCU reader in one marker |
| RCU-sched | Read side current; update side removed in v5.1 | No | Never as a separate flavour. rcu_read_lock_sched() when you want preemption disabled and an RCU reader |
SRCU (srcu_read_lock(&sp)) | Current, structurally separate | Yes | Readers must block. Per-domain, so one stalled SRCU reader cannot stall unrelated subsystems |
| RCU-Tasks / -Rude / -Trace | Current, narrow | Tasks-Trace: yes | Tracing and BPF: waiting for tasks to leave a trampoline that is about to be freed |
Two facts make the surviving read-side markers less confusing than they look. First, rcu_read_lock_bh() is documented in v6.12 as “equivalent to rcu_read_lock(), but also disables softirqs. Note that anything else that disables softirqs can also serve as an RCU read-side critical section. However, please note that this equivalence applies only to v5.0 and later. Before v5.0, rcu_read_lock() and rcu_read_lock_bh() were unrelated” (rcupdate.h v6.12). Second — and this is the practically important consequence — anything that disables preemption, softirqs, or interrupts is now implicitly an RCU read-side critical section. checklist.rst spells out the pairing rule: if the updater uses call_rcu() or synchronize_rcu(), then readers may use “(1) rcu_read_lock() and rcu_read_unlock(), (2) any pair of primitives that disables and re-enables softirq… or (3) any pair of primitives that disables and re-enables preemption.”
That is not a curiosity; it is load-bearing in the fast paths of the kernel. The checklist’s own worked example is XDP, “which calls BPF programs from network-driver NAPI (softirq) context. BPF relies heavily on RCU protection for its data structures, but because the BPF program invocation happens entirely within a single local_bh_disable() section in a NAPI poll cycle, this usage is safe.” No rcu_read_lock() appears in that path at all — the softirq context is the read-side critical section. The checklist also warns what happens when this reasoning is done wrong: “Mixing things up will result in confusion and broken kernels, and has even resulted in an exploitable security issue.”
flowchart TD Q0["Which RCU family?"] --> Q1{"Will readers need<br/>to block / sleep?"} Q1 -->|Yes| Q2{"Is this tracing -<br/>ftrace or BPF?"} Q2 -->|Yes| TRACE["RCU-Tasks / RCU-Tasks-Rude /<br/>RCU-Tasks-Trace"] Q2 -->|No| SRCU["SRCU<br/>(own srcu_struct domain)"] Q1 -->|No| Q3{"Must readers be respected<br/>on CPUs deep in idle,<br/>entering/exiting user mode,<br/>or offline?"} Q3 -->|Yes| SRCU2["SRCU (strongly preferred)<br/>or RCU-Tasks-Trace"] Q3 -->|No| Q4{"Must NMI/hardirq handlers<br/>and preempt-disabled regions<br/>count as readers?"} Q4 -->|Yes| VAN1["Vanilla RCU update primitives<br/>(since v4.20 they already do)"] Q4 -->|No| Q5{"Must grace periods complete<br/>even if a CPU is monopolised<br/>by softirq? (DoS exposure)"} Q5 -->|Yes| BH["Disable softirq across readers<br/>e.g. rcu_read_lock_bh();<br/>vanilla update primitives"] Q5 -->|No| Q6{"Workload too update-intensive<br/>for normal RCU?"} Q6 -->|Yes| SLAB["SLAB_TYPESAFE_BY_RCU<br/>- but be careful"] Q6 -->|No| VAN2["Plain RCU"]
The flavour decision tree, transcribed from whatisRCU.rst’s own list (a)–(h). What it shows: almost every branch now terminates in “use the vanilla update primitives”; the only genuinely different destinations are SRCU (sleeping readers), the Tasks family (tracing), and SLAB_TYPESAFE_BY_RCU (update-heavy workloads). The insight to take: the questions that used to select an API now mostly select a read-side marker while the update side stays the same. Two branches deserve special notice: the idle/user-mode/offline branch, where whatisRCU.rst says “SRCU and RCU Tasks Trace are the only choices that will work for you, with SRCU being strongly preferred in almost all cases,” and the last one, where SLAB_TYPESAFE_BY_RCU weakens the guarantee from “this object stays alive” to only “this memory stays this type” — a genuinely different contract, discussed below.
Why RCU Scales: Nothing on the Read Side Bounces
The reason RCU is the kernel’s SMP scaling workhorse is mechanical, not magical. A conventional reader-writer lock — even one that admits many concurrent readers — requires every reader to write shared state: incrementing a reader count, or acquiring a per-CPU sequence, anything that tells the writer “a reader is here.” On a multi-core machine, that shared write means the cache line holding the lock word bounces between cores: each reader’s write puts the line into the Modified state on that core and invalidates every other core’s cached copy, forcing a coherence transaction on the next reader. As the number of cores rises, the cache-line ping-pong, not the actual work, dominates — readers spend their time fighting over a cache line nobody is even contending for semantically. The hardware mechanism is Cache Coherence and the Store Buffer; the important point here is that it is triggered by any write to a shared line, contended or not.
flowchart LR subgraph RW["Reader-writer lock: every reader WRITES"] direction TB C0["CPU 0 reader<br/>atomic_inc on the shared reader count"] -->|"line becomes Modified on CPU 0"| L1["Lock cache line"] C1["CPU 1 reader<br/>atomic_inc on the shared reader count"] -->|"invalidate CPU 0,<br/>fetch, modify"| L1 C2["CPU 2 reader<br/>atomic_inc on the shared reader count"] -->|"invalidate CPU 1,<br/>fetch, modify"| L1 L1 --> COST["N readers = N coherence<br/>round trips on ONE line"] end subgraph RCUS["RCU: every reader writes NOTHING shared"] direction TB D0["CPU 0 reader<br/>preempt_disable()"] --> P0["per-CPU state on CPU 0"] D1["CPU 1 reader<br/>preempt_disable()"] --> P1["per-CPU state on CPU 1"] D2["CPU 2 reader<br/>preempt_disable()"] --> P2["per-CPU state on CPU 2"] P0 --> GAIN["N readers = ZERO<br/>cross-CPU traffic"] P1 --> GAIN P2 --> GAIN end
Why the read side scales, drawn as coherence traffic. What it shows: the difference is not that RCU’s readers are cheaper per instruction but that they touch different cache lines from each other. Reader-writer-lock readers all hammer one line; RCU readers touch only lines private to their own CPU, so adding cores adds no traffic at all. The insight to take: this is why RCU’s advantage grows with core count while a reader-writer lock’s shrinks — and why a microbenchmark on two cores will badly understate the difference. The kernel documentation states the payoff directly: concurrent RCU readers “can dispense with the atomic operations, memory barriers, and communications cache misses that are so expensive on present-day SMP computer systems, even in absence of lock contention” — note “even in absence of lock contention,” which is exactly the point.
The atomic-pointer publish is what makes the read side’s silence possible: whatisRCU.rst observes that “writes to single aligned pointers are atomic on modern CPUs, allowing atomic insertion, removal, and replacement of data items in a linked structure without disrupting readers.” The cost is real but it is paid by writers (allocate, copy, wait a grace period), and RCU is only the right tool when reads vastly outnumber writes so that the read-side savings dominate. rcu.rst puts the same claim from the reader’s side: RCU readers “need not acquire any locks, perform any atomic instructions, write to shared memory, or (on CPUs other than Alpha) execute any memory barriers” (rcu.rst v6.12). The Alpha exception is the historical curiosity that shaped rcu_dereference(); see The publish-subscribe Pattern in RCU.
Two Analogies, and When Each One Fits
whatisRCU.rst offers two different ways to think about RCU, and the reason it offers two is that neither alone covers the ways RCU is used. Getting the right analogy in your head for the problem at hand is most of the battle.
Analogy 1 — RCU as a reader-writer lock
The most common use of RCU “is analogous to reader-writer locking,” and the conversion is startlingly mechanical:
| Reader-writer lock version | RCU version | Why |
|---|---|---|
rwlock_t listmutex; | spinlock_t listmutex; | Readers no longer take the lock at all, so a plain exclusive lock suffices for writers |
read_lock(&listmutex); | rcu_read_lock(); | Temporal marker replaces exclusion |
list_for_each_entry(...) | list_for_each_entry_rcu(...) | Adds the dependency-ordered loads on the reader side |
write_lock(&listmutex); | spin_lock(&listmutex); | Writer-side exclusion is unchanged in spirit |
list_del(&p->list); | list_del_rcu(&p->list); | Omits pointer poisoning, which would break concurrent readers |
write_unlock(); kfree(p); | spin_unlock(); synchronize_rcu(); kfree(p); | The one new line: reclamation is deferred |
The rwlock-to-RCU conversion, line for line, from whatisRCU.rst §6. What it shows: structurally, converting a read-mostly list from rwlock_t to RCU changes five lines and adds one. The insight to take: the diff being small is exactly what makes the conversion dangerous. Two semantics changed invisibly. First, “the read-side and update-side critical sections can now run concurrently… if multiple independent list updates must be seen as a single atomic update, converting to RCU will require special care.” Second, “the presence of synchronize_rcu() means that the RCU version of delete() can now block” — so a delete() that was previously callable from atomic context no longer is, unless you switch to call_rcu() or kfree_rcu().
Analogy 2 — RCU as a reference count on everything
The reader-writer analogy fails for a large class of RCU uses, so whatisRCU.rst supplies a second: “another helpful analogy considers RCU an effective reference count on everything which is protected by RCU.” Between rcu_read_lock() and rcu_read_unlock(), “any reference taken with rcu_dereference() on a pointer marked as __rcu can be treated as though a reference-count on that object has been temporarily increased.”
The crucial qualifier is what that pseudo-reference protects: “a reference count typically does not prevent the referenced object’s values from changing, but does prevent changes to type — particularly the gross change of type that happens when that object’s memory is freed and re-allocated for some other purpose.” So an RCU reference guarantees the object is still an object of that type, which is enough that “spinlocks can still be safely locked, normal reference counters can be safely manipulated, and __rcu pointers can be safely dereferenced.” It does not guarantee the fields hold the values you read a moment ago.
This is why the standard idiom for upgrading an RCU reference into a long-lived one is kref_get_unless_zero() — the unless_zero handles the race where the object is mid-teardown — and why RCU-protected objects that use kref must run their finalizer from a call_rcu() callback, after “all remaining globally visible pointer[s] to the object have been changed.”
The analogy is pushed to its limit by SLAB_TYPESAFE_BY_RCU (renamed from SLAB_DESTROY_BY_RCU), a slab-cache flag that reuses freed objects immediately while deferring only the page return to the allocator. With it, “RCU operations may yield a reference to an object from such a cache that has been concurrently freed and the memory reallocated to a completely different object, though of the same type. In this case RCU doesn’t even protect the identity of the object from changing, only its type.” The reader must therefore re-validate identity after acquiring a reference. There is a specific trap here worth memorising: “it is tempting to simply acquire the spinlock without first taking the reference, but unfortunately any spinlock in a SLAB_TYPESAFE_BY_RCU object must be initialized after each and every call to kmem_cache_alloc(), which renders reference-free spinlock acquisition completely unsafe.”
flowchart TD START["What am I reasoning about?"] --> SCALE{"Scale of the thing<br/>being protected"} SCALE -->|"A multi-part container:<br/>a list, a hash table, a tree"| RW["Use the reader-writer-lock analogy<br/>- concurrency while elements are<br/>added and removed"] SCALE -->|"An individual object<br/>accessed within a whole"| REF["Use the reference-count analogy<br/>- what may I safely do with<br/>this pointer right now?"] REF --> G1{"Is the slab cache<br/>SLAB_TYPESAFE_BY_RCU?"} G1 -->|No| G2["Object identity is stable<br/>for the whole critical section"] G1 -->|Yes| G3["Only the TYPE is stable.<br/>Take a reference, then<br/>re-check identity."]
Choosing between RCU’s two analogies. What it shows: the choice is governed by scale, exactly as whatisRCU.rst frames it — “the reader-writer lock analogy looks at larger multi-part objects such as a linked list… the reference-count analogy looks at the individual objects.” The insight to take: most confusion about “what does RCU actually protect?” comes from applying the container analogy to an individual-object question. RCU does not freeze an object’s contents; it keeps the memory from changing type. If your code needs the contents to be stable, you still need a lock inside the object, a sequence counter, or an atomic read.
Read-Mostly Use Cases
RCU shines wherever a structure is traversed constantly but mutated rarely, and the canonical kernel users are exactly those.
Linked lists are the flagship. “One of the most common uses of RCU is protecting read-mostly linked lists,” and “one big advantage of this approach is that all of the required memory ordering is provided by the list macros” (listRCU.rst v6.12). Readers traverse with list_for_each_entry_rcu() inside rcu_read_lock(); writers use list_add_rcu() / list_del_rcu(), which “add memory barriers that are needed on weakly ordered CPUs,” and list_del_rcu() deliberately “omits the pointer poisoning debug-assist code that would otherwise cause concurrent readers to fail spectacularly.” The guarantee given to a traversing reader is worth quoting exactly, because it is weaker than people assume: the reader “is guaranteed to see all of the elements which were added to the list before they acquired the rcu_read_lock() and are still on the list when they drop the rcu_read_unlock(). Elements which are added to, or removed from the list may or may not be seen. If the writer calls list_replace_rcu(), the reader may see either the old element or the new element; they will not see both, nor will they see neither.” See RCU-Protected Linked Lists.
The process list is the documentation’s own worked example: “a widely used usecase for RCU lists in the kernel is lockless iteration over all processes in the system. task_struct::tasks represents the list node that links all the processes.” Removal goes through release_task(), which takes write_lock(&tasklist_lock), calls list_del_rcu(&p->tasks), drops the lock, and then defers the actual teardown with call_rcu(&p->rcu, delayed_put_task_struct). Notice the shape: writer-side exclusive lock, RCU-aware unlink, deferred free — the same three-part pattern every time.
Network routing and other packet-path tables are read on every packet — millions per second per core — and updated only when routes change. RCU is what lets the forwarding fast path traverse them without reader-side locking, and the DoS-resistance requirement discussed above is not hypothetical: the entire RCU-bh flavour was created because a softirq-saturated CPU could otherwise wedge grace periods and OOM the machine. The networking subsystem even has its own alias, synchronize_net(), in the API list.
The dentry cache (dcache) of the VFS is walked on every path lookup. whatisRCU.rst uses it as its example of the case where “an entirely different thread [does] the reclamation,” which is exactly what the dcache does. RCU-walk path resolution traverses without per-dentry locking, falling back to ref-counted walking only when it must — the trade-off detailed in RCU-Walk and Ref-Walk.
BPF and XDP deserve a separate mention because the RCU usage there is invisible. As checklist.rst describes, BPF program invocation from NAPI softirq context “happens entirely within a single local_bh_disable() section,” and since v4.20 that section is an RCU read-side critical section. BPF map lookups are RCU-protected reads with no rcu_read_lock() anywhere in sight.
Module, notifier, and registration lists (audit rules, notifier chains, and similar) round out the pattern: read on hot paths, changed only at load and unload. listRCU.rst’s worked example of modifying an element in place — the audit rule update, where the writer copies the rule, edits the copy, and calls list_replace_rcu() — is the clearest small illustration in the tree of why the mechanism is called read-copy update.
The Honest Limits: What RCU Costs
RCU’s reputation for being free is earned on the read side and unearned everywhere else. The kernel’s own checklist opens not with a technique but with a gate:
“Is RCU being applied to a read-mostly situation? If the data structure is updated more than about 10 % of the time, then you should strongly consider some other approach, unless detailed performance measurements show that RCU is nonetheless the right tool for the job. Yes, RCU does reduce read-side overhead by increasing write-side overhead, which is exactly why normal uses of RCU will do much more reading than updating.” (checklist.rst v6.12)
The document then names the three legitimate exceptions to that 10 % rule, which are worth knowing because they are the cases where RCU is right despite being slower: where performance does not matter and RCU is simply the simpler implementation; where “the low real-time latency of RCU’s read-side primitives is critically important”; and where “RCU readers are used to prevent the ABA problem for lockless updates” — a “mildly counter-intuitive situation where rcu_read_lock() and rcu_read_unlock() are used to protect updates,” effectively borrowing a garbage collector’s simplifications for a lock-free algorithm.
Concretely, here is the bill:
| Cost | Magnitude (v6.12 documentation) | Consequence |
|---|---|---|
| Update-side copy | One allocation plus a structure copy per update | An in-place spin_lock(); x->field = v; spin_unlock(); is far cheaper. RCU updates are not “lock-free writes” |
| Grace-period latency | synchronize_rcu() “may therefore incur several milliseconds of latency in addition to the duration of the longest RCU read-side critical section” | Any update path a user can observe must not call it naively |
| Memory footprint | Freed memory stays allocated for at least a grace period; callbacks queue per CPU | A burst of updates holds a burst of memory. Uncapped, this is an OOM |
rcu_head per object | Two pointers, 2-byte aligned minimum (m68k sets that floor) | Non-trivial in size-critical structures. The page structure resorts to unions to fit it |
| Staleness | Readers may see a version that is one update old | Wrong for anything needing a linearisable read |
| Grace periods can be prevented | A looping reader, a CPU-bound RT task preempting a reader, or a stuck GP kthread | System-wide: RCU stalls, then OOM, then hang |
The batching that makes the multi-millisecond latency tolerable is worth understanding, because it explains a counter-intuitive benchmark result. Grace periods are shared: “it is not unusual for a single grace-period-wait operation to serve more than 1,000 separate invocations of synchronize_rcu(), thus amortizing the per-invocation overhead down to nearly zero” (Requirements.rst v6.12). So a thousand concurrent updaters do not cost a thousand grace periods; they cost roughly one. The corollary is that a single updater in an otherwise idle system sees the worst possible latency, because there is nobody to share a grace period with. synchronize_rcu_expedited() exists for that case, “reducing the grace-period latency down to a few tens of microseconds on small systems, at least in cases where the RCU read-side critical sections are short,” in exchange for degrading real-time latency on every non-idle online CPU by “roughly the same latency degradation as a scheduling-clock interrupt” — see Expedited Grace Periods.
The memory-footprint cost is the one that produces incidents rather than merely slow code. checklist.rst enumerates four ways to restore back-pressure when using call_rcu(): cap the number of elements awaiting free and stall updates on the update-side mutex when the cap is hit (explicitly not a spinlock — “other CPUs spinning on the lock could prevent the grace period from ever ending”); limit the update rate structurally; restrict updates to a trusted user; or “periodically invoke rcu_barrier(), permitting a limited number of updates per grace period.” And even with those, the ceiling is not absolute: “although these primitives do take action to avoid memory exhaustion when any given CPU has too many callbacks, a determined user or administrator can still exhaust memory. This is especially the case if a system with a large number of CPUs has been configured to offload all of its RCU callbacks onto a single CPU” — a direct warning about misconfigured rcu_nocbs, discussed in RCU and NOCB Offloaded Callbacks.
Failure Modes and Common Misunderstandings
Conceptual errors
“RCU is a lock.” It is not. rcu_read_lock() provides no mutual exclusion — two readers, or a reader and a writer, run fully concurrently. The word “lock” names a critical-section boundary, not exclusion. Treating it as a lock (expecting it to serialize writers, for instance) is the most common conceptual error, and it is why Requirements.rst has a whole section titled “Readers Do Not Exclude Updaters.”
“RCU protects the data.” RCU protects the reader’s ability to keep using the version it already has until a grace period passes. It says nothing about two writers racing — writers must still serialize with a real lock. Under the reference-count analogy it is even narrower: RCU prevents a change of type, not a change of value.
“Readers can sleep.” Not in classic or Tree RCU — blocking inside rcu_read_lock() on a non-preemptible kernel is illegal and can stall grace periods system-wide. If a reader must sleep, that is a different flavour: Sleepable RCU and SRCU.
“Free immediately after unpublishing.” The whole point is that you cannot. rcu_assign_pointer() removes the structure from new readers’ view, but pre-existing readers still hold it. Always gate the kfree() behind synchronize_rcu(), call_rcu(), or kfree_rcu().
“A grace period partitions the timeline.” Requirements.rst has two symmetrical sections — “Grace Periods Don’t Partition Read-Side Critical Sections” and “Read-Side Critical Sections Don’t Partition Grace Periods” — precisely because the natural intuition here is wrong. A grace period is not a global barrier that all readers are on one side or the other of; it guarantees only what it says, that readers pre-existing at its start have ended by its end.
The stall: RCU’s signature production failure
The failure that shows up in dmesg is the RCU CPU stall warning, printed when “a given RCU grace period extends more than the specified number of seconds.” The threshold is CONFIG_RCU_CPU_STALL_TIMEOUT, whose v6.12 default is 21 seconds (range 3–300, per kernel/rcu/Kconfig.debug), runtime-adjustable through /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout. A splat looks like:
INFO: rcu_sched detected stalls on CPUs/tasks:
2-...: (3 GPs behind) idle=06c/0/0 softirq=1453/1455 fqs=0
16-...: (0 ticks this GP) idle=81c/0/0 softirq=764/764 fqs=0
(detected by 32, t=2603 jiffies, g=7075, q=625)
Read it as follows (stallwarn.rst v6.12): CPU 32 is the detector, not the culprit; CPUs 2 and 16 are blocking the grace period. “(3 GPs behind)” means CPU 2 “has not interacted with the RCU core for the past three grace periods,” while “(0 ticks this GP)” means CPU 16 “has not taken any scheduling-clock interrupts during the current stalled grace period” — two different diseases with two different treatments. In a PREEMPT_RCU build the blocker may be a task rather than a CPU, printed as a PID such as P3421, which is the fingerprint of a preempted reader.
flowchart TD S["RCU CPU stall warning in dmesg"] --> W{"Who is named?"} W -->|"A task PID, e.g. P3421"| T["A preempted reader is stuck.<br/>Usual cause: a CPU-bound RT task<br/>preempted a low-priority reader<br/>that cannot migrate"] W -->|"A CPU number"| C{"What does the annotation say?"} C -->|"'(0 ticks this GP)'"| TICK["That CPU is not taking the<br/>scheduling-clock interrupt:<br/>looping with interrupts disabled,<br/>or a nohz_full misconfiguration"] C -->|"'(N GPs behind)'"| BEHIND["That CPU has not reached a<br/>quiescent state: looping in a reader,<br/>with preemption or BH disabled,<br/>or without any cond_resched()"] W -->|"'All QSes seen' /<br/>'kthread starved for'"| KT["Every CPU reported, but the<br/>GP kthread itself never ran.<br/>Scheduling problem, not an RCU reader"] T --> FIX1["Bound the reader; give the RT task<br/>a duty cycle; check CPU affinity"] TICK --> FIX2["Find the interrupts-disabled loop;<br/>check nohz_full / isolcpus setup"] BEHIND --> FIX3["Add cond_resched() to the loop,<br/>or shorten the critical section"] KT --> FIX4["Check RT priorities vs the<br/>rcu_preempt / rcuc kthreads"]
Triaging an RCU stall from the splat alone. What it shows: the three annotations in a stall message point at three genuinely different root causes, and the message identifies the detector first, which is the field most often misread as the culprit. The insight to take: an RCU stall is rarely an RCU bug. It is a report that somebody, somewhere, is not reaching a quiescent state — usually a loop that forgot cond_resched(), a real-time task starving a preempted reader, or a nohz_full CPU that has stopped taking ticks. stallwarn.rst lists the full cause set, including the memorable one that “a 115Kbaud serial console can be way too slow to keep up with boot-time message rates, and will frequently result in RCU CPU stall warning messages.” The consequence of an unfixed stall is not just a log line: with call_rcu() in play, callbacks accumulate and “eventually cause the system to run out of memory and hang.”
Expedited grace periods get their own splat format, e.g. INFO: rcu_sched detected expedited stalls on CPUs/tasks: { 7-... } 21119 jiffies s: 73 root: 0x2/., where the three dots after the CPU number encode online status now, online at grace-period start, and ever-online-since-boot, and the root: bitmask names which children of the root rcu_node are still blocking.
Debug options that catch RCU bugs before production
RCU misuse is unusually amenable to static and dynamic checking, and checklist.rst item 16 says to turn the checks on:
CONFIG_PROVE_RCUmakesrcu_dereference()and friends assert that the appropriate read-side critical section is actually held — “rcu_dereference(p): check for RCU read-side critical section”,rcu_dereference_bh(p)for RCU-bh,srcu_dereference(p, sp)for SRCU (lockdep.rst v6.12). This catches the single most common RCU bug: dereferencing an RCU-protected pointer outside any critical section.CONFIG_DEBUG_OBJECTS_RCU_HEADcatches double-call_rcu()on the samercu_headand callbacks queued on freed memory.- Sparse with
__rcuannotations —rcu_assign_pointer()invokesrcu_check_sparse(p, __rcu)— turns “this pointer must be dereferenced throughrcu_dereference()” into a compile-time type error. rcutortureis the in-tree stress harness for RCU itself (torture.rst v6.12).
Note the conservative fallback documented in lockdep.rst: the rcu_read_lock_held() family “will therefore return 1 if they aren’t certain (for example, if CONFIG_DEBUG_LOCK_ALLOC is not set). This prevents things like WARN_ON(!rcu_read_lock_held()) from giving false positives when lockdep is disabled.” In other words, these assertions only assert in a debug build — they are not a runtime safety net in production kernels.
Alternatives and When to Choose Them
| Mechanism | Reader cost | Reader may block? | Readers retry? | Writer cost | Choose it when |
|---|---|---|---|---|---|
| RCU | Zero shared writes | No (SRCU: yes) | Never | Copy + grace period before free | Read-mostly (< ~10 % updates), pointer-based, readers must be fast and must not retry |
rw_semaphore (Read-Write Semaphores) | Writes the reader count → cache-line bouncing | Yes | No | Blocks all readers | Readers must sleep and updates are not vanishingly rare |
seqlock (Sequence Locks and seqlock) | Two counter reads, no writes | No | Yes, on writer overlap | Never blocked by readers | Small fixed-size data, cheap to re-read, no pointers to chase — timekeeping is the archetype |
| Per-CPU data (Per-CPU Variables) | Zero — nothing is shared | Depends | No | Must aggregate across CPUs to read globally | The state is naturally partitionable per CPU |
| Plain spinlock/mutex | An atomic RMW on a shared line | Mutex: yes | No | Same | Reads and writes are roughly balanced, or the data is tiny and updated in place |
SLAB_TYPESAFE_BY_RCU | As RCU, plus identity re-check | No | Re-validate, not retry | No grace period per free | The workload is too update-intensive for plain RCU but the read path still must be lock-free |
| Hazard pointers (not in-tree; see perfbook §9.3) | A store plus a barrier or IPI per reference | No | No | Bounded memory | You need RCU-like deferral with a hard bound on unreclaimed memory |
Sibling read-mostly mechanisms compared on the axes that actually decide the choice. What it shows: RCU and seqlocks both give lock-free readers, but they differ on the axis that matters most in practice — a seqlock reader may have to retry, which is fine for reading a timestamp and catastrophic for walking a pointer-rich structure that may have been freed under you. The insight to take: the decision rule a kernel reviewer would apply is a conjunction, not a preference: read-mostly, pointer-based, readers must be fast and may not retry, and writers can tolerate a grace-period delay before reclamation → RCU. If reads and writes are balanced, RCU’s writer-side cost (copy plus grace period) makes it lose to a plain lock; the checklist’s ~10 % figure is the rough boundary.
A useful contrast the vault’s other notes fill in: RCU’s read-side cheapness rests on the same hardware facts that make Memory Barriers in the Linux Kernel necessary, and its publish step is a textbook release/acquire pair as described in Acquire Release and Fence Semantics. RCU does not avoid the memory model — it encapsulates it, which is why rcu_assign_pointer() and list_add_rcu() exist at all rather than leaving callers to place barriers by hand. listRCU.rst’s framing is the honest one: “one big advantage of this approach is that all of the required memory ordering is provided by the list macros.”
Production Notes
Tuning knobs that actually get used. Three boot parameters cover most production RCU tuning, all documented in Documentation/admin-guide/kernel-parameters.txt (v6.12):
rcu_nocbs=<cpu-list>moves callback invocation off the listed CPUs onto dedicated kthreads, which is the standard configuration for latency-isolated ornohz_fullCPUs. The caution fromchecklist.rstapplies: offloading all callbacks onto one CPU is a documented route to memory exhaustion. See RCU and NOCB Offloaded Callbacks.rcupdate.rcu_expedited=1makes normal grace periods use the expedited path, andrcupdate.rcu_normal=1forces the opposite and “overridesrcupdate.rcu_expedited.” The pair exists because boot is the one phase where grace-period latency dominates:rcupdate.rcu_normal_after_bootgives you expedited grace periods during boot and normal ones afterwards.rcutree.rcu_normal_wake_from_gp(also settable at runtime via/sys/module/rcutree/parameters/rcu_normal_wake_from_gp) changes which context performs the wakeups for normalsynchronize_rcu()waiters.
Where RCU came from, and why that matters for reading old material. RCU’s design and its Linux implementation are the work of Paul McKenney, whose 2004 dissertation Exploiting Deferred Destruction: An Analysis of Read-Copy-Update Techniques in Operating System Kernels (OGI School of Science & Engineering, Oregon Health & Science University) remains the fullest treatment of the technique’s origins in Sequent’s DYNIX/ptx. The living version of that material is his book Is Parallel Programming Hard, And, If So, What Can You Do About It? (kernel.org), whose Chapter 9 (“Deferred Processing”) places RCU alongside reference counting, hazard pointers, and sequence locking as four answers to the same question; the edition read for this note is v2026.06.21a. rcu.rst also notes the patent situation, which occasionally still surfaces in licensing discussions: “there are several known patents related to RCU… Of these, one was allowed to lapse by the assignee, and the others have been contributed to the Linux kernel under GPL. Many (but not all) have long since expired.” Userspace implementations exist under LGPL at liburcu.org.
Reading old RCU material safely. Because RCU’s API changed shape in v4.20/v5.1 and its documentation is unusually long-lived, the single most useful habit is to check the vintage of anything you read. Three tells that a document predates the consolidation: it presents call_rcu_bh() or call_rcu_sched() as callable; it describes “three flavours of RCU” without the word historical; or it says rcu_read_lock_bh() and rcu_read_lock() are unrelated (true before v5.0, false after). The in-tree documents are maintained in lockstep with the code and mark their own historical sections, which is why this note leans on them so heavily.
Uncertain
Verify: the LWN articles cited here (“What is RCU, Fundamentally?”, “What is RCU? Part 2: Usage”, “RCU part 3: the RCU API”) date from 2007–2008 and therefore describe the pre-consolidation, three-flavour world in their API sections. Reason: they are cited above only for the conceptual content (the three fundamental mechanisms, the publish/subscribe argument, the value-speculation hazard), which has not changed; their API inventories have. Additionally,
lwn.netreturned HTTP 429 “Blocked due to excessive requests” for further article fetches during this research, so the more recent LWN coverage of the flavour consolidation could not be retrieved and cited. To resolve: re-fetchlwn.netarticle listings for the RCU tag when not rate-limited, and preferDocumentation/RCU/for any API-level claim. uncertain
See Also
- RCU Read-Side Critical Sections — the read side in depth: nesting, preemptible RCU,
rcu_dereferencerules, lockdep checks - RCU Grace Periods — how the “all pre-existing readers done” moment is actually detected, quiescent state by quiescent state
- The publish-subscribe Pattern in RCU —
rcu_assign_pointer/rcu_dereferencememory ordering walked symbol-by-symbol - call_rcu and Deferred Reclamation — the asynchronous, non-blocking reclamation path and
rcu_barrier() - Tree RCU — the scalable, combining-tree grace-period implementation in production kernels
- Expedited Grace Periods — the microsecond-latency path and what it costs every other CPU
- RCU and NOCB Offloaded Callbacks —
rcu_nocbs, callback offloading, and CPU isolation - Sleepable RCU and SRCU — the flavour whose readers may block, and why it needs explicit counters
- RCU-Protected Linked Lists —
list_*_rcu()primitives and the exact traversal guarantee - RCU-Walk and Ref-Walk — the VFS dcache’s RCU-based path lookup and its fallback
- Acquire Release and Fence Semantics — the release store underneath
rcu_assign_pointer() - Cache Coherence and the Store Buffer — why a shared write bounces a cache line, and why RCU’s readers avoid it
- Memory Barriers in the Linux Kernel — the barrier vocabulary RCU encapsulates
- Sequence Locks and seqlock, Per-CPU Variables, Read-Write Semaphores, Static Keys and Code Patching — sibling read-mostly / lock-avoiding primitives
- Preemption Disabling and preempt_count, Kernel Preemption Models — what
rcu_read_lock()actually compiles to, and the config axis that decides it - The PREEMPT_RT Real-Time Kernel — why preemptible readers and RCU stalls interact badly under real-time priorities
- Linux Kernel Synchronization MOC — parent map; RCU is section E