Skip to contentSkip to conversation

rust: Add Per-CPU Variable API

RFC PATCH Superseded Mitchell Levy 1 participant 4 messages Apr 14, 2025

Subsystems (4)

v2 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v2 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.

---
This series builds on my earlier RFC by adding support for dynamically allocated per-CPU variables. In the process of this work, I became convinced that the API in the original RFC was untenable. In particular, copying a PerCpuRef would need to be an unsafe operation whose safety requirements would be very onerous to verify. Essentially, a PerCpuRef would need to be entirely unique on each CPU core (except for the moment the copy is created, immediately before it is sent off to another thread --- technically the requirement is that the copy could not be Deref'd before being sent). This is essentially impossible to satisfy if you wish to use on_each_cpu, and it is certainly not ergonomic.

The key challenge was preventing the creation of aliasing &mut T's while still allowing PerCpu<T>'s to be freely copied. Ultimately, Boqun suggested a closure-based API in which the user asserts that the contents of the closure don't "nest" with clones. That is, the user asserts that they're *not* doing something like:

```
let mut my_pcpu: PerCpu<u32> = /* ... */;
let mut my_pcpu2: PerCpu<u32> = my_pcpu.clone();
unsafe { my_pcpu.get(CpuGuard::new()) }.with(|my_pcpu_val: &mut u32| {
    // UNSAFETY: This is bad
    unsafe { my_pcpu2.get(CpuGuard::new()) }
      .with(|aliasing_ref: &mut u32| { /* ... */ });
}
```

This safety condition is (in my opinion) much easier to verify because we must only ensure the contents of the closure are well-behaved, rather than try and trace all possible paths that a PerCpuRef may take.

This API is certainly different from the original RFC, which is why I've sent another RFC. It's now much closer to the user-space thread-local API, though still avoids requiring the use of Cell-like types.

A small number of simplifying choices are made here, namely that allocations are always GFP_KERNEL. This was done with the intention of keeping the patch small while the overall shape of the API is evaluated.

---
Changes from RFC:
- Renamed PerCpuVariable to StaticPerCpuSymbol to be more descriptive
- Support dynamically allocated per-CPU variables via the
PerCpuAllocation type. Rework statically allocated variables to use
this new type.
- Make use of a token/closure-based API via the PerCpu and PerCpuToken types, rather than an API based on PerCpuRef that automatically Deref(Mut)'s into a &(mut) T.
- Rebased
- Link to RFC: https://lore.kernel.org/r/[email protected]

---
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           | 119 +++++++++++++++++++
 rust/helpers/helpers.c            |   2 +
 rust/helpers/percpu.c             |  20 ++++
 rust/helpers/preempt.c            |  14 +++
 rust/kernel/lib.rs                |   3 +
 rust/kernel/percpu.rs             | 239 ++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs   |  29 +++++
 scripts/generate_rust_analyzer.py |   2 +-
 10 files changed, 437 insertions(+), 1 deletion(-)

---
base-commit: a2cc6ff5ec8f91bc463fd3b0c26b61166a07eb11
change-id: 20240813-rust-percpu-ea2f54b5da33

Best regards,

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

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

Add a CpuGuard type that disables preemption for its lifetime. Add a PerCpuAllocation type used to track and, in the case of dynamic allocations, create and free per-CPU allocations. Add a define_per_cpu! macro to create static per-CPU allocations. Add a PerCpu type that manages PerCpuAllocation's being used from multiple cores, and add a PerCpuToken type used to assert exclusive access to a particular per-CPU variable.

---
 rust/helpers/helpers.c          |   2 +
 rust/helpers/percpu.c           |   9 ++
 rust/helpers/preempt.c          |  14 +++
 rust/kernel/lib.rs              |   3 +
 rust/kernel/percpu.rs           | 239 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs |  29 +++++
 6 files changed, 296 insertions(+)
rust/helpers/helpers.c +2 -0
index e1c21eba9b15..cff605710f77 100644
@@ -23,7 +23,9 @@
 #include "page.c"
 #include "platform.c"
 #include "pci.c"
+#include "percpu.c"
 #include "pid_namespace.c"
+#include "preempt.c"
 #include "rbtree.c"
 #include "rcu.c"
 #include "refcount.c"
rust/helpers/percpu.c new file +9 -0
new file mode 100644
index 000000000000..a091389f730f
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/percpu.h>
+
+void __percpu *rust_helper_alloc_percpu(size_t sz, size_t align)
+{
+	return __alloc_percpu(sz, align);
+}
+
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 de07aadd1ff5..dc88becdb338 100644
@@ -67,6 +67,9 @@
 pub mod page;
 #[cfg(CONFIG_PCI)]
 pub mod pci;
+// Only x86_64 is supported by percpu for now
+#[cfg(CONFIG_X86_64)]
+pub mod percpu;
 pub mod pid_namespace;
 pub mod platform;
 pub mod prelude;
rust/kernel/percpu.rs new file +239 -0

239 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 v2 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 cd41bc906fbd..64c5283a509f 100755
@@ -144,7 +144,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 v2 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 | 119 ++++++++++++++++++++++++++++++++++++++++++++++++
 rust/helpers/percpu.c   |  11 +++++
 4 files changed, 140 insertions(+)
lib/Kconfig.debug +9 -0
index df9587aa5c5e..e9fa12940cf2 100644
@@ -2425,6 +2425,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 f07b24ce1b3f..adace4f3ae63 100644
@@ -289,6 +289,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 +119 -0

119 lines not on this page · raw message

rust/helpers/percpu.c +11 -0
index a091389f730f..0e9b2fed3ebd 100644
@@ -1,9 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/percpu.h>
+#include <linux/smp.h>
 
 void __percpu *rust_helper_alloc_percpu(size_t sz, size_t align)
 {
 	return __alloc_percpu(sz, align);
 }
 
+void rust_helper_on_each_cpu(smp_call_func_t func, void *info, int wait)
+{
+	on_each_cpu(func, info, wait);
+}
+
+int rust_helper_smp_processor_id(void)
+{
+	return smp_processor_id();
+}
+
Signature · 1 line
2.34.1