Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ obj-m += dma.o
obj-m += blkram.o
obj-m += vnetloop.o
obj-m += kmem_cache.o
obj-m += example_rcu.o

KDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(CURDIR)
Expand Down
105 changes: 105 additions & 0 deletions examples/example_rcu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include <linux/module.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
#include <linux/delay.h>

struct my_data {
int value;
};

static struct my_data __rcu *shared_data;
static DEFINE_SPINLOCK(data_lock);
static struct task_struct *reader_thread;

static int reader_thread_fn(void *data)
{
while (!kthread_should_stop()) {
struct my_data *p;
int val;
bool valid = false;

rcu_read_lock();
p = rcu_dereference(shared_data);
if (p) {
val = p->value;
valid = true;
}
rcu_read_unlock();

if (valid)
pr_info("RCU Reader: value = %d\n", val);
else
pr_info("RCU Reader: data is NULL\n");

msleep(500);
}
return 0;
}

static void update_data(int new_value)
{
struct my_data *new_p, *old_p;

new_p = kmalloc(sizeof(*new_p), GFP_KERNEL);
if (!new_p)
return;
new_p->value = new_value;

spin_lock(&data_lock);

old_p = rcu_dereference_protected(shared_data, lockdep_is_held(&data_lock));
rcu_assign_pointer(shared_data, new_p);

spin_unlock(&data_lock);

if (old_p) {
synchronize_rcu();
kfree(old_p);
pr_info("RCU Updater: old memory freed\n");
}
}

static int __init example_rcu_init(void)
{
pr_info("Loading RCU example module\n");

update_data(42);

reader_thread = kthread_run(reader_thread_fn, NULL, "rcu_reader");
if (IS_ERR(reader_thread))
return PTR_ERR(reader_thread);

msleep(1000);
update_data(100);
msleep(1000);

return 0;
}

static void __exit example_rcu_exit(void)
{
struct my_data *old_p;

pr_info("Unloading RCU example module\n");

kthread_stop(reader_thread);

spin_lock(&data_lock);
old_p = rcu_dereference_protected(shared_data, lockdep_is_held(&data_lock));
RCU_INIT_POINTER(shared_data, NULL);
spin_unlock(&data_lock);

if (old_p) {
synchronize_rcu();
kfree(old_p);
}
}

module_init(example_rcu_init);
module_exit(example_rcu_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kuan-Wei Chiu <visitorckw@gmail.com>");
MODULE_DESCRIPTION("RCU Example Module");
30 changes: 30 additions & 0 deletions lkmpg.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2318,6 +2318,36 @@ \subsection{Atomic operations}
\item \href{https://lwn.net/Articles/698315/}{Atomic usage patterns in the kernel}
\end{itemize}

\subsection{Read-Copy-Update (RCU)}
\label{sec:rcu}
Read-Copy-Update (RCU) is a synchronization mechanism that allows extremely fast, lock-free reads while still permitting concurrent updates.
It is highly optimized for read-mostly scenarios.

Unlike spinlocks or mutexes, RCU readers do not acquire any locks or write to shared memory.
This completely avoids cache-line bouncing and results in near-zero overhead for the read path.
The tradeoff is that the updaters bear the cost of synchronization.
When modifying a data structure, an updater creates a copy, modifies it, and then publishes the new version by replacing the old pointer in a single atomic operation.

Because concurrent readers might still be accessing the old data, the updater cannot free it immediately.
Instead, it must wait for a "grace period" to elapse---a duration long enough to ensure that all pre-existing readers have left their critical sections.
Only then can the old memory be safely reclaimed.

The RCU API relies on a few fundamental primitives:
\begin{itemize}
\item \cpp|rcu_read_lock()| and \cpp|rcu_read_unlock()|: Define a read-side critical section. Readers must not sleep inside this section.
\item \cpp|rcu_dereference()|: Safely fetches an RCU-protected pointer for readers, preventing stale or reordered pointer loads.
\item \cpp|rcu_assign_pointer()|: Used by updaters to publish a new pointer.
\item \cpp|synchronize_rcu()|: Blocks the updater until all pre-existing RCU readers have completed their critical sections.
\end{itemize}

Keep in mind that RCU only protects the readers.
Concurrent writers must still serialize among themselves using a standard lock, such as a spinlock or a mutex.
For more comprehensive details, you can refer to the official Linux kernel documentation on RCU: \url{https://docs.kernel.org/RCU/whatisRCU.html}.

The following example demonstrates a basic RCU implementation where a reader safely accesses shared data while a writer updates it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding some official materials would be better:


\samplec{examples/example_rcu.c}
Comment thread
visitorckw marked this conversation as resolved.
Resolved

% FIXME: we should rewrite this section
\section{Replacing Print Macros}
\label{sec:print_macros}
Expand Down