diff --git a/examples/Makefile b/examples/Makefile index e51c0bc1..211dd28e 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -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) diff --git a/examples/example_rcu.c b/examples/example_rcu.c new file mode 100644 index 00000000..9308bedd --- /dev/null +++ b/examples/example_rcu.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("RCU Example Module"); diff --git a/lkmpg.tex b/lkmpg.tex index c951fe48..7efc96a3 100644 --- a/lkmpg.tex +++ b/lkmpg.tex @@ -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. + +\samplec{examples/example_rcu.c} + % FIXME: we should rewrite this section \section{Replacing Print Macros} \label{sec:print_macros}