Skip to contentSkip to conversation

rust: Add Per-CPU Variable API

PATCH Superseded Mitchell Levy 3 participants 11 messages Jun 24, 2025 – Jun 26, 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.

---

---
Changes in RFC v2:
- 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]

---
Changes in v1:
- Use wrapping_add in PerCpuPtr::get_ref since overflow is expected.
- Separate the dynamic and static cases, with shared logic in a
PerCpuPtr type.
- Implement pin-hole optimizations for numeric types
- Don't assume GFP_KERNEL when allocating the Arc in the dynamic
case.
- Link to RFC v2: https://lore.kernel.org/r/[email protected]

---
Mitchell Levy (5):
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
rust: percpu: Add pin-hole optimizations for numerics
rust: percpu: cache per-CPU pointers in the dynamic case

 lib/Kconfig.debug                 |   9 ++
 lib/Makefile                      |   1 +
 lib/percpu_test_rust.rs           | 156 +++++++++++++++++++
 rust/helpers/helpers.c            |   2 +
 rust/helpers/percpu.c             |  20 +++
 rust/helpers/preempt.c            |  14 ++
 rust/kernel/lib.rs                |   3 +
 rust/kernel/percpu.rs             | 306 ++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs   |  35 +++++
 rust/kernel/percpu/numeric.rs     | 117 +++++++++++++++
 scripts/generate_rust_analyzer.py |   2 +-
 11 files changed, 664 insertions(+), 1 deletion(-)

---
base-commit: 19272b37aa4f83ca52bdf9c16d5d81bdd1354494
change-id: 20240813-rust-percpu-ea2f54b5da33

Best regards,

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

PATCH 1/5 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 dynamic allocations. Add a define_per_cpu! macro to create static per-CPU allocations. Add DynamicPerCpu and StaticPerCpu to provide a high-level API. Add a PerCpu trait to unify the dynamic and static cases.

---
 rust/helpers/helpers.c          |   2 +
 rust/helpers/percpu.c           |   9 ++
 rust/helpers/preempt.c          |  14 ++
 rust/kernel/lib.rs              |   3 +
 rust/kernel/percpu.rs           | 299 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs |  35 +++++
 6 files changed, 362 insertions(+)
rust/helpers/helpers.c +2 -0
index 0f1b5d115985..d56bbe6334d3 100644
@@ -29,7 +29,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 6b4774b2b1c3..733f9ff8b888 100644
@@ -95,6 +95,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 +299 -0

299 lines not on this page · raw message

rust/kernel/percpu/cpu_guard.rs new file +35 -0
new file mode 100644
index 000000000000..14c04b12e7f0
@@ -0,0 +1,35 @@
+// 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 Default for CpuGuard {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+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

Re: [PATCH 1/5] rust: percpu: introduce a rust API for per-CPU variables

kernel test robot noticed the following build errors:

Hi Mitchell,

kernel test robot noticed the following build errors:

[auto build test ERROR on 19272b37aa4f83ca52bdf9c16d5d81bdd1354494]

