<style>
.reveal {
font-size: 32px;
}
section {
text-align: left;
}
</style>
# If you want to use RCU in Rust...
*Boqun Feng*, Self
*Paul E. McKenney*, Meta Platforms Kernel Team
---
## ... what you can do?
* Just call a C function:
* If there is already a C function doing the heavy-lifting including accesses to RCU protect data, you can just call the C function
* Also you can always open-unsafe-code RCU primitives in a Rust function
```rust=
fn some_func() {
unsafe { bindings::rcu_read_lock(); }
...
unsafe { bindings::rcu_read_unlock(); }
}
```
* Or use a Rust version RCU API
---
## RCU core APIs
* `rcu_read_lock()` and `rcu_read_unlock()`
* `rcu_dereference()`
* `rcu_assign_pointer()`
* `synchronize_rcu()` and `call_rcu()`
----
### `rcu_read_lock()`
```rust=
/// RAII struct representing holding a RCU read lock.
struct RcuGuard {
_not_send: PhantomData<*mut ()>,
}
impl RcuGuard {
pub fn new() -> Self {
unsafe { bindings::rcu_read_lock(); }
Self {
_not_send: PhantomData,
}
}
}
```
----
### `rcu_read_unlock()`
```rust=
impl Drop for RcuGuard {
fn drop(&mut self) {
unsafe { bindings::rcu_read_unlock(); }
}
}
```
----
### `rcu_dereference()`
```rust=
/// # Invariants: `ptr` must be
/// * either null
/// * or a valid pointer to T and under
/// RCU grace period protection.
struct UnsafeRcu<T> {
ptr: AtomicPtr<T>
}
impl<T> UnsafeRcu<T> {
pub fn dereference<'a, 'b>(
&'a self, _guard: &'b RcuGuard
) -> Option<&'b T> {
let p = self.ptr.load(Relaxed);
if p.is_null() {
return None;
}
// For the address dependency ordering
fence(Acquire);
// SAFETY: `&RcuGuard` must outlive `&T`, so the reference is
// valid under RCU Guarantee
Some(unsafe { &* p })
}
}
```
----
Example that reference can outlive the RCU pointer but not RCU read-side critical section
```c=
struct something {
int val;
struct work_struct work;
struct shared_work __rcu *sw;
}
void reclaim(struct something *sth) {
struct shared_work *sw;
rcu_read_lock();
sw = rcu_dereference(sth->sw);
// sth->sw = NULL;
// Do some free work
queue_work(..., &sth->work);
do_shared_work(sw);
rcu_read_unlock();
}
```
----
### `rcu_assign_pointer()`
```rust=
impl<T> UnsafeRcu<T> {
/// # Safety
/// `ptr` needs to fulfill the invariants of `UnsafeRcu`.
pub unsafe fn set(&self, ptr: *mut T) {
self.ptr.store(ptr, Release);
}
pub fn load(&self) -> *mut T {
self.ptr.load(Relaxed)
}
pub unsafe swap(&self, ptr: *mut T) -> *mut T {
self.ptr.swap(ptr, AcqRel) // xchg
}
}
```
---
## Existing RCU use cases: an incomplete list
* Quasi reader-writer lock.
* Quasi reference count.
* Quasi multi-version consistency control.
* Light-weight garbage collector.
* Delete-only list (relatively rare)..
* Add-only list (relatively rare)..
* Type-safe memory (relatively rare)..
* Existence guarantee.
* Phased state change (relatively rare)..
---
## Design/Requirement dimensions
### 1. Read-side permissions for non-pointer fields (and non-RCU pointers):
* a) None.
* b) Read-only.
* c) Read-write. (For example, counting rare read-side error conditions.)
* Interior mutability for the win?
* d) One of the above on a per-field basis.
----
### 2. Update-side permissions for non-pointer fields (and non-RCU pointers):
* a) None.
* b) Read-only.
* c) Read-write.
* d) One of the above on a per-field basis.
----
### 3. Read-side permissions for pointer fields linking RCU-protected data:
* a) None. (But how is this useful given no way to traverse the structure?)
* b) Read-only, which is the common case.
* c) Read-write, enabling reader traversal to fine-grained in-structure update.
* d) One of the above on a per-field basis, supporting multilinked structures.
----
### 4. Update-side permissions for pointer fields linking RCU-protected data:
* a) None. (But how is this useful given no way to traverse the structure?)
* b) Read-only, which enables updating with full-structure replacement.
* c) Read-write, enabling updater traversal to fine-grained in-structure update.
* d) One of the above on a per-field basis, supporting multilinked structures.
----
### 5. Changing update-side permissions when restricted by readers:
* a) Manually, for example, using unsafe blocks.
* b) Automatic for insertion, e.g., `p2 = rcu_assign_pointer(gp, p1)`.
* c) Automatic for removal, e.g., `call_rcu()` updates permission.
`synchronize_rcu`() would needs pointers in and out?
* d) Update-side function to be executed during reader accessibility.
* Seems uselessly restrictive to me, but failure of imagination on my part?
----
### 6. C-language interoperability:
* a) Rust-only (easy case!).
* b) C-only (easy case!)
* c) Rust read-only, C update-only.
* d) Rust read-only, C readers and updaters. (Practical difference from #c?)
* e) C read-only, Rust update-only.
* f) C read-only, Rust readers and updaters. (Practical difference from #e?)
* g) Rust readers and updaters with C updaters.
* h) Rust updaters and C readers and updaters. (Practical difference from #g?)
* i) Rust and C both read and update
----
#### Rust/C RCU Interoperability Table (1/4)
| Rust || C || |
| --- | ----- | ----- | ---- | ----- |
| Read | Update | Read | Update | Commentary |
| N | N | N | N | Pointless ;-) |
| N | N | N | Y | Not RCU. |
| N | N | Y | N | No synchronization needed |
| N | N | Y | Y | C-only (6b) |
----
#### Rust/C RCU Interoperability Table (2/4)
| Rust || C || |
| --- | ----- | ----- | ---- | ----- |
| Read | Update | Read | Update | Commentary |
| N | Y | N | N | Not RCU. |
| N | Y | N | Y | Not RCU. |
| N | Y | Y | N | Rust updaters, C readers (6e). |
| N | Y | Y | Y |Rust updaters, C readers and updaters (6h). |
----
#### Rust/C RCU Interoperability Table (3/4)
| Rust || C || |
| --- | ----- | ----- | ---- | ----- |
| Read | Update | Read | Update | Commentary |
| Y | N | N | N | No synchronization needed |
| Y | N | N | Y | Rust readers, C updaters (6c). |
| Y | N | Y | N | No synchronization needed |
| Y | N | Y | Y | Rust readers, C readers and updaters (6d). |
----
#### Rust/C RCU Interoperability Table (4/4)
| Rust || C || |
| --- | ----- | ----- | ---- | ----- |
| Read | Update | Read | Update | Commentary |
| Y | Y | N | N | Rust-only (6a). |
| Y | Y | N | Y | Rust readers and updaters, C update (6g). |
| Y | Y | Y | N | Rust readers and updaters, C readers (6f). |
| Y | Y | Y | Y | Both Rust and C readers and updaters (6i). |
----
### More Dimensions???
Probably!
Especially if we decide to attempt automatic conversion from RCU-protected C data structures to RCU-protected Rust data structures, which we might want to do in order to avoid Rust/C skew.
---
## Case studies #1: Read (Copy) Update
```rust=
// struct config {
// int x;
// int y;
// int z;
// }
struct Config {
x: i32,
y: i32,
z: i32,
}
```
```rust
// void update(struct config __rcu **config, int x, int y, int z)
pub fn update(cfg: &UnsafeRCU<Config>, x: i32, y: i32, z: i32);
// (int, int, int) read(struct config __rcu **config)
pub fn read(cfg: &UnsafeRcu<Config>) -> (i32, i32, i32);
```
Two-level pointer to simulate a global pointer.
----
### Read (Copy) Update: Reader side
```rust=
pub fn read(config: &UnsafeRcu<Config>) -> Option<(i32, i32, i32)> {
// rcu_read_lock();
let g = RcuGuard::new();
// struct config *cfg = rcu_dereference(*config);
let cfg = cfg.dereference(&g)?;
(cfg.x, cfg.y, cfg.z)
// drop(g): rcu_read_unlock();
}
```
----
### Read (Copy) Update: Updater side?
```rust=
pub fn update(config: &UnsafeRCU<Config>, x: i32, y: i32, z: i32) {
// struct config *new = your_alloc_config(x, y, z);
let new: *mut Config = your_alloc_config(x, y, z);
// struct config *old = rcu_access_pointer(*config);
let old = cfg.load();
// rcu_assign_pointer(*config, new);
// SAFETY: `update` is the only function updates `config`, and it will
// call a synchronize_rcu() before release `old`
unsafe { cfg.set(new) }
// SAFETY: Worst case is deadlock, so it's still safe?
unsafe { bindings::synchronize_rcu(); }
your_free_config(old);
}
```
----
### Read (Copy) Update: Updater side
Need atomic update, otherwise maybe double free.
```rust=
pub fn update(config: &UnsafeRCU<Config>, x: i32, y: i32, z: i32) {
// struct config *new = your_alloc_config(x, y, z);
let new: *mut Config = your_alloc_config(x, y, z);
// struct config *old = xchg_acqrel(*config, new);
// SAFETY: `update` is the only function updates `config`, and it will
// call a synchronize_rcu() before release `old`, also `old` will be
// only freed by here, no double free.
let old = unsafe { cfg.swap(new) };
// SAFETY: Worst case is deadlock, so it's still safe?
unsafe { bindings::synchronize_rcu(); }
your_free_config(old);
}
```
----
### Read (Copy) Update
* `your_alloc_config()` and `your_free_config()` can be implemented by `kmalloc/kfree`
* also `your_free_config()` can be implemented by `kfree_rcu`, no need to call `synchronize_rcu` then.
----
### Read (Copy) Update: Generify with `Box`
```rust=
pub fn update<T>(data: &UnsafeRcu<T>, new_box: Box<T>) {
let new: *mut T = Box::into_raw(new_box);
let old = unsafe { data.swap(new) };
if !old.is_null() {
unsafe { bindings::synchronize_rcu(); }
// SAFETY: `old` was previously set by the update function,
// so it comes from a previous `Box::into_raw`,
// and since we called `synchronize_rcu` after
// `swap`, no reader is referencing the old data now.
drop(unsafe { Box::from_raw(old) });
}
Ok(())
}
```
----
### Read Copy Update
```rust
pub fn copy_update<T>(data: &UnsafeRcu<T>, update_fn: fn(&mut T)) { ... }
```
Example:
```rust=
copy_update(&some_config, |cfg: &mut Config| {
// cfg is a copy of the old data
cfg.x = 12
})
```
----
### Read Copy Update
```rust=
pub fn copy_update<T: Copy>(data: &UnsafeRcu<T>, update_fn: fn(&mut T)) {
<lock>
let old = data.load();
<handle the old == null and return>
// Requires T: Copy
let mut new_box: Box<T> = Box::try_new(unsafe {*old})?;
update_fn(&mut new_box);
unsafe { data.set(Box::into_raw(new_box)); }
<unlock>
<synchronize_rcu or call_rcu>
}
```
```rust=
#[derive(Copy, Clone)]
struct Config { ... }
```
----
## Read Copy Update
How can we fill the `<lock>` and `<unlock>` part?
* Big RCU updater lock!
* Make `copy_update` an `&mut` function?
```rust=
pub fn copy_update<T: Copy>(
data: &mut UnsafeRcu<T>, update_fn: fn(&mut T)
) { ... }
/// but this in theory doesn't work,
// since updater shouldn't block readers in RCU
/// it's unsafe that `&mut` and `&` co-exist)
```
* Make `copy_update` an `unsafe` function
```rust
pub unsafe fn copy_update<T: Copy>(
data: &UnsafeRcu<T>, update_fn: fn(&mut T)
) { ... }
```
----
## Read Copy Update
Put it together, we have a safer API
```rust=
impl<T> BoxRcu<T> {
pub fn update(&self, new: Box<T>) { ... }
pub fn dereference<'rcu>(&self, &'rcu RcuGuard) -> &'rcu T { ... }
// Needs to wait for a grace period
pub unsafe fn swap(&self, new: Box<T>) -> Option<Box<T>> { ... }
// Needs to wait for a grace period and ensure exclusive among updaters
pub unsafe fn replace(&self, new: Box<T>) -> Option<Box<T>> { ...}
}
impl<T: Copy> BoxRcu<T> {
// safe is Big RCU updater lock is used.
pub fn copy_update(&self, update_fn: fn(&mut T)) { ... }
}
```
---
## Case studies #2: Fine-grained lock
```rust=
// struct foo {
// int a;
// int b;
// struct config __rcu *cfg;
// }
struct Foo {
a: i32,
b: i32,
cfg: BoxRcu<Config>,
}
```
----
### Fine-grained lock: Where is the lock?
```rust=
// struct foo_locked {
// spinlock_t lock;
// struct foo foo;
// }
impl SpinLock<Foo> {
pub fn lock(&self) -> SpinLockGuard<'_, Foo> { <lock> }
}
impl<'a> Drop for SpinLockGuard<'a, Foo> {
fn drop(&mut self) { <unlock> }
}
impl<'a> DerefMut for SpinLockGuard<'a, Foo> {
fn deref_mut(&mut self) -> &mut Foo { ... }
}
```
----
### Fine-grained lock: How would updaters access `Foo::cfg`?
```rust=
let some_foo: &SpinLock<Foo> = ...
let guard: SpinLockGuard<...> = some_foo.lock();
let foo: &mut Foo = guard.deref_mut();
// Exclusive accesses until foo's life ends.
<readers in the same time?>
```
`&mut Foo` is required to access `Foo::a` and `Foo::b`, but `Foo::cfg` should be able to be accessed by readers and updaters at the same time!
----
### Fine-grained lock: A quick solution
regroup the fields
```rust=
struct FooLocked {
a: i32,
b: i32,
}
struct Foo {
locked: SpinLock<FooLocked>,
cfg: BoxRcu<Config>,
}
```
It works, but is not flexible, for example `Foo` can be:
```rust=
struct Foo {
a: i32,
b: i32,
confg: BoxRcu<Config>,
c: i64,
bar: BoxRcu<Bar>,
}
```
also `unsafe` is still needed for updating `Foo::cfg`.
----
### Fine-grained lock: An RCU-reader-aware lock?
```rust=
impl SpinAndRcu<Foo> {
pub fn updater(&self) -> Updater<'_, Foo> { ... }
pub fn get_cfg(&self) -> &BoxRcu<Config> { ... }
}
impl<'a> Updater<'a, Foo> {
pub fn mut_a(&mut self) -> &mut i32 { ... }
pub fn mut_b(&mut self) -> &mut i32 { ... }
pub fn update_cfg(&mut self, new: Box<Config>) { ... }
pub fn copy_update_cfg(&mut self, update_fn: fn(&mut Config)) { ... }
}
```
Works but not a generic solution.
----
### Fine-grained lock: A generic solution
should have the below methods:
```rust
pub fn deref(updater: &Updater<'a, T>) -> &T
```
* $\forall f$ : `F` as a normal (exclusively writable) field of `T`, we have
```rust
pub fn ???(updater: &mut Updater<'a, T>) -> &mut F
```
* $\forall r$ : `BoxRcu<F>` s a RCU field of `T`, we have
```rust
pub fn ???(lock: &SpinAndRcu<T>) -> &BoxRcu<F>
```
* $\forall r$ : `BoxRcu<F>` s a RCU field of `T`, we also have
```rust
pub fn ???(updater: &mut Updater<T>, new: Box<F>)
```
----
### Fine-grained lock: Define a field of a struct
```rust=
pub unsafe trait Field<Base: ?Sized> {
/// The type of the field.
type Type: ?Sized;
/// The name of the field.
const NAME: &'static str;
/// Adjust the pointer from the containing type.
///
/// # Safety
///
/// `base` must be a non-null and aligned pointer to [`Self::Base`].
unsafe fn field_of(base: *const Base) -> *const Self::Type;
}
```
----
For example, define this for `Foo::a`
```rust=
unsafe impl Field<Foo> for Foo::a {
type Type = i32;
cost NAME: &'static str = "a";
unsafe fn field_of(base: *const Foo) -> *const Self::Type {
// &foo->a
addr_of!(unsafe { * base }.a)
}
}
```
----
How to get these `impl`s **automatically**?
"Use the field projection, Luke!"
* [Library work](https://docs.rs/field-projection/latest/field_projection/) by Gary Guo
* [Language/compiler](https://internals.rust-lang.org/t/pre-rfc-field-projection/17383) proposal by Benno Lossin
----
Mutable accesses to lock-protected fields
```rust=
impl<'a, T> Updater<'a, T> {
/// Accesses a non-RCU field mutably.
pub fn project_mut<F>(&mut self) -> &mut <F as Field<T>>::Type
where
F: FieldMut<T>,
{
// SAFETY: `self.lock.data.get()` is non-null and aligned to `T`.
let ptr = unsafe {
<F as Field<T>>::field_of(self.guard.lock.data.get())
};
// SAFETY: We are holding the lock,
// so we have the mutable accessibilty to the field.
unsafe { &mut *(ptr as *mut _) }
}
}
```
usage
```rust=
let updater: &mut Updater<'a, Foo> = ...;
// foo->a = 1
*(updater.project_mut::<Foo::a>()) = 1;
```
----
Lockless accesses to RCU fields
```rust=
impl<T> SpinAndRcu<T> {
pub fn project<F, S>(&self) -> &BoxRcu<S>
where
F: Field<T, Type = BoxRcu<S>>,
{
// SAFETY: `self.lock.data.get()` is non-null and aligned to `T`.
let ptr = unsafe { F::field_of(self.lock.data.get()) };
unsafe { &*ptr }
}
}
```
usage
```rust=
let foo: &SpinAndRcu<Foo> = ...;
// rcu_read_lock();
// int x = rcu_dereference(foo->cfg)->x;
let x = foo.project::<Foo::cfg, Config>().dereference(&RcuGuard::new()).x;
```
----
Update RCU fields with lock held
```rust=
impl<'a, T> Updater<'a, T> {
pub fn update<F, S>(&self, new: Box<S>)
where
F: Field<T, Type = BoxRcu<S>>,
{
// SAFETY: `self.lock.data.get()` is non-null and aligned to `T`.
let ptr = unsafe { F::field_of(self.guard.lock.data.get()) };
// SAFETY: `T` owns the RCU field, and with `Guard` of `T`
// means exclusive accesses among updaters.
let old = unsafe { (*ptr).unsafe_swap(Box::into_raw(new)) }
<synchronize_rcu() and free old>
}
}
```
----
usage
```rust=
let foo :&SpinAndRcu<foo> = ...;
// spin_lock(&foo->lock);
let mut updater: Updater<'a, Foo> = foo.updater();
// struct config *new = kmalloc(...);
// other initialization
let mut new: Box<Config> = ...;
// new->x = foo->cfg->x;
new.x = updater.cfg.x;
// struct config *old = foo->cfg;
// rcu_assign_pointer(foo->cfg, new);
// free_rcu(old);
updater.update::<Foo::cfg, Config>(new);
// spin_unlock(&foo->lock);
```
---
## Summary
* `UnsafeRcu`: 1b, 2a, 3c, 4c, 5b-, 6i
* `BoxRcu`: 1b, 2a, 3c, 4c, 5c- (5c if using Big RCU updater side lock)
* 6a is regrouping is needed
* 6i otherwise
* Field projection + `BoxRcu`: 1d, 2d, 3d, 4d, 5c, 6i
---
## Future work
* Build the proper type hierarchy to make `call_rcu()` work
* Explore more use cases (linked list, etc.)
---
## Acknowledgments
* The original idea of using field projection for RCU was based on discussion in Rust-for-Linux meetings/zulip, all glory to the team!
* `rcu_read_lock()` implementation is from Wedson ;-)
* Special thanks to: Paul E. McKenney, Neeraj Upadhyay, Frederic Weisbecker, Uladzislau Rezki and Joel Fernandes for a rehearsal of the Rust RCU API.
{"description":"If you want to use RCU in Rust…","title":"If you want to use RCU in Rust…","slideOptions":"{}","contributors":"[{\"id\":\"dffd2ff1-560b-4d58-97ff-99ee85479024\",\"add\":43358,\"del\":25079}]"}