RCU, references, and safe object reclamation

Explain RCU publication, grace periods, references and deferred object reclamation.

14 min readChapter 26 of 168Intermediate → advanced
In this chapter

Before you start#

Read atomics and memory ordering, locks and sleeping, and tasks, fork, exit, and wait. The source baseline is upstream v6.12. This chapter's reader/writer timeline is supplied source-only reasoning; the optional observation reads configuration and proc information without changing RCU settings, loading a module or running rcutorture.

Learning goals#

Describe publication, read-side access, removal, grace period and reclamation as distinct phases. Follow actual task-list removal and deferred reference release in v6.12, and explain when a reader needs a reference beyond its RCU critical section. Distinguish ordinary RCU, SRCU and Tasks RCU contexts without treating a grace period as a global stop.

A pointer needs ordering and lifetime#

Read-copy-update, RCU, is a family of kernel synchronization mechanisms useful for read-mostly data. A common pattern initializes a new object, publishes a pointer to it, lets readers traverse under the appropriate read-side protocol, removes or replaces the old pointer, and delays reclamation until relevant pre-existing readers have passed through the required grace period. This is not one lock that serializes all readers and writers. Writers usually need their own lock or other serialization to prevent conflicting updates to a list or object.

rcu_assign_pointer publishes using the ordering and type-checking contract appropriate to an RCU pointer. rcu_dereference retrieves it with the corresponding compiler and dependency guarantees under an appropriate read-side context. rcu_read_lock and rcu_read_unlock delimit a regular RCU read-side critical section. Those primitives support a carefully designed protocol: they do not make a pointer valid before publication, make every field immutable, or automatically grant an owned reference after the reader exits. Read the RCU overview and the actual subsystem's rules.

A grace period waits for relevant read-side critical sections that were already in progress relative to that grace period. It is not a global stop of all threads and does not require that no reader anywhere ever begins while it runs. A later reader can proceed concurrently. If that reader can still obtain an old object through a permitted path, the design must account for it; one cannot merely assume every reader instantly sees the latest pointer. synchronize_rcu waits for a grace period in a suitable sleepable context. call_rcu arranges a callback after the required grace period rather than blocking the caller for that callback. The callback context and permitted operations are API-specific; do not assume it is a normal process-context worker that may sleep arbitrarily.

Source walkthrough#

Removal and deferred release
Removal and deferred release1. Reader to Task object: access under the applicable read-side protocol. 2. Updater to Task object: remove list link under writer synchronization. 3. Updater to RCU: queue callback after the relevant reference transition. 4. RCU to Reader: wait for relevant pre-existing readers. 5. RCU to Task object: deferred callback drops its task reference.ReaderUpdaterRCUTask objectaccess under the applicable read-side protocolremove list link under writersynchronizationqueue callback afterthe relevantreference transitionwait for relevant pre-existing readersdeferred callbackdrops its taskreference
  1. ReaderTask object

    access under the applicable read-side protocol

  2. UpdaterTask object

    remove list link under writer synchronization

  3. UpdaterRCU

    queue callback after the relevant reference transition

  4. RCUReader

    wait for relevant pre-existing readers

  5. RCUTask object

    deferred callback drops its task reference

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Reader → Task object: access under the applicable read-side protocol
  2. Updater → Task object: remove list link under writer synchronization
  3. Updater → RCU: queue callback after the relevant reference transition
  4. RCU → Reader: wait for relevant pre-existing readers
  5. RCU → Task object: deferred callback drops its task reference
Source map. A conditional task-list source map, not a captured grace period. Other task references and paths also govern lifetime.

The following real list operation appears in __unhash_process in v6.12 kernel/exit.c (SPDX GPL-2.0-only; surrounding PID and group-death branches omitted):

c
list_del_rcu(&p->tasks);

This removes the task's list link using an RCU-aware list operation on the applicable group-death path. The surrounding exit code holds tasklist_lock for the update; RCU traversal does not itself serialize concurrent writers. Removal prevents future traversal through that link according to the list protocol but is not the same action as immediately freeing p. Readers that already obtained a pointer under the proper RCU protocol may still need the object to remain valid. The v6.12 RCU list definitions explain why deletion and deferred reclamation are separate.

The same kernel/exit.c contains put_task_struct_rcu_user, with this short excerpt (same tag and SPDX notice; surrounding function declaration omitted):

c
if (refcount_dec_and_test(&task->rcu_users))
	call_rcu(&task->rcu, delayed_put_task_struct);

