In-Depth Analysis of the Linux RCU Mechanism: Principles and Practices
1 Introduction to RCU
In the world of concurrent programming within the Linux kernel, Read-Copy-Update (RCU) is an astonishing synchronization mechanism that addresses the performance issues of read-write locks in read-intensive scenarios. RCU was first implemented and promoted by Paul E. McKenney in the Linux kernel and has since become an indispensable part of the kernel’s core infrastructure. Unlike traditional lock-based synchronization mechanisms, RCU allows readers and writers to execute concurrently without locks, achieving completely lock-free read paths, which brings significant performance improvements on multi-core systems.
Essentially, RCU is a synchronization mechanism suitable for read-mostly, write-less scenarios, enabling efficient concurrent access to data through a “copy-update-replace” logic chain. Before we delve into the details, let’s consider a real-life analogy: imagine a library needing to update a frequently used reference book. The traditional method would involve closing the library for renovations (locking), which would prevent all readers from accessing it. In contrast, RCU’s approach is to first prepare a new revised edition (create a copy), and then atomically replace the old book with the new one at an appropriate moment (atomic pointer update). This way, readers arriving before the replacement continue to read the old version, while those arriving afterward automatically receive the new version, with neither party being blocked.
RCU’s applicable scenarios mainly feature the following characteristics: (1) data structures are primarily accessed via pointers; (2) read operations vastly outnumber update operations; (3) reader-side code cannot tolerate the overhead of locks; (4) update operations are relatively infrequent. In the Linux kernel, RCU is widely used to protect various data structures, such as process descriptor tables, the virtual file system (VFS) layer, and network protocol stacks.
From a broader perspective, RCU represents a shift in the design philosophy of synchronization primitives: from adversarial mutual exclusion to cooperative separation. Traditional locking mechanisms are based on the principle of mutual exclusion, where readers and writers compete against each other, leading to limited scalability. RCU adopts a separation strategy, allowing readers and writers to work in parallel for most of the time through clever memory management schemes. This design makes RCU perform exceptionally well in multi-core systems and large data processing, especially in modern server and cloud computing environments, where its value is further highlighted.
2 Core Principles of RCU
2.1 Basic Concepts and Everyday Analogies
The core idea of RCU can be summarized as **”read directly when reading, copy and modify when writing”**. Imagine a family bulletin board: there is a board that records the schedules of family members. When the mother needs to update the schedule, she does not stop others from viewing the board; instead, she first copies the current content onto a new sheet, makes modifications on the new sheet, and then, at a moment when no one is paying attention, replaces the old sheet with the new one. After the replacement, she does not immediately discard the old sheet but waits for a while to ensure that everyone who might have seen the old bulletin has finished before discarding it.
In this analogy, the “replacement moment” corresponds to the publishing operation in RCU, “waiting to ensure” corresponds to the grace period, and **”discarding the old sheet”** corresponds to reclamation. These three concepts form the core triangle of RCU.
From a technical perspective, the data protected by RCU is typically accessed via pointers. Readers access data through this pointer; updaters first create a copy of the data and modify the copy, then use atomic operations to update the pointer to point to the new data, and finally reclaim the old data at an appropriate time. The key is determining when it is safe to reclaim the old data – that is, when all possible readers that could access the old data have completed their access.
2.2 Reader-Side Principle Analysis
The reader-side operation is the simplest and most efficient part of RCU. In the Linux kernel, a typical RCU reader-side code looks like this:
rcu_read_lock();
/* Read protected data */
data = rcu_dereference(global_pointer);
/* Use data */
rcu_read_unlock();
Here, <span>rcu_read_lock()</span> and <span>rcu_read_unlock()</span><span> do not perform actual locking and unlocking operations like traditional locks. In a non-preemptive kernel, they may be completely empty operations. Their real purpose is to </span><strong><span>inform the compiler not to reorder instructions</span></strong><span>, and in a preemptive kernel, to mark the beginning and end of the critical section.</span>
<span>rcu_dereference()</span> is a critical macro that ensures that read operations occur in the expected order on weakly ordered processors like Alpha. It can be seen as a form of memory barrier that ensures the reading of the pointer occurs before the actual data is accessed.
The key feature of the reader-side is zero overhead – no atomic operations, no memory barriers (on most architectures), and no lock contention. This makes the RCU read path extremely efficient, maintaining stable performance even in high-concurrency read scenarios.
2.3 Update Side and Grace Period Mechanism
The update-side operation is relatively complex, involving three key steps: copy, update, and publish. Here is a typical update operation:
/* 1. Create a new copy */
struct data *new_data = kmalloc(sizeof(*new_data), GFP_KERNEL);
*new_data = *old_data;
new_data->field = new_value;
/* 2. Atomic replacement: publish new pointer */
rcu_assign_pointer(global_pointer, new_data);
/* 3. Synchronize and wait for grace period to end */
synchronize_rcu();
/* 4. Reclaim old data */
kfree(old_data);
Here, <span>rcu_assign_pointer()</span> ensures that the new data is fully initialized before the new pointer is visible to readers. It includes a write memory barrier to prevent compiler and CPU reordering optimizations.
The grace period is the most ingenious concept in RCU. A grace period refers to the time span from the start of the update until all possible readers that might reference the old data have exited. After the grace period ends, the updater can safely reclaim the old data.
The Linux kernel uses context switches, user mode execution, and idle loops as indicators for exiting reader-side critical sections. When a CPU experiences one of these states, the kernel considers that all RCU reader-side critical sections on that CPU have completed.
The following diagram illustrates the concept of the grace period during the RCU update process:

