Skip to contentSkip to conversation

rust: Add Per-CPU Variable API

RFC PATCH Superseded Mitchell Levy 5 participants 11 messages Dec 19, 2024 – Jan 8, 2025

Subsystems (4)

v1 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

cover rust: Add Per-CPU Variable API

This series adds an API for declaring an using per-CPU variables from Rust, and it also adds support for Rust access to C per-CPU variables (subject to some soundness requirements). It also adds a small test module, lib/percpu_test_rust.rs, in the vein of lib/percpu_test.c.

---
I am sending this patch as an RFC to gather feedback on the API as well as get some input on correctness. In particular, the unsafe getter macro is somewhat cumbersome, though its safety requirements for pure-Rust code aren't too difficult to verify (interoperation with C code is certainly much more tricky).

It was suggested that I base my implementation on Rust's thread-local storage API. However, this API requires that for a thread-local T, any particular usage must be via a &T. Thus, many times it is required that thread-local variables be in the form RefCell<T> or Cell<T>. This has some pretty severe disadvantages in the context of per-CPU variables. Namely, RefCell<T> may panic if .borrow_mut is called multiple times at different points in the call stack. This is similar to the problem inherent in my current implementation (that is, unsafe_get_per_cpu_ref must be used carefully so as to not create aliasing mut references), but this implementation flags this potential problem via an unsafe block and the resulting PerCpuRef can be easily passed along and used fairly easily. Cell<T> on the other hand requires a copy any time the underlying value is used, which is entirely avoided here.

---
Mitchell Levy (3):
rust: percpu: introduce a rust API for per-CPU variables
rust: rust-analyzer: add lib to dirs searched for crates
rust: percpu: add a rust per-CPU variable test

 lib/Kconfig.debug                 |   9 +++
 lib/Makefile                      |   1 +
 lib/percpu_test_rust.rs           |  65 +++++++++++++++
 rust/helpers/helpers.c            |   1 +
 rust/helpers/preempt.c            |  14 ++++
 rust/kernel/lib.rs                |   3 +
 rust/kernel/percpu.rs             | 161 ++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs   |  29 +++++++
 scripts/generate_rust_analyzer.py |   2 +-
 9 files changed, 284 insertions(+), 1 deletion(-)

---
base-commit: 0c5928deada15a8d075516e6e0d9ee19011bb000
change-id: 20240813-rust-percpu-ea2f54b5da33

Best regards,

Signature · 1 line
Mitchell Levy <[email protected]>
#1 lore raw

RFC PATCH 1/3 rust: percpu: introduce a rust API for per-CPU variables

Add a CpuGuard type that disables preemption for its lifetime. Add a PerCpuVariable type used for declaring per-CPU variables and a declare_per_cpu! macro for its use. Add a PerCpuRef that allows actual use of the per-CPU variable and an usafe_get_per_cpu_ref! helper macro to convert PerCpuVariable to PerCpuRef.

---
 rust/helpers/helpers.c          |   1 +
 rust/helpers/preempt.c          |  14 ++++
 rust/kernel/lib.rs              |   3 +
 rust/kernel/percpu.rs           | 161 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs |  29 ++++++++
 5 files changed, 208 insertions(+)
rust/helpers/helpers.c +1 -0
index dcf827a61b52..1db0dcd8f11f 100644
@@ -19,6 +19,7 @@
 #include "mutex.c"
 #include "page.c"
 #include "pid_namespace.c"
+#include "preempt.c"
 #include "rbtree.c"
 #include "refcount.c"
 #include "security.c"
rust/helpers/preempt.c new file +14 -0
new file mode 100644
index 000000000000..2c7529528ddd
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/preempt.h>
+
+void rust_helper_preempt_disable(void)
+{
+	preempt_disable();
+}
+
+void rust_helper_preempt_enable(void)
+{
+	preempt_enable();
+}
+
rust/kernel/lib.rs +3 -0
index e1065a7551a3..3634e1412d60 100644
@@ -49,6 +49,9 @@
 #[cfg(CONFIG_NET)]
 pub mod net;
 pub mod page;
+// Only x86_64 is supported by percpu for now
+#[cfg(CONFIG_X86_64)]
+pub mod percpu;
 pub mod pid_namespace;
 pub mod prelude;
 pub mod print;
rust/kernel/percpu.rs new file +161 -0

161 lines not on this page · raw message