The reference count is decremented and tested; only the transition indicated by the helper schedules the RCU callback. The callback later performs a deferred task-reference release in this implementation. This is an actual relationship between task lifetime, a reference count and RCU; it is not proof that every task_struct is freed immediately at the callback or that every list deletion uses this exact helper. Other task references and release paths matter. The callback's embedded rcu_head must remain valid until the callback executes, and the updater must obey the surrounding task-list and lifetime protocols.

In kernel/exit.c, v6.12, SPDX GPL-2.0-only, the RCU callback that put_task_struct_rcu_user schedules is shown whole:

c
static void delayed_put_task_struct(struct rcu_head *rhp)
{
	struct task_struct *tsk = container_of(rhp, struct task_struct, rcu);

	kprobe_flush_task(tsk);
	rethook_flush_task(tsk);
	perf_event_delayed_put(tsk);
	trace_sched_process_free(tsk);
	put_task_struct(tsk);
}

The callback runs after the grace period and calls put_task_struct, which is itself a reference drop. Whether memory is freed there depends on that function's own count, which the excerpt does not show. The callback also says nothing about the other task references that the release paths hold.

A corresponding generic list helper, list_del_rcu, unlinks an entry while preserving the traversal requirements documented for concurrent RCU readers. Its comments explicitly require a grace period or callback before freeing an entry that old readers could still access. Using ordinary list_del and immediately freeing the object because a writer lock is held can break readers that do not acquire that writer lock. The writer lock serializes updates; RCU supplies a separate read-side lifetime guarantee.

In v6.12 include/linux/rculist.h, SPDX GPL-2.0, the end of the list_del_rcu comment and the function read as follows (the start of the comment omitted):

c
 * Note that the caller is not permitted to immediately free
 * the newly deleted entry.  Instead, either synchronize_rcu()
 * or call_rcu() must be used to defer freeing until an RCU
 * grace period has elapsed.
 */
static inline void list_del_rcu(struct list_head *entry)
{
	__list_del_entry(entry);
	entry->prev = LIST_POISON2;
}

The comment is the rule the timeline below relies on: the entry cannot be freed immediately, and synchronize_rcu() or call_rcu() must come first. The body only unlinks the entry and poisons its prev pointer. It does not take the writer lock, which the caller must hold separately, and it says nothing about a reader that already holds a pointer to the containing object.

Readers, writers and reference acquisition#

A reader that needs an object only inside its protected read-side section can use the applicable RCU traversal and finish before unlocking. A reader that must retain it afterward usually needs an independently valid reference acquired while the object's memory and identity are stable. refcount_inc_not_zero can support some such designs: it fails if the count is already zero rather than reviving it. The v6.12 refcount interface explicitly says that this operation assumes object memory is already stable and does not itself provide general memory ordering. The caller must follow the subsystem's publication and object-initialization contract.

An unsuccessful increment is a normal branch: abandon the candidate, retry a lookup if the protocol permits, or report failure. Do not dereference the candidate after leaving the RCU section merely because its address was once non-null. Even a successful increment is not a substitute for validating the object's identity where storage can be reused. SLAB_TYPESAFE_BY_RCU delays freeing the slab page, not reuse of an individual object slot. A reader can encounter the same address holding a different object generation. Such designs need the allocation's documented ordering and a key or generation recheck after acquiring a reference. The v6.12 slab documentation specifically identifies refcount_inc_not_zero_acquire() for acquiring the reference before checking identity, and refcount_set_release() when initializing the new object's count after its fields. A plain refcount_inc_not_zero() has no acquire ordering: even when it succeeds, it does not by itself make the new generation's initialized fields visible for that identity check, nor prove it is the object originally sought.

Failed increments and reused slots
  1. Candidate under RCU

    Pointer read inside the read-side section; memory is stable only under the chosen protocol.

  2. Increment if nonzero

    refcount_inc_not_zero fails at zero; a failure means abandon or retry, never revive.

  3. Identity check

    With SLAB_TYPESAFE_BY_RCU the address can hold a new generation after reuse.

  4. Ordering and key check

    After taking the reference, verify the key or generation of the object actually found.

  5. Keep or release

    Keep the reference only if every check passed; otherwise release it and do not reuse the raw pointer.

Conceptual model. Conceptual model of conditional reference acquisition in Linux v6.12; the exact allocation and ordering rules depend on the subsystem.

RCU protects a reader's access from a particular reclamation protocol; it does not automatically protect mutable fields from concurrent writers. A field might require a lock, an atomic access, an immutable replacement or another documented mechanism. An RCU-protected list traversal and a refcount-protected object after lookup can be combined, but each guarantee has a separate start and end. Conversely, synchronize_rcu does not wait for arbitrary references held after readers have left their critical sections. A final free must respect both the grace-period and reference-count conditions chosen by the design.