2.4 Memory Barriers and Sequential Consistency
The implementation of RCU heavily relies on memory barriers to ensure sequential consistency in multi-core environments. Memory barriers act like “fences” in the code, preventing the compiler and CPU from reordering instructions for optimization.
On the reader side, <span>rcu_dereference()</span> includes a read memory barrier, ensuring that operations following the pointer acquisition do not get reordered before the pointer acquisition. On the update side, <span>rcu_assign_pointer()</span> includes a write memory barrier, ensuring that all store operations prior to the pointer update are visible to other CPUs before the pointer update itself becomes visible to them.
This ordering guarantee is crucial for the correctness of RCU. Consider the scenario of data publishing: the updater must first initialize the new data and then publish the pointer. Without memory barriers, the CPU or compiler might reorder these two steps, leading readers to see uninitialized data.
3 RCU Code Framework and Data Structures
3.1 Core Data Structure Relationship Diagram
The implementation of RCU involves multiple core data structures that work together to manage grace period detection and callback handling. Here is an overview of the main data structures and their relationships:

3.2 rcu_state Structure
<span>rcu_state</span> structure is the global state manager for RCU, with each RCU variant (such as rcu_sched, rcu_bh, rcu_preempt) having its own independent instance. Its main fields include:
- •
<span>node</span>: points to the root node of the RCU node tree, used for hierarchical grace period detection. - •
<span>gp_seq</span>: the current grace period sequence number, used to track grace period progress. - •
<span>completed</span>: the completed grace period sequence number. - •
<span>ncpus</span>: the number of CPUs being tracked. - •
<span>rcu_data</span>: an array of per-CPU data pointers. - •
<span>call</span>: a pointer to the callback function.
This structure manages the advancement of the grace period through the <span>gp_seq</span> field, with the sequence number incrementing each time a new grace period begins.
3.3 rcu_node Hierarchical Tree
To address the scalability challenges of modern multi-core systems (especially NUMA systems), Linux RCU introduces a hierarchical tree structure to manage grace period states. The <span>rcu_node</span> structure forms the nodes of this tree:
struct rcu_node {
raw_spinlock_t lock;
unsigned long gp_seq;
unsigned long qsmask;
int grplo;
int grphi;
struct rcu_node *parent;
struct list_head blkd_tasks;
};
The tree structure design allows grace period detection to occur hierarchically: leaf nodes collect the states of the CPUs they manage, intermediate nodes merge the states of their child nodes, and the root node ultimately determines the state of the entire system. This design reduces lock contention and improves scalability.
<span>qsmask</span> is a bitmap field that tracks which CPUs managed by this node have not yet experienced the current grace period. When all bits are cleared, it indicates that all CPUs managed by this node have experienced the grace period.
3.4 Per-CPU Data rcu_data
<span>rcu_data</span> structure is a per-CPU variable, with each CPU having its own instance, containing the RCU state for that CPU:
struct rcu_data {
struct rcu_segcblist cblist;
unsigned long gp_seq;
int cpu;
bool nohz_full;
bool bypass;
struct rcu_head *oob_head;
struct rcu_node *my_node;
struct rcu_state *rsp;
};
The key field <span>cblist</span> is a segmented callback list that manages callbacks segmented by grace period. This segmented management allows RCU to handle multiple callbacks at different stages simultaneously, improving efficiency.
3.5 Callback Management rcu_segcblist
<span>rcu_segcblist</span> structure manages pending RCU callbacks:
struct rcu_segcblist {
struct rcu_head *head;
struct rcu_head **tails;
long len;
};
It uses four segments to classify callbacks:
- • Ready: callbacks that can be called immediately.
- • Waiting for Current Grace Period: callbacks waiting for the current grace period to end.
- • Waiting for Next Grace Period: callbacks waiting for the next grace period.
- • Offline: callbacks temporarily stored when the CPU is offline.
This segmented management makes callback processing more efficient, especially during grace period transitions.
4 Core RCU Model Analysis
4.1 State Machine and Grace Period Advancement
The core of RCU is a finely-tuned state machine that manages the start, advancement, and end of grace periods. The following diagram illustrates the complete state flow of RCU grace periods:

The advancement of the grace period consists of the following key steps:
- 1. Start Phase: When a new grace period is needed, RCU increments
<span>gp_seq</span>, initializes the relevant state, and wakes up the RCU kernel thread. - 2. Collection Phase: RCU collects the states of CPUs through each CPU’s
<span>rcu_data</span>structure. Each CPU reports that it has experienced the grace period through<span>rcu_report_qs_rnp</span>. - 3. Completion Phase: When all CPUs report, the grace period officially ends, and the relevant callbacks can be safely invoked.
- 4. Callback Invocation Phase: After the grace period ends, RCU invokes all callbacks waiting for that grace period, completing the release of old data.
4.2 Hybrid Tree Structure Combination Mechanism
RCU uses a hybrid tree structure combined with per-CPU variables to efficiently manage grace periods. This design is key to modern RCU’s ability to scale to thousands of CPUs.
In the tree structure, each <span>rcu_node</span> manages a group of CPUs. When a CPU reports that it has experienced the grace period, it first updates the corresponding leaf node. The leaf node, after collecting reports from all managed CPUs, will report up to its parent node. This hierarchical reporting mechanism greatly reduces contention at the root node.
At the same time, RCU uses per-CPU variables to store most state information, allowing each CPU to primarily operate on its own data when reporting grace period experiences, only acquiring node locks when necessary. This further reduces lock contention.
4.3 Callback Processing and Memory Reclamation
Callback processing is the core of RCU memory reclamation. When invoking <span>synchronize_rcu()</span> or <span>call_rcu()</span><span>, RCU does not immediately perform cleanup but delays the cleanup operation until the grace period ends.</span>
<span>call_rcu()</span> function is the foundation of RCU asynchronous processing:
void call_rcu(struct rcu_head *head, rcu_callback_t func)
{
__call_rcu(head, func, rcu_state_p, -1, 0);
}
This function adds the callback structure <span>rcu_head</span> to the callback list of the current CPU’s <span>rcu_data</span>. The elements in the callback list will be processed in batches after the grace period ends.
To debug RCU callback processing, the Linux kernel introduces the <span>CONFIG_RCU_TRACE_CB</span> option, which can track the queuing, flushing, and invocation process of each callback. When this option is enabled, the <span>rcu_head</span> structure will contain debugging information:
struct cb_debug_info {
u16 cb_queue_jiff;
u16 first_bp_jiff;
u16 cb_flush_jiff;
enum cb_debug_flags flags:16;
};
4.4 Preemptible RCU and Real-Time Improvements
To meet the needs of real-time systems, the Linux kernel introduces Preemptible RCU. In traditional RCU, reader-side critical sections are not allowed to be preempted, which can lead to unpredictable delays in real-time systems.
Preemptible RCU provides better real-time performance through the following improvements:
- • Allowing preemption within RCU reader-side critical sections.
- • Using a special list
<span>blkd_tasks</span>to track blocked tasks. - • When a task is preempted, adding it to the blocked task list and removing it from the list when the task resumes.
This mechanism ensures that even if a reader-side task is preempted, the grace period can still advance correctly without indefinitely waiting for the preempted task.
4.5 Callback-Free CPUs and Offline Handling
Modern energy-saving technologies such as <span>nohz</span> mode allow CPUs to completely turn off clock interrupts when idle, which poses challenges for RCU since RCU relies on clock interrupts to detect grace periods.
To address this issue, RCU introduces Callback-Free CPUs handling. When a CPU enters <span>nohz</span> mode, it first reports the grace period state to ensure that grace period advancement is not blocked during the interrupt shutdown.
Similarly, when a CPU goes offline, RCU requires special handling to ensure that the offline CPU does not block grace period progress. RCU handles CPU offline events through the <span>rcu_report_dead()</span> function, removing the offline CPU from grace period detection.
5 RCU Tool Commands and Debugging Methods
5.1 Debug Configuration Options
The Linux kernel provides a rich set of RCU debugging options that can be enabled in the kernel configuration:
| Configuration Option | Description | Performance Impact |
|---|---|---|
<span>CONFIG_PROVE_RCU</span> |
Checks the correctness of RCU usage. | Moderate |
<span>CONFIG_RCU_TORTURE_TEST</span> |
RCU stress testing. | Only during testing |
<span>CONFIG_RCU_TRACE_CB</span> |
Tracks the lifecycle of callbacks. | Slight |
<span>CONFIG_RCU_CPU_STALL_TIMEOUT</span> |
Detects RCU stalls. | Negligible |
<span>CONFIG_DEBUG_CREDENTIALS</span> |
Credentials RCU debugging. | Moderate |
Among these, <span>CONFIG_PROVE_RCU</span> is an important debugging option that uses the Lockdep framework to verify the correct usage of RCU APIs. When this option is enabled, the kernel tracks the state of RCU reader-side critical sections and detects potential misuse, such as blocking within critical sections or violating lock order.
5.2 Runtime Monitoring and Debugging
At runtime, RCU status can be monitored through the sysfs and debugfs file systems:
Common Monitoring Commands:
# View RCU grace period status
cat /sys/kernel/debug/rcu/rcu_preempt/gp_seq
# View RCU callback statistics
cat /sys/kernel/debug/rcu/rcu_preempt/rcu_callback
# View per-CPU RCU status
cat /sys/kernel/debug/rcu/rcu_preempt/rcudata
# Detect RCU stall information
dmesg | grep "RCU stall"
RCU stall detection is an important debugging feature. When the RCU grace period exceeds a preset time (default 21 seconds), the kernel prints warning messages, including the stalled CPU and backtrace information, helping developers locate issues.
5.3 Performance Testing and Stress Testing
RCU provides a dedicated stress testing module <span>rcutorture</span> to validate the correctness and robustness of RCU implementations:
# Load rcutorture module
modprobe rcutorture
# View test status
cat /sys/kernel/debug/rcutorture/rcu_torture_stats
# Unload module
rmmod rcutorture
The <span>rcutorture</span> test creates multiple kernel threads that execute various combinations of RCU operations, including reader-side critical sections, update operations, callback processing, and simulating various exceptional conditions such as memory pressure and CPU hotplugging.
5.4 Kernel Tracing and Events
For in-depth debugging, the Linux kernel provides RCU-related tracing events:
# Enable RCU callback tracing
echo 1 > /sys/kernel/debug/tracing/events/rcu/enable
# View tracing results
cat /sys/kernel/debug/tracing/trace
RCU tracing events include:
- •
<span>rcu_invoke_callback</span>: callback invocation event. - •
<span>rcu_batch_start</span>: start of batch processing callbacks. - •
<span>rcu_torture_read</span>: rcutorture read event.
These events can help developers understand the internal behavior of RCU, diagnose performance issues, and correctness problems.
6 Simple RCU Examples and Practices
6.1 Kernel Module Example
To fully demonstrate the usage of RCU, we create a simple kernel module that illustrates reading and writing data structures protected by RCU:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/delay.h>
struct data {
int value;
char name[32];
struct rcu_head rcu;
};
static struct data *global_data;
static struct task_struct *reader_thread;
static struct task_struct *writer_thread;
static int reader_function(void *data)
{
struct data *d;
while (!kthread_should_stop()) {
rcu_read_lock();
d = rcu_dereference(global_data);
printk(KERN_INFO "Reader: value=%d, name=%s\n", d->value, d->name);
rcu_read_unlock();
msleep(1000);
}
return 0;
}
static void data_reclaim(struct rcu_head *rcu)
{
struct data *d = container_of(rcu, struct data, rcu);
printk(KERN_INFO "Reclaiming data: %s\n", d->name);
kfree(d);
}
static int writer_function(void *data)
{
struct data *new_data, *old_data;
int counter = 0;
while (!kthread_should_stop()) {
/* Create new data */
new_data = kmalloc(sizeof(*new_data), GFP_KERNEL);
new_data->value = counter++;
snprintf(new_data->name, sizeof(new_data->name), "Data-%d", new_data->value);
/* Atomic replacement */
old_data = global_data;
rcu_assign_pointer(global_data, new_data);
/* Asynchronous reclamation of old data */
call_rcu(&old_data->rcu, data_reclaim);
msleep(5000);
}
return 0;
}
static int __init rcu_test_init(void)
{
/* Initialize global data */
global_data = kmalloc(sizeof(*global_data), GFP_KERNEL);
global_data->value = 0;
strcpy(global_data->name, "Initial");
/* Create reader and writer threads */
reader_thread = kthread_run(reader_function, NULL, "rcu-reader");
writer_thread = kthread_run(writer_function, NULL, "rcu-writer");
printk(KERN_INFO "RCU test module loaded\n");
return 0;
}
static void __exit rcu_test_exit(void)
{
kthread_stop(reader_thread);
kthread_stop(writer_thread);
/* Clean up data */
synchronize_rcu();
kfree(global_data);
printk(KERN_INFO "RCU test module unloaded\n");
}
module_init(rcu_test_init);
module_exit(rcu_test_exit);
MODULE_LICENSE("GPL");
6.2 Example Code Analysis
This example module demonstrates the typical usage pattern of RCU:
- 1. Data Structure Design:
<span>struct data</span>includes the<span>rcu_head</span>field, which is necessary for RCU callbacks. - 2. Reader-Side Protection: In the
<span>reader_function</span>, the<span>rcu_read_lock()</span>and<span>rcu_read_unlock()</span><span> are used to define the critical section, safely obtaining the pointer within the critical section using </span><code><span>rcu_dereference()</span>. - 3. Update Side Operations: In the
<span>writer_function</span>, a new copy of the data is created and modified first, then the global pointer is atomically replaced using<span>rcu_assign_pointer()</span><span>, and finally, the reclamation function is registered using </span><code><span>call_rcu()</span>. - 4. Memory Reclamation: The
<span>data_reclaim</span>function retrieves the complete data structure from the<span>rcu_head</span>pointer using the<span>container_of</span>macro and then frees the memory.
6.3 Compilation and Testing
Compiling this module requires the Linux kernel header files. The Makefile content is as follows:
obj-m += rcu_test.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
After loading the module, you can observe the output through <span>dmesg</span><span>, where you will see the reader periodically printing data, the writer updating data every 5 seconds, and the old data being reclaimed after the grace period ends.</span>
6.4 Practical Application Considerations
When using RCU in actual kernel development, the following points should be noted:
- 1. Avoid Blocking: Reader-side critical sections must not block and cannot call functions that may block.
- 2. Memory Barrier Usage: Correctly using
<span>rcu_dereference()</span><span> and </span><code><span>rcu_assign_pointer()</span><span> ensures memory visibility.</span> - 3. Understanding Grace Periods:
<span>synchronize_rcu()</span><span> waits for all </span><strong><span>pre-existing</span></strong><span> readers, not all readers.</span> - 4. Debugging Support: Enable debugging options like
<span>CONFIG_PROVE_RCU</span><span> during the development phase to detect RCU usage errors early.</span>
7 Conclusion
Advantages:
- • Zero Overhead on Reader Side: The read path is lock-free, with no atomic operations or memory barriers (on most architectures).
- • Outstanding Scalability: Reading performance scales linearly with the number of CPUs.
- • Avoids Deadlocks: Reader-side operations do not lead to deadlocks, simplifying software design.
- • Real-Time Capability: Reader-side operations are not blocked, making it suitable for real-time systems.
Limitations:
- • High Overhead on Writer Side: Writing requires memory allocation, copying, and synchronization waiting.
- • Memory Usage: There is temporary memory duplication.
- • Complexity: The implementation mechanism is complex, with a high threshold for understanding and usage.
- • Limited Applicability: Primarily suitable for pointer-based data structures with more reads than writes.
Linux RCU is a sophisticated synchronization mechanism that represents the evolution of synchronization primitive design philosophy: from confrontation to cooperation, from mutual exclusion to separation. Through lock-free reading, delayed reclamation on the writing side, and grace period detection, it provides unparalleled performance in high-concurrency reading scenarios.