rust/kernel/percpu/cpu_guard.rs new file +29 -0
new file mode 100644
index 000000000000..6f7d320a5c9a
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+//! Contains abstractions for disabling CPU preemption. See CpuGuard.
+
+/// A RAII guard for bindings::preempt_disable and bindings::preempt_enable. Guarantees preemption
+/// is disabled for as long as this object exists.
+pub struct CpuGuard {
+    // Don't make one without using new()
+    _phantom: (),
+}
+
+impl CpuGuard {
+    /// Create a new CpuGuard. Disables preemption for its lifetime.
+    pub fn new() -> Self {
+        // SAFETY: There are no preconditions required to call preempt_disable
+        unsafe {
+            bindings::preempt_disable();
+        }
+        CpuGuard { _phantom: () }
+    }
+}
+
+impl Drop for CpuGuard {
+    fn drop(&mut self) {
+        // SAFETY: There are no preconditions required to call preempt_enable
+        unsafe {
+            bindings::preempt_enable();
+        }
+    }
+}
Signature · 1 line
2.34.1
#2 lore raw

RFC PATCH 2/3 rust: rust-analyzer: add lib to dirs searched for crates

In reply to Mitchell Levy

When generating rust-project.json, also include crates in lib/

---
 scripts/generate_rust_analyzer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
scripts/generate_rust_analyzer.py +1 -1
index 09e1d166d8d2..7d7ffb45fc0c 100755
@@ -109,7 +109,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
     # Then, the rest outside of `rust/`.
     #
     # We explicitly mention the top-level folders we want to cover.
-    extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers"))
+    extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers", "lib"))
     if external_src is not None:
         extra_dirs = [external_src]
     for folder in extra_dirs:
Signature · 1 line
2.34.1
#3 lore raw

RFC PATCH 3/3 rust: percpu: add a rust per-CPU variable test

In reply to Mitchell Levy

Add a short exercise for Rust's per-CPU variable API, modelled after lib/percpu_test.c

---
 lib/Kconfig.debug       |  9 +++++++
 lib/Makefile            |  1 +
 lib/percpu_test_rust.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 75 insertions(+)
lib/Kconfig.debug +9 -0
index f3d723705879..75a91f1766ce 100644
@@ -2404,6 +2404,15 @@ config PERCPU_TEST
 
 	  If unsure, say N.
 
+config PERCPU_TEST_RUST
+	tristate "Rust per cpu operations test"
+	depends on m && DEBUG_KERNEL && RUST
+	help
+	  Enable this option to build a test module which validates Rust per-cpu
+	  operations.
+
+	  If unsure, say N.
+
 config ATOMIC64_SELFTEST
 	tristate "Perform an atomic64_t self-test"
 	help
lib/Makefile +1 -0
index a8155c972f02..0ea8d414763c 100644
@@ -301,6 +301,7 @@ obj-$(CONFIG_RBTREE_TEST) += rbtree_test.o
 obj-$(CONFIG_INTERVAL_TREE_TEST) += interval_tree_test.o
 
 obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
+obj-$(CONFIG_PERCPU_TEST_RUST) += percpu_test_rust.o
 
 obj-$(CONFIG_ASN1) += asn1_decoder.o
 obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
lib/percpu_test_rust.rs new file +65 -0

65 lines not on this page · raw message

Signature · 1 line
2.34.1
#4 lore raw

Re: [PATCH RFC 3/3] rust: percpu: add a rust per-CPU variable test

lib/percpu_test_rust.rs line 25

Quoted 2 lines · Mitchell Levy
lib/percpu_test_rust.rs new file +2 -0
+        let mut native: i64 = 0;
+        let mut pcpu: PerCpuRef<i64> = unsafe { unsafe_get_per_cpu_ref!(PERCPU, CpuGuard::new()) };

A bit complex.

lib/percpu_test_rust.rs line 34

Quoted 8 lines
lib/percpu_test_rust.rs new file +8 -0
+        native += -1;
+        *pcpu += -1;
+        assert!(native == *pcpu && native == -1);
+
+        native += 1;
+        *pcpu += 1;
+        assert!(native == *pcpu && native == 0);
+

That's pretty straightforward..... But is there no symbolic access to the per cpu namespace? How would you access the kernel per cpu variables defined in C?

How do you go about using per cpu atomics like

this_cpu_inc(nr_dentry_unused);

#5 lore raw

Re: [PATCH RFC 3/3] rust: percpu: add a rust per-CPU variable test

Quoted 7 lines · Christoph Lameter (Ampere)

lib/percpu_test_rust.rs line 25

Quoted 2 lines · Mitchell Levy
lib/percpu_test_rust.rs new file +2 -0
+        let mut native: i64 = 0;
+        let mut pcpu: PerCpuRef<i64> = unsafe { unsafe_get_per_cpu_ref!(PERCPU, CpuGuard::new()) };

A bit complex.

