From: Boqun Feng <boqun.feng@gmail.com>
To: rust-for-linux@vger.kernel.org, rcu@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
llvm@lists.linux.dev, lkmm@lists.linux.dev
Cc: "Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <benno.lossin@proton.me>,
"Andreas Hindborg" <a.hindborg@samsung.com>,
"Alice Ryhl" <aliceryhl@google.com>,
"Alan Stern" <stern@rowland.harvard.edu>,
"Andrea Parri" <parri.andrea@gmail.com>,
"Will Deacon" <will@kernel.org>,
"Peter Zijlstra" <peterz@infradead.org>,
"Nicholas Piggin" <npiggin@gmail.com>,
"David Howells" <dhowells@redhat.com>,
"Jade Alglave" <j.alglave@ucl.ac.uk>,
"Luc Maranget" <luc.maranget@inria.fr>,
"Paul E. McKenney" <paulmck@kernel.org>,
"Akira Yokosawa" <akiyks@gmail.com>,
"Daniel Lustig" <dlustig@nvidia.com>,
"Joel Fernandes" <joel@joelfernandes.org>,
"Nathan Chancellor" <nathan@kernel.org>,
"Nick Desaulniers" <ndesaulniers@google.com>,
kent.overstreet@gmail.com,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
elver@google.com, "Mark Rutland" <mark.rutland@arm.com>,
"Thomas Gleixner" <tglx@linutronix.de>,
"Ingo Molnar" <mingo@redhat.com>,
"Borislav Petkov" <bp@alien8.de>,
"Dave Hansen" <dave.hansen@linux.intel.com>,
x86@kernel.org, "H. Peter Anvin" <hpa@zytor.com>,
"Catalin Marinas" <catalin.marinas@arm.com>,
torvalds@linux-foundation.org,
linux-arm-kernel@lists.infradead.org,
linux-fsdevel@vger.kernel.org, "Trevor Gross" <tmgross@umich.edu>,
dakr@redhat.com, "Frederic Weisbecker" <frederic@kernel.org>,
"Neeraj Upadhyay" <neeraj.upadhyay@kernel.org>,
"Josh Triplett" <josh@joshtriplett.org>,
"Uladzislau Rezki" <urezki@gmail.com>,
"Steven Rostedt" <rostedt@goodmis.org>,
"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
"Lai Jiangshan" <jiangshanlai@gmail.com>,
Zqiang <qiang.zhang1211@gmail.com>,
"Paul Walmsley" <paul.walmsley@sifive.com>,
"Palmer Dabbelt" <palmer@dabbelt.com>,
"Albert Ou" <aou@eecs.berkeley.edu>,
linux-riscv@lists.infradead.org
Subject: [RFC v3 12/12] rust: sync: rcu: Add RCU protected pointer
Date: Mon, 21 Apr 2025 09:42:21 -0700 [thread overview]
Message-ID: <20250421164221.1121805-13-boqun.feng@gmail.com> (raw)
In-Reply-To: <20250421164221.1121805-1-boqun.feng@gmail.com>
RCU protected pointers are an atomic pointer that can be loaded and
dereferenced by mulitple RCU readers, but only one updater/writer can
change the value (following a read-copy-update pattern usually).
This is useful in the case where data is read-mostly. The rationale of
this patch is to provide a proof of concept on how RCU should be exposed
to the Rust world, and it also serves as an example for atomic usage.
Similar mechanisms like ArcSwap [1] are already widely used.
Provide a `Rcu<P>` type with an atomic pointer implementation. `P` has
to be a `ForeignOwnable`, which means the ownership of a object can be
represented by a pointer-size value.
`Rcu::dereference()` requires a RCU Guard, which means dereferencing is
only valid under RCU read lock protection.
`Rcu::read_copy_update()` is the operation for updaters, it requries a
`Pin<&mut Self>` for exclusive accesses, since RCU updaters are normally
exclusive with each other.
A lot of RCU functionalities including asynchronously free (call_rcu()
and kfree_rcu()) are still missing, and will be the future work.
Also, we still need language changes like field projection [2] to
provide better ergonomic.
Acknowledgment: this work is based on a lot of productive discussions
and hard work from others, these are the ones I can remember (sorry if I
forgot your contribution):
* Wedson started the work on RCU field projection and Benno followed it
up and had been working on it as a more general language feature.
Also, Gary's field-projection repo [3] has been used as an example for
related discussions.
* During Kangrejos 2023 [4], Gary, Benno and Alice provided a lot of
feedbacks on the talk from Paul and me: "If you want to use RCU in
Rust for Linux kernel..."
* During a recent discussion among Benno, Paul and me, Benno suggested
using `Pin<&mut>` to guarantee the exclusive access on updater
operations.
Link: https://crates.io/crates/arc-swap [1]
Link: https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/Field.20Projections/near/474648059 [2]
Link: https://github.com/nbdd0121/field-projection [3]
Link: https://kangrejos.com/2023 [4]
Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
---
rust/kernel/sync/rcu.rs | 275 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 274 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/sync/rcu.rs b/rust/kernel/sync/rcu.rs
index b51d9150ffe2..201c09cb60db 100644
--- a/rust/kernel/sync/rcu.rs
+++ b/rust/kernel/sync/rcu.rs
@@ -4,7 +4,12 @@
//!
//! C header: [`include/linux/rcupdate.h`](srctree/include/linux/rcupdate.h)
-use crate::{bindings, types::NotThreadSafe};
+use crate::bindings;
+use crate::{
+ sync::atomic::{Atomic, Relaxed, Release},
+ types::{ForeignOwnable, NotThreadSafe},
+};
+use core::{marker::PhantomData, pin::Pin, ptr::NonNull};
/// Evidence that the RCU read side lock is held on the current thread/CPU.
///
@@ -45,3 +50,271 @@ fn drop(&mut self) {
pub fn read_lock() -> Guard {
Guard::new()
}
+
+/// An RCU protected pointer, the pointed object is protected by RCU.
+///
+/// # Invariants
+///
+/// Either the pointer is null, or it points to a return value of [`P::into_foreign`] and the atomic
+/// variable exclusively owns the pointer.
+pub struct Rcu<P: ForeignOwnable>(Atomic<*mut crate::ffi::c_void>, PhantomData<P>);
+
+/// A pointer that has been unpublished, but hasn't waited for a grace period yet.
+///
+/// The pointed object may still have an existing RCU reader. Therefore a grace period is needed to
+/// free the object.
+///
+/// # Invariants
+///
+/// The pointer has to be a return value of [`P::into_foreign`] and [`Self`] exclusively owns the
+/// pointer.
+pub struct RcuOld<P: ForeignOwnable>(NonNull<crate::ffi::c_void>, PhantomData<P>);
+
+impl<P: ForeignOwnable> Drop for RcuOld<P> {
+ fn drop(&mut self) {
+ // SAFETY: As long as called in a sleepable context, which should be checked by klint,
+ // `synchronize_rcu()` is safe to call.
+ unsafe {
+ bindings::synchronize_rcu();
+ }
+
+ // SAFETY: `self.0` is a return value of `P::into_foreign()`, so it's safe to call
+ // `from_foreign()` on it. Plus, the above `synchronize_rcu()` guarantees no existing
+ // `ForeignOwnable::borrow()` anymore.
+ let p: P = unsafe { P::from_foreign(self.0.as_ptr()) };
+ drop(p);
+ }
+}
+
+impl<P: ForeignOwnable> Rcu<P> {
+ /// Creates a new RCU pointer.
+ pub fn new(p: P) -> Self {
+ // INVARIANTS: The return value of `p.into_foreign()` is directly stored in the atomic
+ // variable.
+ Self(Atomic::new(p.into_foreign()), PhantomData)
+ }
+
+ /// Creates a null RCU pointer.
+ pub const fn null() -> Self {
+ Self(Atomic::new(core::ptr::null_mut()), PhantomData)
+ }
+
+ /// Dereferences the protected object.
+ ///
+ /// Returns `Some(b)`, where `b` is a reference-like borrowed type, if the pointer is not null,
+ /// otherwise returns `None`.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use kernel::alloc::{flags, KBox};
+ /// use kernel::sync::rcu::{self, Rcu};
+ ///
+ /// let x = Rcu::new(KBox::new(100i32, flags::GFP_KERNEL)?);
+ ///
+ /// let g = rcu::read_lock();
+ /// // Read in under RCU read lock protection.
+ /// let v = x.dereference(&g);
+ ///
+ /// assert_eq!(v, Some(&100i32));
+ ///
+ /// # Ok::<(), Error>(())
+ /// ```
+ ///
+ /// Note the borrowed access can outlive the reference of the [`Rcu<P>`], this is because as
+ /// long as the RCU read lock is held, the pointed object should remain valid.
+ ///
+ /// In the following case, the main thread is responsible for the ownership of `shared`, i.e. it
+ /// will drop it eventually, and a work item can temporarily access the `shared` via `cloned`,
+ /// but the use of the dereferenced object doesn't depend on `cloned`'s existence.
+ ///
+ /// ```rust
+ /// # use kernel::alloc::{flags, KBox};
+ /// # use kernel::workqueue::system;
+ /// # use kernel::sync::{Arc, atomic::{Atomic, Acquire, Release}};
+ /// use kernel::sync::rcu::{self, Rcu};
+ ///
+ /// struct Config {
+ /// a: i32,
+ /// b: i32,
+ /// c: i32,
+ /// }
+ ///
+ /// let config = KBox::new(Config { a: 1, b: 2, c: 3 }, flags::GFP_KERNEL)?;
+ ///
+ /// let shared = Arc::new(Rcu::new(config), flags::GFP_KERNEL)?;
+ /// let cloned = shared.clone();
+ ///
+ /// // Use atomic to simulate a special refcounting.
+ /// static FLAG: Atomic<i32> = Atomic::new(0);
+ ///
+ /// system().try_spawn(flags::GFP_KERNEL, move || {
+ /// let g = rcu::read_lock();
+ /// let v = cloned.dereference(&g).unwrap();
+ /// drop(cloned); // release reference to `shared`.
+ /// FLAG.store(1, Release);
+ ///
+ /// // but still need to access `v`.
+ /// assert_eq!(v.a, 1);
+ /// drop(g);
+ /// });
+ ///
+ /// // Wait until `cloned` dropped.
+ /// while FLAG.load(Acquire) == 0 {
+ /// // SAFETY: Sleep should be safe.
+ /// unsafe { kernel::bindings::schedule(); }
+ /// }
+ ///
+ /// drop(shared);
+ ///
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn dereference<'rcu>(&self, _rcu_guard: &'rcu Guard) -> Option<P::Borrowed<'rcu>> {
+ // Ordering: Address dependency pairs with the `store(Release)` in read_copy_update().
+ let ptr = self.0.load(Relaxed);
+
+ if !ptr.is_null() {
+ // SAFETY:
+ // - Since `ptr` is not null, so it has to be a return value of `P::into_foreign()`.
+ // - The returned `Borrowed<'rcu>` cannot outlive the RCU Guar, this guarantees the
+ // return value will only be used under RCU read lock, and the RCU read lock prevents
+ // the pass of a grace period that the drop of `RcuOld` or `Rcu` is waiting for,
+ // therefore no `from_foreign()` will be called for `ptr` as long as `Borrowed` exists.
+ //
+ // CPU 0 CPU 1
+ // ===== =====
+ // { `x` is a reference to Rcu<Box<i32>> }
+ // let g = rcu::read_lock();
+ //
+ // if let Some(b) = x.dereference(&g) {
+ // // drop(g); cannot be done, since `b` is still alive.
+ //
+ // if let Some(old) = x.replace(...) {
+ // // `x` is null now.
+ // println!("{}", b);
+ // }
+ // drop(old):
+ // synchronize_rcu();
+ // drop(g);
+ // // a grace period passed.
+ // // No `Borrowed` exists now.
+ // from_foreign(...);
+ // }
+ Some(unsafe { P::borrow(ptr) })
+ } else {
+ None
+ }
+ }
+
+ /// Read, copy and update the pointer with new value.
+ ///
+ /// Returns `None` if the pointer's old value is null, otherwise returns `Some(old)`, where old
+ /// is a [`RcuOld`] which can be used to free the old object eventually.
+ ///
+ /// The `Pin<&mut Self>` is needed because this function needs the exclusive access to
+ /// [`Rcu<P>`], otherwise two `read_copy_update()`s may get the same old object and double free.
+ /// Using `Pin<&mut Self>` provides the exclusive access that C side requires with the type
+ /// system checking.
+ ///
+ /// Also this has to be `Pin` because a `&mut Self` may allow users to `swap()` safely, that
+ /// will break the atomicity. A [`Rcu<P>`] should be structurally pinned in the struct that
+ /// contains it.
+ ///
+ /// Note that `Pin<&mut Self>` cannot assume noalias here because [`Atomic<T>`] is a
+ /// [`Opaque<T>`] which has the same effect on aliasing rules as [`UnsafePinned`].
+ ///
+ /// [`UnsafePinned`]: https://rust-lang.github.io/rfcs/3467-unsafe-pinned.html
+ pub fn read_copy_update<F>(self: Pin<&mut Self>, f: F) -> Option<RcuOld<P>>
+ where
+ F: FnOnce(Option<P::Borrowed<'_>>) -> Option<P>,
+ {
+ // step 1: READ.
+ // Ordering: Address dependency pairs with the `store(Release)` in read_copy_update().
+ let old_ptr = NonNull::new(self.0.load(Relaxed));
+
+ let old = old_ptr.map(|nonnull| {
+ // SAFETY: Per type invariants `old_ptr` has to be a value return by a previous
+ // `into_foreign()`, and the exclusive reference `self` guarantees that `from_foreign()`
+ // has not been called.
+ unsafe { P::borrow(nonnull.as_ptr()) }
+ });
+
+ // step 2: COPY, or more generally, initializing `new` based on `old`.
+ let new = f(old);
+
+ // step 3: UPDATE.
+ if let Some(new) = new {
+ let new_ptr = new.into_foreign();
+ // Ordering: Pairs with the address dependency in `dereference()` and
+ // `read_copy_update()`.
+ // INVARIANTS: `new.into_foreign()` is directly store into the atomic variable.
+ self.0.store(new_ptr, Release);
+ } else {
+ // Ordering: Setting to a null pointer doesn't need to be Release.
+ // INVARIANTS: The atomic variable is set to be null.
+ self.0.store(core::ptr::null_mut(), Relaxed);
+ }
+
+ // INVARIANTS: The exclusive reference guarantess that the ownership of a previous
+ // `into_foreign()` transferred to the `RcuOld`.
+ Some(RcuOld(old_ptr?, PhantomData))
+ }
+
+ /// Replaces the pointer with new value.
+ ///
+ /// Returns `None` if the pointer's old value is null, otherwise returns `Some(old)`, where old
+ /// is a [`RcuOld`] which can be used to free the old object eventually.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use core::pin::pin;
+ /// # use kernel::alloc::{flags, KBox};
+ /// use kernel::sync::rcu::{self, Rcu};
+ ///
+ /// let mut x = pin!(Rcu::new(KBox::new(100i32, flags::GFP_KERNEL)?));
+ /// let q = KBox::new(101i32, flags::GFP_KERNEL)?;
+ ///
+ /// // Read in under RCU read lock protection.
+ /// let g = rcu::read_lock();
+ /// let v = x.dereference(&g);
+ ///
+ /// // Replace with a new object.
+ /// let old = x.as_mut().replace(q);
+ ///
+ /// assert!(old.is_some());
+ ///
+ /// // `v` should still read the old value.
+ /// assert_eq!(v, Some(&100i32));
+ ///
+ /// // New readers should get the new value.
+ /// assert_eq!(x.dereference(&g), Some(&101i32));
+ ///
+ /// drop(g);
+ ///
+ /// // Can free the object outside the read-side critical section.
+ /// drop(old);
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn replace(self: Pin<&mut Self>, new: P) -> Option<RcuOld<P>> {
+ self.read_copy_update(|_| Some(new))
+ }
+}
+
+impl<P: ForeignOwnable> Drop for Rcu<P> {
+ fn drop(&mut self) {
+ let ptr = *self.0.get_mut();
+ if !ptr.is_null() {
+ // SAFETY: As long as called in a sleepable context, which should be checked by klint,
+ // `synchronize_rcu()` is safe to call.
+ unsafe {
+ bindings::synchronize_rcu();
+ }
+
+ // SAFETY: `self.0` is a return value of `P::into_foreign()`, so it's safe to call
+ // `from_foreign()` on it. Plus, the above `synchronize_rcu()` guarantees no existing
+ // `ForeignOwnable::borrow()` anymore.
+ drop(unsafe { P::from_foreign(ptr) });
+ }
+ }
+}
--
2.47.1
prev parent reply other threads:[~2025-04-21 16:42 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-21 16:42 [RFC v3 00/12] LKMM generic atomics in Rust Boqun Feng
2025-04-21 16:42 ` [RFC v3 01/12] rust: Introduce atomic API helpers Boqun Feng
2025-04-21 16:42 ` [RFC v3 02/12] rust: sync: Add basic atomic operation mapping framework Boqun Feng
2025-04-21 16:42 ` [RFC v3 03/12] rust: sync: atomic: Add ordering annotation types Boqun Feng
2025-04-21 16:42 ` [RFC v3 04/12] rust: sync: atomic: Add generic atomics Boqun Feng
2025-04-21 16:42 ` [RFC v3 05/12] rust: sync: atomic: Add atomic {cmp,}xchg operations Boqun Feng
2025-04-21 16:42 ` [RFC v3 06/12] rust: sync: atomic: Add the framework of arithmetic operations Boqun Feng
2025-04-21 16:42 ` [RFC v3 07/12] rust: sync: atomic: Add Atomic<u{32,64}> Boqun Feng
2025-04-21 16:42 ` [RFC v3 08/12] rust: sync: atomic: Add Atomic<{usize,isize}> Boqun Feng
2025-04-21 16:42 ` [RFC v3 09/12] rust: sync: atomic: Add Atomic<*mut T> Boqun Feng
2025-04-21 16:42 ` [RFC v3 10/12] rust: sync: atomic: Add arithmetic ops for " Boqun Feng
2025-04-21 16:42 ` [RFC v3 11/12] rust: sync: Add memory barriers Boqun Feng
2025-04-21 16:42 ` Boqun Feng [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250421164221.1121805-13-boqun.feng@gmail.com \
--to=boqun.feng@gmail.com \
--cc=a.hindborg@samsung.com \
--cc=akiyks@gmail.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=aou@eecs.berkeley.edu \
--cc=benno.lossin@proton.me \
--cc=bjorn3_gh@protonmail.com \
--cc=bp@alien8.de \
--cc=catalin.marinas@arm.com \
--cc=dakr@redhat.com \
--cc=dave.hansen@linux.intel.com \
--cc=dhowells@redhat.com \
--cc=dlustig@nvidia.com \
--cc=elver@google.com \
--cc=frederic@kernel.org \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=hpa@zytor.com \
--cc=j.alglave@ucl.ac.uk \
--cc=jiangshanlai@gmail.com \
--cc=joel@joelfernandes.org \
--cc=josh@joshtriplett.org \
--cc=kent.overstreet@gmail.com \
--cc=linux-arch@vger.kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=lkmm@lists.linux.dev \
--cc=llvm@lists.linux.dev \
--cc=luc.maranget@inria.fr \
--cc=mark.rutland@arm.com \
--cc=mathieu.desnoyers@efficios.com \
--cc=mingo@redhat.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=neeraj.upadhyay@kernel.org \
--cc=npiggin@gmail.com \
--cc=ojeda@kernel.org \
--cc=palmer@dabbelt.com \
--cc=parri.andrea@gmail.com \
--cc=paul.walmsley@sifive.com \
--cc=paulmck@kernel.org \
--cc=peterz@infradead.org \
--cc=qiang.zhang1211@gmail.com \
--cc=rcu@vger.kernel.org \
--cc=rostedt@goodmis.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=stern@rowland.harvard.edu \
--cc=tglx@linutronix.de \
--cc=tmgross@umich.edu \
--cc=torvalds@linux-foundation.org \
--cc=urezki@gmail.com \
--cc=will@kernel.org \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).