url:    https://github.com/intel-lab-lkp/linux/commits/Mitchell-Levy/rust-percpu-introduce-a-rust-API-for-per-CPU-variables/20250625-061219
base:   19272b37aa4f83ca52bdf9c16d5d81bdd1354494
patch link:    https://lore.kernel.org/r/20250624-rust-percpu-v1-1-9c59b07d2a9c%40gmail.com
patch subject: [PATCH 1/5] rust: percpu: introduce a rust API for per-CPU variables
config: x86_64-buildonly-randconfig-004-20250626 (https://download.01.org/0day-ci/archive/20250626/[email protected]/config)
compiler: clang version 20.1.7 (https://github.com/llvm/llvm-project 6146a88f60492b520a36f8f8f3231e15f3cc6082)
rustc: rustc 1.78.0 (9b00956e5 2024-04-29)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250626/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

>> error[E0425]: cannot find function `align_of` in this scope

--> rust/kernel/percpu.rs:106:65
|
106 | let ptr: *mut T = unsafe { alloc_percpu(size_of::<T>(), align_of::<T>()) } as *mut T;
| ^^^^^^^^ not found in this scope
|
help: consider importing this function
|
6 + use core::mem::align_of;
|
--

>> error: use of extern static is unsafe and requires unsafe block (error E0133)

--> rust/kernel/percpu.rs:71:53
|
71 | let this_cpu_off_pcpu = core::ptr::addr_of!(this_cpu_off);
| ^^^^^^^^^^^^ use of extern static
|
= note: for more information, see issue #71668 <https://github.com/rust-lang/rust/issues/71668>
= note: extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior
note: an unsafe function restricts its caller, but its body is safe by default
--> rust/kernel/percpu.rs:70:5
|
70 | pub unsafe fn get_ref(&self) -> &mut T {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: requested on the command line with -D unsafe-op-in-unsafe-fn

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
#3 lore raw

PATCH 2/5 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 7c3ea2b55041..08e14ae9c1a0 100755
@@ -152,7 +152,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs, core_edit
     # 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
#4 lore raw

PATCH 3/5 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 | 120 ++++++++++++++++++++++++++++++++++++++++++++++++
 rust/helpers/percpu.c   |  11 +++++
 4 files changed, 141 insertions(+)
lib/Kconfig.debug +9 -0
index ebe33181b6e6..959ce156c601 100644
@@ -2418,6 +2418,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 c38582f187dd..ab19106cc22c 100644
@@ -288,6 +288,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 +120 -0

120 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
#5 lore raw

PATCH 4/5 rust: percpu: Add pin-hole optimizations for numerics

In reply to Mitchell Levy

The C implementations of this_cpu_add, this_cpu_sub, etc., are optimized to save an instruction by avoiding having to compute this_cpu_ptr(&x) for some per-CPU variable x. For example, rather than

    u64 *x_ptr = this_cpu_ptr(&x);
    *x_ptr += 5;

the implementation of this_cpu_add is clever enough to make use of the fact that per-CPU variables are implemented on x86 via segment registers, and so we can use only a single instruction (where we assume &x is already in rax)

    add gs:[rax], 5

Add this optimization via a PerCpuNumeric type to enable code-reuse between DynamicPerCpu and StaticPerCpu.

---
 lib/percpu_test_rust.rs       |  36 +++++++++++++
 rust/kernel/percpu.rs         |   1 +
 rust/kernel/percpu/numeric.rs | 117 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 154 insertions(+)
lib/percpu_test_rust.rs +36 -0
index a9652e6ece08..114015435a85 100644
@@ -25,6 +25,26 @@
 define_per_cpu!(PERCPU: i64 = 0);
 define_per_cpu!(UPERCPU: u64 = 0);
 
+macro_rules! make_optimization_test {
+    ($ty:ty) => {
+        let mut test: DynamicPerCpu<$ty> = DynamicPerCpu::new(GFP_KERNEL).unwrap();
+        {
+            let _ = CpuGuard::new();
+            // SAFETY: No other usage of `test`
+            unsafe { test.get(CpuGuard::new()) }.with(|val: &mut $ty| *val = 10);
+            test.num().add(1);
+            // SAFETY: No other usage of `test`
+            unsafe { test.get(CpuGuard::new()) }.with(|val: &mut $ty| assert_eq!(*val, 11));
+            test.num().add(10);
+            // SAFETY: No other usage of `test`
+            unsafe { test.get(CpuGuard::new()) }.with(|val: &mut $ty| assert_eq!(*val, 21));
+            test.num().sub(5);
+            // SAFETY: No other usage of `test`
+            unsafe { test.get(CpuGuard::new()) }.with(|val: &mut $ty| assert_eq!(*val, 16));
+        }
+    };
+}
+
 impl kernel::Module for PerCpuTestModule {
     fn init(_module: &'static ThisModule) -> Result<Self, Error> {
         pr_info!("rust percpu test start\n");
@@ -94,6 +114,22 @@ fn init(_module: &'static ThisModule) -> Result<Self, Error> {
 
         pr_info!("rust dynamic percpu test done\n");
 
+        pr_info!("rust numeric optimizations test start\n");
+
+        make_optimization_test!(u8);
+        make_optimization_test!(u16);
+        make_optimization_test!(u32);
+        make_optimization_test!(u64);
+        make_optimization_test!(usize);
+
+        make_optimization_test!(i8);
+        make_optimization_test!(i16);
+        make_optimization_test!(i32);
+        make_optimization_test!(i64);
+        make_optimization_test!(isize);
+
+        pr_info!("rust numeric optimizations test done\n");
+
         // Return Err to unload the module
         Result::Err(EINVAL)
     }
rust/kernel/percpu.rs +1 -0
index a912f74349e0..9659bff2d889 100644
@@ -2,6 +2,7 @@
 //! This module contains abstractions for creating and using per-CPU variables from Rust.
 //! See the define_per_cpu! macro and the PerCpu<T> type.
 pub mod cpu_guard;
+pub mod numeric;
 
 use bindings::{alloc_percpu, free_percpu};
 
rust/kernel/percpu/numeric.rs new file +117 -0

117 lines not on this page · raw message

Signature · 1 line
2.34.1
#6 lore raw

Re: [PATCH 4/5] rust: percpu: Add pin-hole optimizations for numerics

Quoted 4 lines · Mitchell Levy

The C implementations of this_cpu_add, this_cpu_sub, etc., are optimized to save an instruction by avoiding having to compute this_cpu_ptr(&x) for some per-CPU variable x. For example, rather than

Cool. Great progress for Rust support. Maybe we can switch the SLUB allocator over or come up with SLRB for the Slab Rust allocator ;-)

rust/kernel/percpu/numeric.rs line 85

Quoted 10 lines
rust/kernel/percpu/numeric.rs new file +10 -0
+        impl PerCpuNumeric<'_, $ty> {
+            /// Adds `rhs` to the per-CPU variable.
+            pub fn add(&mut self, rhs: $ty) {
+                // SAFETY: `self.ptr.0` is a valid offset into the per-CPU area (i.e., valid as a
+                // pointer relative to the `gs` segment register) by the invariants of PerCpu.
+                unsafe {
+                    asm!(
+                        concat!("add gs:[{off}], {val}"),
+                        off = in(reg) self.ptr.0 as *mut $ty,
+                        val = in(reg_byte) rhs,

That looks arch specific to x86? What about ARM and other platforms?

#7 lore raw

Re: [PATCH 4/5] rust: percpu: Add pin-hole optimizations for numerics

Quoted 9 lines · Christoph Lameter (Ampere)
Quoted 4 lines · Mitchell Levy

The C implementations of this_cpu_add, this_cpu_sub, etc., are optimized to save an instruction by avoiding having to compute this_cpu_ptr(&x) for some per-CPU variable x. For example, rather than

Cool. Great progress for Rust support. Maybe we can switch the SLUB allocator over or come up with SLRB for the Slab Rust allocator ;-)

Thank you!

I'm certainly very excited about the prospect of more Rust :)

Quoted 12 lines

rust/kernel/percpu/numeric.rs line 85

Quoted 11 lines
rust/kernel/percpu/numeric.rs new file +10 -0
+        impl PerCpuNumeric<'_, $ty> {
+            /// Adds `rhs` to the per-CPU variable.
+            pub fn add(&mut self, rhs: $ty) {
+                // SAFETY: `self.ptr.0` is a valid offset into the per-CPU area (i.e., valid as a
+                // pointer relative to the `gs` segment register) by the invariants of PerCpu.
+                unsafe {
+                    asm!(
+                        concat!("add gs:[{off}], {val}"),
+                        off = in(reg) self.ptr.0 as *mut $ty,
+                        val = in(reg_byte) rhs,
Quoted 1 line

That looks arch specific to x86? What about ARM and other platforms?

Yes; pretty much everything added by this series is x86_64 specific. In rust/kernel/lib.rs the whole percpu module is gated behind #[cfg(CONFIG_X86_64)].

I'm certainly interested in adding support for ARM and other architectures. That said, x86 is where I started, and since it's in a workable state, I wanted to get some input from the list.

#8 lore raw

Re: [PATCH 4/5] rust: percpu: Add pin-hole optimizations for numerics

In reply to Mitchell Levy

rust/kernel/percpu/numeric.rs line 97

Quoted 5 lines · Mitchell Levy
rust/kernel/percpu/numeric.rs new file +3 -0
+                        concat!("add gs:[{off}], {val:", $reg, "}"),
 
+                        concat!("sub gs:[{off}], {val:", $reg, "}"),
 
+                        concat!("sub gs:[{off}], {val}"),

Where are the other RMV instructions like this_cpu_xchg and this_cpu_cmpxchg, this_cpu_cmpxchg_double etc?

#9 lore raw

Re: [PATCH 4/5] rust: percpu: Add pin-hole optimizations for numerics

Quoted 10 lines · Christoph Lameter (Ampere)

rust/kernel/percpu/numeric.rs line 97

Quoted 5 lines · Mitchell Levy
rust/kernel/percpu/numeric.rs new file +3 -0
+                        concat!("add gs:[{off}], {val:", $reg, "}"),
 
+                        concat!("sub gs:[{off}], {val:", $reg, "}"),
 
+                        concat!("sub gs:[{off}], {val}"),

Where are the other RMV instructions like this_cpu_xchg and this_cpu_cmpxchg, this_cpu_cmpxchg_double etc?

I wanted to stick with (what I saw as) the most basic operations for the first submission. That said, I tried to design things such that adding more operations should be straightforward. Happy to build on this further for a v2 if folks are interested in that.

#10 lore raw

PATCH 5/5 rust: percpu: cache per-CPU pointers in the dynamic case

In reply to Mitchell Levy

Currently, the creation of a PerCpuNumeric requires a memory read via the Arc managing the dynamic allocation. While the compiler might be clever enough to consolidate these reads in some cases, the read must happen *somewhere*, which, when we're concerning ourselves with individual instructions, is a very high burden.

Instead, cache the PerCpuPointer inside the DynamicPerCpu structure; then, the Arc is used solely to manage the allocation.

---
 rust/kernel/percpu.rs         | 12 +++++++++---
 rust/kernel/percpu/numeric.rs |  2 +-
 2 files changed, 10 insertions(+), 4 deletions(-)
rust/kernel/percpu.rs +9 -3
index 9659bff2d889..ffcfe02124ab 100644
@@ -23,7 +23,10 @@
 
 /// Holds a dynamically-allocated per-CPU variable.
 pub struct DynamicPerCpu<T> {
+    // INVARIANT: `ptr` is managed by `alloc` and the value of `ptr` does not change for the
+    // lifetime of `self`.
     alloc: Arc<PerCpuAllocation<T>>,
+    ptr: PerCpuPtr<T>,
 }
 
 /// Holds a statically-allocated per-CPU variable.
@@ -199,9 +202,10 @@ impl<T> DynamicPerCpu<T> {
     pub fn new(flags: Flags) -> Option<Self> {
         let alloc: PerCpuAllocation<T> = PerCpuAllocation::new()?;
 
+        let ptr = alloc.0;
         let arc = Arc::new(alloc, flags).ok()?;
 
-        Some(Self { alloc: arc })
+        Some(Self { alloc: arc, ptr })
     }
 
     /// Wraps a `PerCpuAllocation<T>` in a `PerCpu<T>`
@@ -210,8 +214,9 @@ pub fn new(flags: Flags) -> Option<Self> {
     /// * `alloc` - The allocation to use
     /// * `flags` - The flags used to allocate an `Arc` that keeps track of the `PerCpuAllocation`.
     pub fn new_from_allocation(alloc: PerCpuAllocation<T>, flags: Flags) -> Option<Self> {
+        let ptr = alloc.0;
         let arc = Arc::new(alloc, flags).ok()?;
-        Some(Self { alloc: arc })
+        Some(Self { alloc: arc, ptr })
     }
 }
 
@@ -219,7 +224,7 @@ pub fn new_from_allocation(alloc: PerCpuAllocation<T>, flags: Flags) -> Option<S
 // don't deallocate the underlying `PerCpuAllocation` until `self` is dropped.
 unsafe impl<T> PerCpu<T> for DynamicPerCpu<T> {
     unsafe fn ptr(&mut self) -> &PerCpuPtr<T> {
-        &self.alloc.0
+        &self.ptr
     }
 }
 
@@ -227,6 +232,7 @@ impl<T> Clone for DynamicPerCpu<T> {
     fn clone(&self) -> Self {
         Self {
             alloc: self.alloc.clone(),
+            ptr: self.ptr,
         }
     }
 }
rust/kernel/percpu/numeric.rs +1 -1
index e4008f872af1..1b37cc7e5c19 100644
@@ -62,7 +62,7 @@ impl DynamicPerCpu<$ty> {
             /// Returns a `PerCpuNumeric` that can be used to manipulate the underlying per-CPU
             /// variable.
             pub fn num(&self) -> PerCpuNumeric<'_, $ty> {
-                PerCpuNumeric { ptr: &self.alloc.0 }
+                PerCpuNumeric { ptr: &self.ptr }
             }
         }
         impl StaticPerCpu<$ty> {
Signature · 1 line
2.34.1