rust: Add Per-CPU Variable API
Subsystems (4)
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]>
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
kernel test robot
build botRe: [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
PATCH 2/5 rust: rust-analyzer: add lib to dirs searched for crates
In reply to Mitchell LevyWhen 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
@@ -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
PATCH 3/5 rust: percpu: add a rust per-CPU variable test
In reply to Mitchell LevyAdd 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
@@ -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
@@ -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
@@ -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
PATCH 4/5 rust: percpu: Add pin-hole optimizations for numerics
In reply to Mitchell LevyThe 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
@@ -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: &'staticThisModule ) ->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
@@ -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
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 computethis_cpu_ptr(&x)for some per-CPU variablex. 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?
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 computethis_cpu_ptr(&x)for some per-CPU variablex. For example, rather thanCool. 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.
Re: [PATCH 4/5] rust: percpu: Add pin-hole optimizations for numerics
In reply to Mitchell Levyrust/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?
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_xchgandthis_cpu_cmpxchg,this_cpu_cmpxchg_doubleetc?
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.
PATCH 5/5 rust: percpu: cache per-CPU pointers in the dynamic case
In reply to Mitchell LevyCurrently, 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
@@ -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
@@ -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