Quoted 13 lines

lib/percpu_test_rust.rs line 34

Quoted 8 lines
lib/percpu_test_rust.rs new file +8 -0
+        native += -1;
+        *pcpu += -1;
+        assert!(native == *pcpu && native == -1);
+
+        native += 1;
+        *pcpu += 1;
+        assert!(native == *pcpu && native == 0);
+

That's pretty straightforward..... But is there no symbolic access to the per cpu namespace? How would you access the kernel per cpu variables defined in C?

FWIW, there is a declare_extern_per_cpu!() marco in patch #1, and Mitchell used it to access this_cpu_off (defined in C).

Maybe we want to see an example here?

Regards,
Boqun

Quoted 5 lines

How do you go about using per cpu atomics like

this_cpu_inc(nr_dentry_unused);

#6 lore raw

Re: [PATCH RFC 3/3] rust: percpu: add a rust per-CPU variable test

In reply to Christoph Lameter (Ampere)
Quoted 6 lines · Christoph Lameter (Ampere)

lib/percpu_test_rust.rs line 25

Quoted 2 lines · Mitchell Levy
lib/percpu_test_rust.rs new file +2 -0
+        let mut native: i64 = 0;
+        let mut pcpu: PerCpuRef<i64> = unsafe { unsafe_get_per_cpu_ref!(PERCPU, CpuGuard::new()) };

A bit complex.

I agree with this, maybe a helper function would suffise? Something in terms of,

  unsafe fn get_per_cpu<T>(var: &PerCpuVariable<T>) -> PerCpuRef<T> {
	  unsafe_get_per_cpu_ref!(var, CpuGuard::new())

}

Quoted 17 lines

lib/percpu_test_rust.rs line 34

Quoted 9 lines
lib/percpu_test_rust.rs new file +8 -0
+        native += -1;
+        *pcpu += -1;
+        assert!(native == *pcpu && native == -1);
+
+        native += 1;
+        *pcpu += 1;
+        assert!(native == *pcpu && native == 0);
+

That's pretty straightforward..... But is there no symbolic access to the per cpu namespace? How would you access the kernel per cpu variables defined in C?

How do you go about using per cpu atomics like

this_cpu_inc(nr_dentry_unused);

#7 lore raw

Re: [PATCH RFC 3/3] rust: percpu: add a rust per-CPU variable test

Quoted 7 lines · Charalampos Mitrodimas
Quoted 5 lines · Christoph Lameter (Ampere)
Quoted 2 lines · Mitchell Levy
lib/percpu_test_rust.rs new file +2 -0
+        let mut native: i64 = 0;
+        let mut pcpu: PerCpuRef<i64> = unsafe { unsafe_get_per_cpu_ref!(PERCPU, CpuGuard::new()) };
Quoted 7 lines
Quoted 1 line

A bit complex.

I agree with this, maybe a helper function would suffise? Something in terms of,

  unsafe fn get_per_cpu<T>(var: &PerCpuVariable<T>) -> PerCpuRef<T> {
	  unsafe_get_per_cpu_ref!(var, CpuGuard::new())

}

I'm certainly open to adding such a helper. Is the main concern here the unwieldy name? Generally, I prefer to keep modifications to global state (disabling preemption via CpuGuard::new()) as explicit as possible, but if there's consensus to the contrary, I'm happy to roll it into the macro/a helper function.

Quoted 17 lines
Quoted 17 lines

lib/percpu_test_rust.rs line 34

Quoted 9 lines
lib/percpu_test_rust.rs new file +8 -0
+        native += -1;
+        *pcpu += -1;
+        assert!(native == *pcpu && native == -1);
+
+        native += 1;
+        *pcpu += 1;
+        assert!(native == *pcpu && native == 0);
+

That's pretty straightforward..... But is there no symbolic access to the per cpu namespace? How would you access the kernel per cpu variables defined in C?

How do you go about using per cpu atomics like

this_cpu_inc(nr_dentry_unused);

#8 lore raw

Re: [PATCH RFC 3/3] rust: percpu: add a rust per-CPU variable test

Quoted 16 lines · Mitchell Levy
Quoted 7 lines · Charalampos Mitrodimas
Quoted 5 lines · Christoph Lameter (Ampere)
Quoted 2 lines · Mitchell Levy
lib/percpu_test_rust.rs new file +2 -0
+        let mut native: i64 = 0;
+        let mut pcpu: PerCpuRef<i64> = unsafe { unsafe_get_per_cpu_ref!(PERCPU, CpuGuard::new()) };
Quoted 7 lines
Quoted 1 line

A bit complex.