A reader A, updater B, reader C timeline#

The following is a hypothetical design, not a claim about one universal kernel object. Initially a registry publishes old object O and owns one reference to it. Reader A enters the proper RCU read-side section, obtains O and successfully takes a separate reference: O now has two references. Updater B serializes against other writers, publishes a new object N and removes O from the registry. B queues an RCU callback that will drop the registry's reference to O after the required grace period. B does not free O immediately.

Reference counts across one grace period
Reference counts across one grace period1. Reader A to Registry: Read O inside the RCU section and take a reference (O count 2). 2. Updater B to Registry: Publish N and remove O from the registry. 3. Updater B to Grace period: call_rcu queues the registry's drop of its reference to O. 4. Reader C to Registry: Begin after the grace period starts and obtain N. 5. Grace period to Registry: Pre-existing readers pass; the callback drops the registry reference (O count 1). 6. Reader A to Registry: Leave the RCU section and drop A's reference; O can be freed under this design.Reader AUpdater BRegistryGrace periodReader CRead O inside the RCU section and take areference (O count 2)Publish N and removeO from the registrycall_rcu queues the registry's drop of itsreference to OBegin after the grace period starts andobtain NPre-existing readerspass; the callbackdrops the registryreference (O count1)Leave the RCU section and drop A'sreference; O can be freed under this design
  1. Reader ARegistry

    Read O inside the RCU section and take a reference (O count 2)

  2. Updater BRegistry

    Publish N and remove O from the registry

  3. Updater BGrace period

    call_rcu queues the registry's drop of its reference to O

  4. Reader CRegistry

    Begin after the grace period starts and obtain N

  5. Grace periodRegistry

    Pre-existing readers pass; the callback drops the registry reference (O count 1)

  6. Reader ARegistry

    Leave the RCU section and drop A's reference; O can be freed under this design

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Reader A → Registry: Read O inside the RCU section and take a reference (O count 2)
  2. Updater B → Registry: Publish N and remove O from the registry
  3. Updater B → Grace period: call_rcu queues the registry's drop of its reference to O
  4. Reader C → Registry: Begin after the grace period starts and obtain N
  5. Grace period → Registry: Pre-existing readers pass; the callback drops the registry reference (O count 1)
  6. Reader A → Registry: Leave the RCU section and drop A's reference; O can be freed under this design
Conceptual model. Hypothetical teaching design from this chapter, not a claim about one kernel object or a captured run; the counts belong to that design.

Reader C begins after the relevant grace period has started and obtains N in this example. C can run while the grace period progresses; it need not be stopped globally. The statement “C obtained N” is an assumption of this timeline, not a claim that every later unsynchronized reader must instantly observe the newest pointer. A leaves its RCU section but keeps its independent O reference. Once all readers that the grace period must wait for have passed their required quiescent state, B's callback can run and drop the registry reference: O's count becomes one, so O remains alive. Later A drops its reference; under this example's specified final-release protocol O can then be freed. N's lifetime is governed by its own registry and reader references.

Change one condition: A observes O but its refcount_inc_not_zero fails because final release has begun. A must not carry O beyond its RCU section as an owned reference. Or suppose A saves O's raw pointer, unlocks and later increments a counter at that address: the grace period can complete and O can be freed or reused before the increment, making the saved pointer unsafe. These are source-based race analyses, not captured runtime events. A real implementation may choose a different reference and callback ordering; prove its actual invariants rather than copying this hypothetical sequence blindly.

Flavors and execution contexts#

TREE_RCU is the scalable implementation used under relevant configurations; preemptible RCU changes how read-side sections and preempted readers are tracked. Quiescent states and grace-period progress depend on the flavor, CPU and task state. A CPU passing through a relevant quiescent state contributes to progress, but a grace period is not simply “wait for every CPU to run idle.” CPU hotplug, nohz operation and callback offloading can change which threads perform work. The exact implementation and configuration matter.

SRCU provides a sleepable RCU flavor with its own domain and reader tracking; it is not automatically interchangeable with ordinary rcu_read_lock. Tasks RCU variants track different kinds of task quiescent states for specialized uses. A function permitted to sleep in an SRCU read-side section is not thereby permitted to sleep inside every regular RCU read-side section. Calling a blocking grace-period wait from a context that cannot sleep is invalid, and waiting for a grace period from within a read-side critical section that the same grace period must wait for can deadlock. Use the flavor-specific documentation and API contract.