I agree with this, maybe a helper function would suffise? Something in terms of,

  unsafe fn get_per_cpu<T>(var: &PerCpuVariable<T>) -> PerCpuRef<T> {
	  unsafe_get_per_cpu_ref!(var, CpuGuard::new())

}

Quoted 5 lines

I'm certainly open to adding such a helper. Is the main concern here the unwieldy name? Generally, I prefer to keep modifications to global state (disabling preemption via CpuGuard::new()) as explicit as possible, but if there's consensus to the contrary, I'm happy to roll it into the macro/a helper function.

Yes, in my opinion, the macro name is indeed complex. You're right about keeping modifications as explicit as possible. A helper wouldn’t be necessary if the macro name were simpler.

Is adding "unsafe_" to a macro that is unsafe a standard practice? Maybe we can remove it from the macro name. Documentation is enough IMO.

Quoted 18 lines
Quoted 18 lines
Quoted 18 lines

lib/percpu_test_rust.rs line 34

Quoted 10 lines
lib/percpu_test_rust.rs new file +8 -0
+        native += -1;
+        *pcpu += -1;
+        assert!(native == *pcpu && native == -1);
+
+        native += 1;
+        *pcpu += 1;
+        assert!(native == *pcpu && native == 0);
+

That's pretty straightforward..... But is there no symbolic access to the per cpu namespace? How would you access the kernel per cpu variables defined in C?

How do you go about using per cpu atomics like

this_cpu_inc(nr_dentry_unused);

#9 lore raw
In reply to Mitchell Levy

Hello,

Quoted 5 lines · Mitchell Levy

This series adds an API for declaring an using per-CPU variables from Rust, and it also adds support for Rust access to C per-CPU variables (subject to some soundness requirements). It also adds a small test module, lib/percpu_test_rust.rs, in the vein of lib/percpu_test.c.

Quoted 7 lines

---
I am sending this patch as an RFC to gather feedback on the API as well as get some input on correctness. In particular, the unsafe getter macro is somewhat cumbersome, though its safety requirements for pure-Rust code aren't too difficult to verify (interoperation with C code is certainly much more tricky).

I've been thinking a little bit about this over the holidays and fwiw am not really the right person to evaluate the rust side of this. I did briefly discuss this with Hudson (cced) who is more familiar with rust.

In addition to Christoph's comments:
1. This implementation seems targeted at static allocations and the safety of this api relies on the end user doing the right thing.

There are many percpu variables created dynamically via alloc_percpu() and the lifetime of those variables are tied to a larger object. So access to those variables is safe via the understanding the larger object exists. I feel like there is room here for rust to do this checking somehow? Maybe in rust-only percpu variables, but something better nonetheless.

2. As Christoph mentioned, the percpu api relies heavily on macros for architecture specific optimizations. Your implementation seems asm-generic correct, but we should find a way to export the architecture specific percpu macros.

this_cpu_off is a percpu variable that helps x86 arch_raw_cpu_ptr().

3. Maybe a good use case to start off with would be percpu_refcount and how rust can use it or how it can make something like that safer?

Some of Hudson's direct comments about the Rust implementation:
1. DerefMut for PerCpuRef is ultimately constructing a reference from a raw integer without preserving the provenance of the original pointer in any way (since this integer is being constructed from raw register reads). I believe this violates Rust's pointer provenance rules for unsafe code.

2. The safety requirements of unsafe_get_per_cpu_ref seem like they would often not be possible to verify at the callsite of the macro without global knowledge -- especially for percpu variables defined in C code. Is this a misunderstanding? Can you show an example of where it would be straightforward to build a higher-level safe API on top of this unsafe one?

Quoted 14 lines

It was suggested that I base my implementation on Rust's thread-local storage API. However, this API requires that for a thread-local T, any particular usage must be via a &T. Thus, many times it is required that thread-local variables be in the form RefCell<T> or Cell<T>. This has some pretty severe disadvantages in the context of per-CPU variables. Namely, RefCell<T> may panic if .borrow_mut is called multiple times at different points in the call stack. This is similar to the problem inherent in my current implementation (that is, unsafe_get_per_cpu_ref must be used carefully so as to not create aliasing mut references), but this implementation flags this potential problem via an unsafe block and the resulting PerCpuRef can be easily passed along and used fairly easily. Cell<T> on the other hand requires a copy any time the underlying value is used, which is entirely avoided here.

Quoted 2 lines

I think this is a cool topic and has helped inspire me to learn a bit more rust. Hopefully I can be more helpful in the future.

Thanks,
Dennis

#10 lore raw
Quoted 24 lines · Dennis Zhou

Hello,

Quoted 5 lines · Mitchell Levy

This series adds an API for declaring an using per-CPU variables from Rust, and it also adds support for Rust access to C per-CPU variables (subject to some soundness requirements). It also adds a small test module, lib/percpu_test_rust.rs, in the vein of lib/percpu_test.c.

Quoted 7 lines

---
I am sending this patch as an RFC to gather feedback on the API as well as get some input on correctness. In particular, the unsafe getter macro is somewhat cumbersome, though its safety requirements for pure-Rust code aren't too difficult to verify (interoperation with C code is certainly much more tricky).

I've been thinking a little bit about this over the holidays and fwiw am not really the right person to evaluate the rust side of this. I did briefly discuss this with Hudson (cced) who is more familiar with rust.

In addition to Christoph's comments:
1. This implementation seems targeted at static allocations and the safety of this api relies on the end user doing the right thing.

Quoted 6 lines

There are many percpu variables created dynamically via alloc_percpu() and the lifetime of those variables are tied to a larger object. So access to those variables is safe via the understanding the larger object exists. I feel like there is room here for rust to do this checking somehow? Maybe in rust-only percpu variables, but something better nonetheless.

You're correct; my hope is that adding dynamic allocations on top of this API should be straightforward. Because we don't have to worry about aliasing concerns (since we're in control of the memory that gets set aside), we can just directly spit out a PerCpuRef. The one wrinkle would be adding a lifetime parameter to PerCpuRef and making everything line up.

Quoted 6 lines

2. As Christoph mentioned, the percpu api relies heavily on macros for architecture specific optimizations. Your implementation seems asm-generic correct, but we should find a way to export the architecture specific percpu macros.

this_cpu_off is a percpu variable that helps x86 arch_raw_cpu_ptr().

Definitely! I'm hoping to add some operator overloading to make using the optimized operations really smooth. I didn't want to bite off too much for this RFC, though.

Quoted 2 lines

3. Maybe a good use case to start off with would be percpu_refcount and how rust can use it or how it can make something like that safer?

This looks like a great idea, thank you!

Quoted 6 lines

Some of Hudson's direct comments about the Rust implementation:
1. DerefMut for PerCpuRef is ultimately constructing a reference from a raw integer without preserving the provenance of the original pointer in any way (since this integer is being constructed from raw register reads). I believe this violates Rust's pointer provenance rules for unsafe code.

Caveat: I'm not super familiar with Rust's provenance model.

My understanding is that there are no "provenance issues" in a loose sense. What I mean by this is that, if everything is treated as the pointer it "should be" the process looks like:

per-cpu subsystem owns per-cpu area -> pointer is stored in this_cpu_area -> we create a new pointer (via an in-bounds offset + shrinking to sizeof(T)) to the contents of the per-cpu variable in question

It looks like asm! actually supports reading into pointer types (I had misread the table in the Rust Reference, and thought it only supported reading into integer types) --- this might help avoid discarding provenance during a cast to usize? I'm definitely interested in hearing more on this point.

Quoted 6 lines

2. The safety requirements of unsafe_get_per_cpu_ref seem like they would often not be possible to verify at the callsite of the macro without global knowledge -- especially for percpu variables defined in C code. Is this a misunderstanding? Can you show an example of where it would be straightforward to build a higher-level safe API on top of this unsafe one?

Yeah interoperation with C per-cpu variables is certainly very difficult. Unfortunately, I think this problem crops up any time you're sharing a piece of memory between Rust and C since the existence of &T that points at some piece of memory is a guarantee to the compiler that the pointed-to memory will not change. This facility is intended more as a trap-door for when there's no better option than as an API building block.

Quoted 14 lines
Quoted 14 lines

It was suggested that I base my implementation on Rust's thread-local storage API. However, this API requires that for a thread-local T, any particular usage must be via a &T. Thus, many times it is required that thread-local variables be in the form RefCell<T> or Cell<T>. This has some pretty severe disadvantages in the context of per-CPU variables. Namely, RefCell<T> may panic if .borrow_mut is called multiple times at different points in the call stack. This is similar to the problem inherent in my current implementation (that is, unsafe_get_per_cpu_ref must be used carefully so as to not create aliasing mut references), but this implementation flags this potential problem via an unsafe block and the resulting PerCpuRef can be easily passed along and used fairly easily. Cell<T> on the other hand requires a copy any time the underlying value is used, which is entirely avoided here.

Quoted 5 lines
Quoted 2 lines

I think this is a cool topic and has helped inspire me to learn a bit more rust. Hopefully I can be more helpful in the future.

I appreciate it; thanks to you and Hudson for your help!

Quoted 2 lines

Thanks,
Dennis