A stalled grace period can result from a CPU or task failing to pass a required quiescent state, long non-preemptible execution, interrupt or scheduling problems, or other causes. An RCU stall warning is evidence to investigate its timestamps, CPU/task state, configuration and logs; it does not uniquely identify one defective reader. Callback offloading can move callback execution to designated threads under configuration, but it does not erase the object's lifetime requirements. Do not turn on host rcutorture or alter RCU knobs to investigate a tutorial example.

Callback completion is a separate milestone#

Suppose an updater enqueues call_rcu for O and later initiates shutdown. Enqueueing the callback establishes neither that the grace period has already completed nor that the callback has executed. A subsystem that must unload code or release callback-owned storage needs the appropriate callback-draining mechanism and must first prevent new callbacks from being queued. A grace-period wait and a callback barrier answer different questions: waiting for readers is not the same as waiting for previously queued callbacks to finish. The exact rcu_barrier flavor and ownership protocol must match the callback API in use. Waiting while holding a lock the callback needs can deadlock just as with workqueue teardown.

A callback can also race with an independently held reference. If the callback's job is to drop one registry reference, that drop need not be the final one. If the design instead directly frees memory in the callback, it must prove that no independently retained references remain. The A/B/C example explicitly chooses the first design; copying its count transitions into an unrelated implementation without verifying its release function would be unsafe.

Practice#

Source-only route: reproduce the A/B/C timeline with a table for publication, A's successful reference acquisition, B's replacement, grace-period completion, callback and A's final put. Record O's reference counts 1 → 2 → 1 → 0 under the stated hypothetical design. Add a row for failed refcount_inc_not_zero and explain why an address reused under SLAB_TYPESAFE_BY_RCU needs identity revalidation. This is supplied proof-oriented reasoning, not a runtime capture.

Read-only host route: inspect only exposed configuration and proc metadata. These files can be missing or restricted; do not change host settings:

sh
uname -r
if [ -r /proc/config.gz ]; then zcat /proc/config.gz | grep -E 'CONFIG_(TREE_RCU|PREEMPT_RCU|RCU_NOCB_CPU|PROVE_RCU)=' || true
elif [ -r "/boot/config-$(uname -r)" ]; then grep -E 'CONFIG_(TREE_RCU|PREEMPT_RCU|RCU_NOCB_CPU|PROVE_RCU)=' "/boot/config-$(uname -r)" || true
else printf 'running-kernel configuration not exposed\n'; fi
if [ -r /sys/kernel/tracing/events/rcu ]; then ls /sys/kernel/tracing/events/rcu | head; else printf 'RCU trace events not visible\n'; fi

A visible config file may be distribution-provided and should be matched to the running kernel; a missing file or trace directory does not prove RCU is absent. Directory names do not prove events were enabled or captured. Paths and release names can disclose environment details, so review before sharing. If zcat is unavailable, report that limitation. Do not mount tracefs, enable events, run rcutorture or load a module on the host. No files are created and no cleanup is needed.

For a counterexample, a completed grace period does not prove A has dropped a reference acquired before unlocking. Deliver the table and this explanation, plus a labeled host visibility observation if performed. A guest test would require a bounded test module and recovery plan; none is required or claimed here.

Knowledge check#

  1. Why does list_del_rcu not permit immediate free of an object visible to earlier readers?
  2. What does a grace period wait for, and why need it not stop reader C globally?
  3. Why can A retain O after leaving its RCU section in the worked timeline?
  4. Why is refcount_inc_not_zero alone insufficient for a reused SLAB_TYPESAFE_BY_RCU slot?
  5. Why can synchronize_rcu inside a relevant read-side critical section deadlock?

Answers#

  1. A reader that began before removal may still hold the object. The updater must defer reclamation through the relevant grace period and respect other references.
  2. It waits for the relevant pre-existing read-side sections or quiescent-state conditions for that RCU flavor. New readers can proceed; this is not a global stop-the-world operation.
  3. In the hypothetical protocol A acquired a valid independent reference while the object was stable. The grace period does not by itself drop that reference.
  4. Storage can represent a different object generation at the same address. The design needs correct initialization ordering and identity revalidation after a safe reference acquisition.
  5. The grace-period wait can require the caller's own read-side section to end, while the caller is blocked waiting for that period. Context and flavor-specific rules must be followed.

Checkpoint#

Deliver the A/B/C reference table and failed-increment/reuse analysis, with optional host visibility notes. Pass when you distinguish publication ordering, writer serialization, grace-period protection and reference lifetime, and explain why C need not be globally stopped. If uncertain, retrace list_del_rcu, put_task_struct_rcu_user and the refcount contract and revise the timeline. This is not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.