rust: Add Per-CPU Variable API
Subsystems (4)
v5 cover rust: Add Per-CPU Variable API
This series adds an API for declaring and 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 sample module, samples/rust/rust_percpu.rs, in the vein of lib/percpu_test.c.
---
---
Changes in v5:
- Rebased
- Documentation clarifications, typo fixes
- Added an early out in CpumaskIter::next to avoid WARNs under certain configs
- CpuGuard functions are now #[inline]
- PerCpuNumerics now use ptr::cast<$ty> to get a *mut $ty rather than ptr::cast<*mut $ty which gives a *mut *mut $ty.
- Changed make_optimization_test!'s CpuGuard to be bound to a name rather than _ to avoid it getting dropped immediately
- Remove DynamicPerCpu::alloc in patch 8 since it's not necessary at that point
- CheckedPerCpu::get now has a &self reciever rather than a &mut reciever.
- Consolidated the non-zeroable dynamic per-cpu support into the introductory patches
- CpuGuards were previously automatically Send and Sync, even
though that doesn't make sense for that type. So, added a PhantomData<*const ()> to prevent those from automatically being applied.
- Impl FusedIterator for the cpumask iterator
- Used the cached self.ptr in DynamicPerCpu's get and get_mut since there's no reason not to save the indirection.
- Added $crate::percpu:: to PerCpuSyncMarkerType and StaticPerCpuSymbol in the define_per_cpu! macro to avoid needing
extra imports where its used. (Thanks Andreas Hindborg)
- Cleaned up logic in CpumaskIter::next (Thanks Yury Norov)
- Link to v4: https://lore.kernel.org/r/[email protected]
Changes in v4:
- Split what was the first patch into three patches (Thanks Yury Norov)
- Add intra-doc links to rustdoc comments (Thanks Miguel Ojeda)
- get_remote_ptr is no longer unsafe (Thanks Miguel Ojeda)
- Renamed the CPU mask getters to be more clear (Thanks Yury Norov)
- Add an ExternStaticPerCpuSymbol type for static per-CPU variables shared with C in order to make the extra soundness requirements for these variables more explicit.
- Properly drop the contents of dynamically allocated per-CPUs when the DynamicPerCpu is dropped, and properly document that PerCpuAllocation does not handle dropping values in the allocation.
- Functions on the numeric token types are now #[inline]
- Safety requirements of PerCpuPtr functions now include the fact that the pointed-to allocation must be live and appropriately laid out in memory, and SAFETY comments of uses of these functions have been
updated to reflect these requirements.
- Included the cpumask patches first, since it might make sense for
these to be merged separately.
- Documentation improvements throughout.
- Link to v3: https://lore.kernel.org/r/[email protected]
Changes in v3:
- Add a CheckedPerCpuToken that enables usage of per-CPU variables via
a &T, allowing for a wholly safe interface when T allows for interior mutability (Thanks Benno Lossin)
- Add support for non-zeroable types to be used in a DynamicPerCpu.
- Remove necessity for unsafe to get a StaticPerCpu from its declaration (Thanks Benno Lossin)
- Allow the declaration of static per-CPU variables of types that are !Sync.
- Implement PerCpuPtr in terms of MaybeUninit<T> rather than T so
as to keep all invariants in the DynamicPerCpu and StaticPerCpu types --- this would also enable PerCpuPtr to be used in a per-CPU type that does lazy initialization.
- Link to v2: https://lore.kernel.org/r/[email protected]
Changes in v2:
- Fix kernel test robot issues
- Fix documentation error
- Require T: Zeroable in the dynamic case
- Link to v1: 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]
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]
---
Mitchell Levy (8):
rust: cpumask: Add a Cpumask iterator
rust: cpumask: Add getters for globally defined cpumasks
rust: percpu: Add C bindings for per-CPU variable API
rust: percpu: introduce a rust API for static per-CPU variables
rust: percpu: introduce a rust API for dynamic per-CPU variables
rust: percpu: add a rust per-CPU variable sample
rust: percpu: Add pin-hole optimizations for numerics
rust: percpu: cache per-CPU pointers in the dynamic case
rust/helpers/cpumask.c | 6 + rust/helpers/helpers.c | 2 + rust/helpers/percpu.c | 22 +++ rust/helpers/preempt.c | 15 ++ rust/kernel/cpumask.rs | 97 ++++++++++++- rust/kernel/lib.rs | 3 + rust/kernel/percpu.rs | 282 ++++++++++++++++++++++++++++++++++++ rust/kernel/percpu/cpu_guard.rs | 48 ++++++ rust/kernel/percpu/dynamic.rs | 227 +++++++++++++++++++++++++++++ rust/kernel/percpu/numeric.rs | 138 ++++++++++++++++++ rust/kernel/percpu/static_.rs | 218 ++++++++++++++++++++++++++++ samples/rust/Kconfig | 9 ++ samples/rust/Makefile | 1 + samples/rust/rust_percpu.rs | 314 ++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1381 insertions(+), 1 deletion(-)
---
base-commit: 8a23051ed8584215b22368e9501f771ef98f0c1d
change-id: 20240813-rust-percpu-ea2f54b5da33
Best regards,
Signature · 1 line
Mitchell Levy <[email protected]>
PATCH v5 1/8 rust: cpumask: Add a `Cpumask` iterator
Add an iterator for Cpumask making use of C's cpumask_next.
--- rust/helpers/cpumask.c | 6 ++++++ rust/kernel/cpumask.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-)
rust/helpers/cpumask.c +6 -0
@@ -50,6 +50,12 @@ bool rust_helper_cpumask_full(struct cpumask *srcp)return cpumask_full (srcp); } +__rust_helper +unsigned int rust_helper_cpumask_next(int n, struct cpumask *srcp) +{ +return cpumask_next (n, srcp); +} +__rust_helper unsigned int rust_helper_cpumask_weight(struct cpumask *srcp) {
rust/kernel/cpumask.rs +50 -1
63 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v5 2/8 rust: cpumask: Add getters for globally defined cpumasks
In reply to Mitchell LevyAdd getters for the global cpumasks documented ininclude/linux/cpumask.h, specifically:
- cpu_possible_mask
- cpu_online_mask
- cpu_enabled_mask
- cpu_present_mask
- cpu_active_mask
--- rust/kernel/cpumask.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+)
rust/kernel/cpumask.rs +46 -0
@@ -80,6 +80,52 @@ pub unsafe fn from_raw<'a>(ptr: *const bindings::cpumask) -> &'a Self {unsafe { &*ptr.cast () } } +/// Get a CPU mask representing possible CPUs; has bit `cpu` set iff cpu is populatable +#[inline] +pub fn possible_cpus () -> &'staticSelf { +// SAFETY: `__cpu_possible_mask` is a valid global provided by the kernel that lives +// forever. +unsafe {Cpumask ::from_raw (&raw const bindings::__cpu_possible_mask) } + } + +/// Get a CPU mask representing online CPUs; has bit `cpu` set iff cpu available to the +/// scheduler +#[inline] +pub fn online_cpus () -> &'staticSelf { +// SAFETY: `__cpu_online_mask` is a valid global provided by the kernel that lives forever. +// Since we wrap the returned pointer in an `Opaque`, it's ok that `__cpu_online_mask` +// may change its value. +unsafe {Cpumask ::from_raw (&raw const bindings::__cpu_online_mask) } + } + +/// Get a CPU mask representing enabled CPUs; has bit `cpu` set iff cpu can be brought online +#[inline] +pub fn enabled_cpus () -> &'staticSelf { +// SAFETY: `__cpu_enabled_mask` is a valid global provided by the kernel that lives forever. +// Since we wrap the returned pointer in an `Opaque`, it's ok that `__cpu_enabled_mask` +// may change its value. +unsafe {Cpumask ::from_raw (&raw const bindings::__cpu_enabled_mask) } + } + +/// Get a CPU mask representing present CPUs; has bit `cpu` set iff cpu is populated +#[inline] +pub fn present_cpus () -> &'staticSelf { +// SAFETY: `__cpu_present_mask` is a valid global provided by the kernel that lives +// forever. Since we wrap the returned pointer in an `Opaque`, it's ok that +// `__cpu_present_mask` may change its value. +unsafe {Cpumask ::from_raw (&raw const bindings::__cpu_present_mask) } + } + +/// Get a CPU mask representing active CPUs; has bit `cpu` set iff cpu is available to +/// migration. +#[inline] +pub fn active_cpus () -> &'staticSelf { +// SAFETY: `__cpu_active_mask` is a valid global provided by the kernel that lives forever. +// Since we wrap the returned pointer in an `Opaque`, it's ok that `__cpu_active_mask` +// may change its value. +unsafe {Cpumask ::from_raw (&raw const bindings::__cpu_active_mask) } + } +/// Obtain the raw `struct cpumask` pointer. pub fn as_raw (&self) -> *mut bindings::cpumask {let this: *const Self = self;
Signature · 1 line
2.34.1
PATCH v5 3/8 rust: percpu: Add C bindings for per-CPU variable API
In reply to Mitchell LevyAdd bindings necessary to implement a Rust per-CPU variable API, specifically per-CPU variable allocation and management of CPU preemption.
--- rust/helpers/helpers.c | 2 ++ rust/helpers/percpu.c | 9 +++++++++ rust/helpers/preempt.c | 15 +++++++++++++++ 3 files changed, 26 insertions(+)
rust/helpers/helpers.c +2 -0
@@ -70,9 +70,11 @@#include "of.c" #include "page.c" #include "pci.c" +#include "percpu.c" #include "pid_namespace.c" #include "platform.c" #include "poll.c" +#include "preempt.c" #include "processor.c" #include "property.c" #include "pwm.c"
rust/helpers/percpu.c new file +9 -0
@@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/percpu.h> + +__rust_helper +void __percpu *rust_helper_alloc_percpu(size_t sz, size_t align) +{ +return __alloc_percpu (sz, align); +}
rust/helpers/preempt.c new file +15 -0
@@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/preempt.h> + +__rust_helper +void rust_helper_preempt_disable(void) +{ +preempt_disable (); +} + +__rust_helper +void rust_helper_preempt_enable(void) +{ +preempt_enable (); +}
Signature · 1 line
2.34.1
PATCH v5 4/8 rust: percpu: introduce a rust API for static per-CPU variables
In reply to Mitchell LevyPer-CPU variables are an important tool for reducing lock contention, especially in systems with many processors. They also provide a convenient way to handle data that are logically associated with a particular CPU (e.g., the currently running task).
Therefore, add a Rust API to make use of statically-allocated per-CPU variables. Add a RAII CpuGuard type for disabling CPU preemption. Introduce unifying abstractions that can be reused for a Rust API for dynamically-allocated per-CPU variables.
---
Using rustc 1.83.0, I can confirm that the symbols __INIT_$id are optimized out by the compiler and do not appear in the final .ko file when compiling samples/rust/rust_percpu.rs (introduced in a later patch in this series).
--- rust/kernel/lib.rs | 3 + rust/kernel/percpu.rs | 259 ++++++++++++++++++++++++++++++++++++++++ rust/kernel/percpu/cpu_guard.rs | 48 ++++++++ rust/kernel/percpu/static_.rs | 218 +++++++++++++++++++++++++++++++++ 4 files changed, 528 insertions(+)
rust/kernel/lib.rs +3 -0
@@ -97,6 +97,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 +259 -0
259 lines not on this page · raw message
rust/kernel/percpu/cpu_guard.rs new file +48 -0
48 lines not on this page · raw message
rust/kernel/percpu/static_.rs new file +218 -0
218 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v5 5/8 rust: percpu: introduce a rust API for dynamic per-CPU variables
In reply to Mitchell LevyDynamically allocated per-CPU variables are core to many of the use-cases of per-CPU variables (e.g., ref counting). Add support for them using the core PerCpuPtr<T> primitive, implementing the PerCpu<T> and CheckedPerCpu<T> traits.
--- rust/helpers/percpu.c | 12 +++ rust/kernel/percpu.rs | 30 +++++- rust/kernel/percpu/dynamic.rs | 217 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+), 4 deletions(-)
rust/helpers/percpu.c +12 -0
@@ -7,3 +7,15 @@ void __percpu *rust_helper_alloc_percpu(size_t sz, size_t align) {return __alloc_percpu (sz, align); } + +__rust_helper +void *rust_helper_per_cpu_ptr(void __percpu *ptr, unsigned int cpu) +{ +return per_cpu_ptr (ptr, cpu); +} + +__rust_helper +void rust_helper_on_each_cpu(smp_call_func_t func, void *info, int wait) +{ +on_each_cpu (func, info, wait); +}
rust/kernel/percpu.rs +26 -4
@@ -1,15 +1,20 @@// SPDX-License-Identifier: GPL-2.0 //! Per-CPU variables. //! -//! See the [`crate::define_per_cpu!`] macro and the [`PerCpu<T>`] trait. +//! See the [`crate::define_per_cpu!`] macro, the [`DynamicPerCpu`] type, and the [`PerCpu<T>`] +//! trait. pub mod cpu_guard; +mod dynamic;mod static_; +#[doc(inline)] +pub use dynamic::*;#[doc(inline)] pub use static_::*;use crate ::{ + cpu::CpuId , declare_extern_per_cpu, percpu::cpu_guard::CpuGuard , types::Opaque ,//
@@ -132,6 +137,23 @@ pub fn get_ptr(&self) -> *mut MaybeUninit<T> {// the invariant that self.0 is a valid offset into the per-CPU area. (this_cpu_area).wrapping_add (self.0 as usize ).cast () } + +/// Get a [`*mut MaybeUninit<T>`](MaybeUninit) to the per-CPU variable on the CPU represented +/// by `cpu`. Note that without some kind of synchronization, use of the returned pointer may +/// cause a data race. It is the caller's responsibility to use the returned pointer in a +/// reasonable way. +/// +/// # Returns +/// - The returned pointer is valid only if `self` is (that is, it points to a live allocation +/// correctly sized and aligned to hold a `T`) +/// - The returned pointer is valid only if the bit corresponding to `cpu` is set in +/// [`kernel::cpumask::Cpumask::possible_cpus()`]. +pub fn get_remote_ptr (&self, cpu:CpuId ) -> *mut MaybeUninit <T > { +// SAFETY: `bindings::per_cpu_ptr` is just doing pointer arithmetic. The returned pointer +// may not be valid (under the conditions specified in this function's documentation), but +// the act of producing the pointer is safe. +unsafe { bindings::per_cpu_ptr (self.0 .cast (), cpu.as_u32 ()) }.cast () + } }// SAFETY: Sending a [`PerCpuPtr<T>`] to another thread is safe because as soon as it's sent, the
@@ -155,9 +177,9 @@ impl<T> Copy for PerCpuPtr<T> {}/// A trait representing a per-CPU variable. /// -/// This is implemented for [`StaticPerCpu<T>`]. The main usage of this trait is to call -/// [`Self::get_mut`] to get a [`PerCpuToken`] that can be used to access the underlying per-CPU -/// variable. +/// This is implemented for both [`StaticPerCpu<T>`] and [`DynamicPerCpu<T>`]. The main usage of +/// this trait is to call [`Self::get_mut`] to get a [`PerCpuToken`] that can be used to access the +/// underlying per-CPU variable. /// /// See [`PerCpuToken::with`]. pub trait PerCpu <T > {
rust/kernel/percpu/dynamic.rs new file +217 -0
217 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v5 6/8 rust: percpu: add a rust per-CPU variable sample
In reply to Mitchell LevyAdd a short exercise for Rust's per-CPU variable API, modelled after lib/percpu_test.c
--- rust/helpers/percpu.c | 1 + rust/kernel/percpu.rs | 2 +- samples/rust/Kconfig | 9 ++ samples/rust/Makefile | 1 + samples/rust/rust_percpu.rs | 278 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 1 deletion(-)
rust/helpers/percpu.c +1 -0
@@ -1,6 +1,7 @@// SPDX-License-Identifier: GPL-2.0 #include <linux/percpu.h> +#include <linux/smp.h> __rust_helper void __percpu *rust_helper_alloc_percpu(size_t sz, size_t align)
rust/kernel/percpu.rs +1 -1
@@ -2,7 +2,7 @@//! Per-CPU variables. //! //! See the [`crate::define_per_cpu!`] macro, the [`DynamicPerCpu`] type, and the [`PerCpu<T>`] -//! trait. +//! trait. Example usage can be found in `samples/rust/rust_percpu.rs`. pub mod cpu_guard;mod dynamic;
samples/rust/Kconfig +9 -0
@@ -172,6 +172,15 @@ config SAMPLE_RUST_SOC If unsure, say N. +config SAMPLE_RUST_PERCPU + tristate "Per-CPU support" + depends on m + help + Enable this option to build a module which demonstrates Rust per-CPU + operations. + + If unsure, say N. + config SAMPLE_RUST_HOSTPROGS bool "Host programs" help
samples/rust/Makefile +1 -0
@@ -16,6 +16,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) += rust_driver_auxiliary.o obj-$(CONFIG_SAMPLE_RUST_CONFIGFS) += rust_configfs.o obj-$(CONFIG_SAMPLE_RUST_SOC) += rust_soc.o +obj-$(CONFIG_SAMPLE_RUST_PERCPU) += rust_percpu.o rust_print-y := rust_print_main.o rust_print_events.o
samples/rust/rust_percpu.rs new file +278 -0
278 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v5 7/8 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.
--- rust/kernel/percpu.rs | 1 + rust/kernel/percpu/dynamic.rs | 10 ++- rust/kernel/percpu/numeric.rs | 138 ++++++++++++++++++++++++++++++++++++++++++ samples/rust/rust_percpu.rs | 36 +++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-)
rust/kernel/percpu.rs +1 -0
@@ -6,6 +6,7 @@pub mod cpu_guard;mod dynamic; +pub mod numeric;mod static_;#[doc(inline)]
rust/kernel/percpu/dynamic.rs +9 -1
@@ -28,7 +28,7 @@/// the memory location on any particular CPU has been initialized. This means that it cannot tell /// whether it should drop the *contents* of the allocation when it is dropped. It is up to the /// user to do this via something like [`core::ptr::drop_in_place`]. -pub struct PerCpuAllocation <T >(PerCpuPtr <T >); +pub struct PerCpuAllocation <T >(pub (super )PerCpuPtr <T >);impl <T :Zeroable >PerCpuAllocation <T > {/// Dynamically allocates a space in the per-CPU area suitably sized and aligned to hold a `T`,
@@ -162,6 +162,14 @@ pub fn new_from(mut initer: impl FnMut(CpuId) -> T, flags: Flags) -> Option<Self } } +impl <T >DynamicPerCpu <T > { +/// Gets the allocation backing this per-CPU variable. +pub (crate )fn alloc (&self) -> &Arc <PerCpuAllocation <T >> { +// SAFETY: This type's invariant ensures that `self.alloc` is `Some`. +unsafe { self.alloc.as_ref ().unwrap_unchecked () } + } +} +impl <T >PerCpu <T >for DynamicPerCpu <T > {unsafe fn get_mut (&mut self, guard:CpuGuard ) ->PerCpuToken <'_,T > {// SAFETY:
rust/kernel/percpu/numeric.rs new file +138 -0
138 lines not on this page · raw message
samples/rust/rust_percpu.rs +36 -0
@@ -28,6 +28,26 @@define_per_cpu! (UPERCPU :u64 =0 );define_per_cpu! (CHECKED :RefCell <u64 > =RefCell ::new (0 )); +macro_rules! make_optimization_test { + ($ty:ty) => { +let mut test:DynamicPerCpu <$ty> =DynamicPerCpu ::new_zero (GFP_KERNEL ).unwrap (); + { +let _guard =CpuGuard ::new (); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty| *val =10 ); + test.num ().add (1 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,11 )); + test.num ().add (10 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,21 )); + test.num ().sub (5 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,16 )); + } + }; +} +impl kernel::Module for PerCpuMod {fn init (_module: &'staticThisModule ) ->Result <Self ,Error > {pr_info! ("rust percpu test start\n" );
@@ -228,6 +248,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 ) }
Signature · 1 line
2.34.1
Re: [PATCH v5 7/8] rust: percpu: Add pin-hole optimizations for numerics
Quoted 111 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 thanu64 *x_ptr = this_cpu_ptr(&x); *x_ptr += 5;the implementation of
this_cpu_addis 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&xis already inrax)add gs:[rax], 5Add this optimization via a
PerCpuNumerictype to enable code-reuse betweenDynamicPerCpuandStaticPerCpu.Signed-off-by Mitchell Levy <[email protected]>--- rust/kernel/percpu.rs | 1 + rust/kernel/percpu/dynamic.rs | 10 ++- rust/kernel/percpu/numeric.rs | 138 ++++++++++++++++++++++++++++++++++++++++++ samples/rust/rust_percpu.rs | 36 +++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-)rust/kernel/percpu.rs +1
-0@@ -6,6 +6,7 @@pub mod cpu_guard;mod dynamic; +pub mod numeric;mod static_;#[doc(inline)] rust/kernel/percpu/dynamic.rs +9
-1@@ -28,7 +28,7 @@/// the memory location on any particular CPU has been initialized. This means that it cannot tell /// whether it should drop the *contents* of the allocation when it is dropped. It is up to the /// user to do this via something like [`core::ptr::drop_in_place`]. -pub struct PerCpuAllocation <T >(PerCpuPtr <T >); +pub struct PerCpuAllocation <T >(pub (super )PerCpuPtr <T >);impl <T :Zeroable >PerCpuAllocation <T > {/// Dynamically allocates a space in the per-CPU area suitably sized and aligned to hold a `T`, @@ -162,6 +162,14 @@ pub fn new_from(mut initer: impl FnMut(CpuId) -> T, flags: Flags) -> Option<Self } } +impl <T >DynamicPerCpu <T > { +/// Gets the allocation backing this per-CPU variable. +pub (crate )fn alloc (&self) -> &Arc <PerCpuAllocation <T >> { +// SAFETY: This type's invariant ensures that `self.alloc` is `Some`. +unsafe { self.alloc.as_ref ().unwrap_unchecked () } + } +} +impl <T >PerCpu <T >for DynamicPerCpu <T > {unsafe fn get_mut (&mut self, guard:CpuGuard ) ->PerCpuToken <'_,T > {// SAFETY: rust/kernel/percpu/numeric.rs new file +39
-0@@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +//! Pin-hole optimizations for [`PerCpu<T>`] where T is a numeric type. + +use super ::*; +use core::arch::asm; + +/// Represents a per-CPU variable that can be manipulated with machine-intrinsic numeric +/// operations. +pub struct PerCpuNumeric <'a,T > { +// INVARIANT: `ptr.0` is a valid offset into the per-CPU area and is initialized on all CPUs +// (since we don't have a CPU guard, we have to be pessimistic and assume we could be on any +// CPU). + ptr: &'aPerCpuPtr <T >, +} + +macro_rules! impl_ops { + ($ty:ty, $reg:tt) => { +impl DynamicPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `DynamicPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.alloc ().0 } + } + } +impl StaticPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `StaticPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.0 } + } + } + +impl PerCpuNumeric <'_, $ty> {
rust/kernel/percpu/numeric.rs line 49
Quoted 10 lines
rust/kernel/percpu/numeric.rs new file +10
-0+/// Adds `rhs` to the per-CPU variable. +#[inline] +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 this type. +unsafe { +asm! ( +concat! ("add gs:[{off}], {val:" , $reg,"}" ), + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg) rhs,
So, every user of .add() now will be only compilable against x86_64? I don't think it's right. Can you make it in a more convenient way:
implement a generic version, and then an x86_64-optimized.
How bad the generic x86_64 version looks comparing to the optimized one?
Thanks,
Yury
Quoted 146 lines
rust/kernel/percpu/numeric.rs new file +89
-0+ ); + } + } + } +impl PerCpuNumeric <'_, $ty> { +/// Subtracts `rhs` from the per-CPU variable. +#[inline] +pub fn sub (&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 this type. +unsafe { +asm! ( +concat! ("sub gs:[{off}], {val:" , $reg,"}" ), + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg) rhs, + ); + } + } + } + }; +} + +macro_rules! impl_ops_byte { + ($ty:ty) => { +impl DynamicPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `DynamicPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.alloc ().0 } + } + } +impl StaticPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `StaticPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.0 } + } + } + +impl PerCpuNumeric <'_, $ty> { +/// Adds `rhs` to the per-CPU variable. +#[inline] +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 this type. +unsafe { +asm! ( +"add gs:[{off}], {val}" , + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg_byte) rhs, + ); + } + } + } +impl PerCpuNumeric <'_, $ty> { +/// Subtracts `rhs` from the per-CPU variable. +#[inline] +pub fn sub (&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 this type. +unsafe { +asm! ( +"sub gs:[{off}], {val}" , + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg_byte) rhs, + ); + } + } + } + }; +} + +impl_ops_byte! (i8 ); +impl_ops! (i16 ,"x" ); +impl_ops! (i32 ,"e" ); +impl_ops! (i64 ,"r" ); +impl_ops! (isize ,"r" ); + +impl_ops_byte! (u8 ); +impl_ops! (u16 ,"x" ); +impl_ops! (u32 ,"e" ); +impl_ops! (u64 ,"r" ); +impl_ops! (usize ,"r" );samples/rust/rust_percpu.rs +36
-0@@ -28,6 +28,26 @@define_per_cpu! (UPERCPU :u64 =0 );define_per_cpu! (CHECKED :RefCell <u64 > =RefCell ::new (0 )); +macro_rules! make_optimization_test { + ($ty:ty) => { +let mut test:DynamicPerCpu <$ty> =DynamicPerCpu ::new_zero (GFP_KERNEL ).unwrap (); + { +let _guard =CpuGuard ::new (); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty| *val =10 ); + test.num ().add (1 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,11 )); + test.num ().add (10 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,21 )); + test.num ().sub (5 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,16 )); + } + }; +} +impl kernel::Module for PerCpuMod {fn init (_module: &'staticThisModule ) ->Result <Self ,Error > {pr_info! ("rust percpu test start\n" );@@ -228,6 +248,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 ) }Signature · 2 lines
-- 2.34.1
Re: [PATCH v5 7/8] rust: percpu: Add pin-hole optimizations for numerics
Quoted 127 lines · Yury Norov
Quoted 111 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 thanu64 *x_ptr = this_cpu_ptr(&x); *x_ptr += 5;the implementation of
this_cpu_addis 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&xis already inrax)add gs:[rax], 5Add this optimization via a
PerCpuNumerictype to enable code-reuse betweenDynamicPerCpuandStaticPerCpu.Signed-off-by Mitchell Levy <[email protected]>--- rust/kernel/percpu.rs | 1 + rust/kernel/percpu/dynamic.rs | 10 ++- rust/kernel/percpu/numeric.rs | 138 ++++++++++++++++++++++++++++++++++++++++++ samples/rust/rust_percpu.rs | 36 +++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-)rust/kernel/percpu.rs +1
-0@@ -6,6 +6,7 @@pub mod cpu_guard;mod dynamic; +pub mod numeric;mod static_;#[doc(inline)] rust/kernel/percpu/dynamic.rs +9
-1@@ -28,7 +28,7 @@/// the memory location on any particular CPU has been initialized. This means that it cannot tell /// whether it should drop the *contents* of the allocation when it is dropped. It is up to the /// user to do this via something like [`core::ptr::drop_in_place`]. -pub struct PerCpuAllocation <T >(PerCpuPtr <T >); +pub struct PerCpuAllocation <T >(pub (super )PerCpuPtr <T >);impl <T :Zeroable >PerCpuAllocation <T > {/// Dynamically allocates a space in the per-CPU area suitably sized and aligned to hold a `T`, @@ -162,6 +162,14 @@ pub fn new_from(mut initer: impl FnMut(CpuId) -> T, flags: Flags) -> Option<Self } } +impl <T >DynamicPerCpu <T > { +/// Gets the allocation backing this per-CPU variable. +pub (crate )fn alloc (&self) -> &Arc <PerCpuAllocation <T >> { +// SAFETY: This type's invariant ensures that `self.alloc` is `Some`. +unsafe { self.alloc.as_ref ().unwrap_unchecked () } + } +} +impl <T >PerCpu <T >for DynamicPerCpu <T > {unsafe fn get_mut (&mut self, guard:CpuGuard ) ->PerCpuToken <'_,T > {// SAFETY: rust/kernel/percpu/numeric.rs new file +39
-0@@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +//! Pin-hole optimizations for [`PerCpu<T>`] where T is a numeric type. + +use super ::*; +use core::arch::asm; + +/// Represents a per-CPU variable that can be manipulated with machine-intrinsic numeric +/// operations. +pub struct PerCpuNumeric <'a,T > { +// INVARIANT: `ptr.0` is a valid offset into the per-CPU area and is initialized on all CPUs +// (since we don't have a CPU guard, we have to be pessimistic and assume we could be on any +// CPU). + ptr: &'aPerCpuPtr <T >, +} + +macro_rules! impl_ops { + ($ty:ty, $reg:tt) => { +impl DynamicPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `DynamicPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.alloc ().0 } + } + } +impl StaticPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `StaticPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.0 } + } + } + +impl PerCpuNumeric <'_, $ty> {rust/kernel/percpu/numeric.rs line 49
Quoted 10 lines
rust/kernel/percpu/numeric.rs new file +10
-0+/// Adds `rhs` to the per-CPU variable. +#[inline] +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 this type. +unsafe { +asm! ( +concat! ("add gs:[{off}], {val:" , $reg,"}" ), + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg) rhs,So, every user of .add() now will be only compilable against
x86_64? I don't think it's right. Can you make it in a more convenient way:
implement a generic version, and then an x86_64-optimized.
Quoted 2 lines
How bad the generic
x86_64version looks comparing to the optimized one?
Currently, all of mod percpu is behind #[cfg(X86_64)], so usage of per-CPU variables in general is only compatible against x86_64.
I believe a generic implementation would require implicitly creating a CpuGuard since in general you require two steps: computing the pointer to the per-CPU variable's slot in the current CPU's area and actually doing the write. On x86_64 we can get around this because segment register relative writes let us combine these two ops into one instruction which can't be torn across CPUs. But in the general case you could have the task get preempted between those two operations and end up with a data race.
As I understand it, x86 is the only arch where this is possible, so even once mod percpu supports more architectures, I think it'd still make some sense to have PerCpuNumeric specifically be x86 exclusive. This means that the user must always explicitly disable preemption rather than having a PerCpuNumeric type that sometimes does and sometimes doesn't.
Thanks,
Mitchell
Quoted 149 lines
Thanks,
YuryQuoted 146 lines
rust/kernel/percpu/numeric.rs new file +89
-0+ ); + } + } + } +impl PerCpuNumeric <'_, $ty> { +/// Subtracts `rhs` from the per-CPU variable. +#[inline] +pub fn sub (&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 this type. +unsafe { +asm! ( +concat! ("sub gs:[{off}], {val:" , $reg,"}" ), + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg) rhs, + ); + } + } + } + }; +} + +macro_rules! impl_ops_byte { + ($ty:ty) => { +impl DynamicPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `DynamicPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.alloc ().0 } + } + } +impl StaticPerCpu <$ty> { +/// Returns a [`PerCpuNumeric`] that can be used to manipulate the underlying per-CPU +/// variable. +#[inline] +pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> { +// The invariant is satisfied because `StaticPerCpu`'s invariant guarantees that +// this pointer is valid and initialized on all CPUs. +PerCpuNumeric { ptr: &self.0 } + } + } + +impl PerCpuNumeric <'_, $ty> { +/// Adds `rhs` to the per-CPU variable. +#[inline] +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 this type. +unsafe { +asm! ( +"add gs:[{off}], {val}" , + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg_byte) rhs, + ); + } + } + } +impl PerCpuNumeric <'_, $ty> { +/// Subtracts `rhs` from the per-CPU variable. +#[inline] +pub fn sub (&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 this type. +unsafe { +asm! ( +"sub gs:[{off}], {val}" , + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg_byte) rhs, + ); + } + } + } + }; +} + +impl_ops_byte! (i8 ); +impl_ops! (i16 ,"x" ); +impl_ops! (i32 ,"e" ); +impl_ops! (i64 ,"r" ); +impl_ops! (isize ,"r" ); + +impl_ops_byte! (u8 ); +impl_ops! (u16 ,"x" ); +impl_ops! (u32 ,"e" ); +impl_ops! (u64 ,"r" ); +impl_ops! (usize ,"r" );samples/rust/rust_percpu.rs +36
-0@@ -28,6 +28,26 @@define_per_cpu! (UPERCPU :u64 =0 );define_per_cpu! (CHECKED :RefCell <u64 > =RefCell ::new (0 )); +macro_rules! make_optimization_test { + ($ty:ty) => { +let mut test:DynamicPerCpu <$ty> =DynamicPerCpu ::new_zero (GFP_KERNEL ).unwrap (); + { +let _guard =CpuGuard ::new (); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty| *val =10 ); + test.num ().add (1 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,11 )); + test.num ().add (10 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,21 )); + test.num ().sub (5 ); +// SAFETY: No other usage of `test` +unsafe { test.get_mut (CpuGuard ::new ()) }.with (|val: &mut $ty|assert_eq! (*val,16 )); + } + }; +} +impl kernel::Module for PerCpuMod {fn init (_module: &'staticThisModule ) ->Result <Self ,Error > {pr_info! ("rust percpu test start\n" );@@ -228,6 +248,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 ) }Signature · 2 lines
-- 2.34.1
Re: [PATCH v5 7/8] rust: percpu: Add pin-hole optimizations for numerics
Quoted 28 lines · Mitchell Levy
Quoted 27 lines · Yury Norov
Quoted 26 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 thanu64 *x_ptr = this_cpu_ptr(&x); *x_ptr += 5;the implementation of
this_cpu_addis 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&xis already inrax)add gs:[rax], 5Add this optimization via a
PerCpuNumerictype to enable code-reuse betweenDynamicPerCpuandStaticPerCpu.Signed-off-by Mitchell Levy <[email protected]>--- rust/kernel/percpu.rs | 1 + rust/kernel/percpu/dynamic.rs | 10 ++- rust/kernel/percpu/numeric.rs | 138 ++++++++++++++++++++++++++++++++++++++++++ samples/rust/rust_percpu.rs | 36 +++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-)
...
Quoted 16 lines
Quoted 16 lines
rust/kernel/percpu/numeric.rs line 49
Quoted 11 lines
rust/kernel/percpu/numeric.rs new file +11
-0+impl PerCpuNumeric <'_, $ty> { +/// Adds `rhs` to the per-CPU variable. +#[inline] +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 this type. +unsafe { +asm! ( +concat! ("add gs:[{off}], {val:" , $reg,"}" ), + off = in(reg) self.ptr.0.cast::<$ty>(), + val = in(reg) rhs,So, every user of .add() now will be only compilable against
x86_64? I don't think it's right. Can you make it in a more convenient way:
implement a generic version, and then an x86_64-optimized.
Quoted 5 lines
Quoted 2 lines
How bad the generic
x86_64version looks comparing to the optimized one?Currently, all of
mod percpuis behind#[cfg(X86_64)], so usage of per-CPU variables in general is only compatible againstx86_64.
Yes, and seemingly for no good reason. The only assembler function in that patch (#4) is the get_ptr(), and to me it's quite easy to get it implemented it in C.
Quoted 8 lines
I believe a generic implementation would require implicitly creating a
CpuGuardsince in general you require two steps: computing the pointer to the per-CPU variable's slot in the current CPU's area and actually doing the write. Onx86_64we can get around this because segment register relative writes let us combine these two ops into one instruction which can't be torn across CPUs. But in the general case you could have the task get preempted between those two operations and end up with a data race.
Yes, you need CpuGuard to protect the .add(), and there's nothing wrong with that. Especially if you provide better alternative for the x86.
Quoted 6 lines
As I understand it, x86 is the only arch where this is possible, so even once
mod percpusupports more architectures, I think it'd still make some sense to havePerCpuNumericspecifically be x86 exclusive. This means that the user must always explicitly disable preemption rather than having aPerCpuNumerictype that sometimes does and sometimes doesn't.
GS register is the x86-only feature, but per-CPU variables are not. In the mother kernel, we implement them for all architectures. For x86, we've got the effective arch implementation where needed. But the generic one is the first and foremost. Refer raw_cpu_ptr() for example.
Have you checked performance difference of generic vs arch per-CPU API on your workload? Is there any measurable difference? If not, I'd rather focus on the generic version.
If you prefer the arch one, it's OK, after all. But can you please put it under rust/arch/x86_64 please? And your driver, correspondingly, would be hosted there as well.
Looking in the rust codebase, I see, it uses assembler inlines for things like compile-time barriers, labels, sections. Those are features of assembler language, a generic thing.
You're the first trying to add something really arch-specific, unless I overlooked something. So please, either stay (happy) on the generic ground, or create all the required infrastructure for architectures, like the mother kernel does. Particularly, such a basic thing like per-CPU API should be available for all architectures.
Thanks,
Yury
PATCH v5 8/8 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.
We might as well also use this speed-up in the standard get and get_mut accessors that give a {Checked,}PerCpuToken
--- rust/kernel/percpu/dynamic.rs | 34 ++++++++++++++++++---------------- rust/kernel/percpu/numeric.rs | 4 ++-- 2 files changed, 20 insertions(+), 18 deletions(-)
rust/kernel/percpu/dynamic.rs +18 -16
@@ -101,6 +101,9 @@ pub struct DynamicPerCpu<T> {// INVARIANT: The memory location in each CPU's per-CPU area pointed at by the alloc is // initialized. alloc: Option<Arc<PerCpuAllocation<T>>>, +// INVARIANT: `ptr` is the per-CPU pointer managed by `alloc`, which does not change for the +// lifetime of `self`. + pub(super) ptr: PerCpuPtr<T>, }impl <T :Zeroable >DynamicPerCpu <T > {
@@ -112,9 +115,13 @@ impl<T: Zeroable> DynamicPerCpu<T> {pub fn new_zero (flags:Flags ) ->Option <Self > {let alloc:PerCpuAllocation <T > =PerCpuAllocation ::new_zero ()?; +let ptr = alloc.0 ;let arc =Arc ::new (alloc, flags).ok ()?; -Some (Self { alloc:Some (arc) }) +Some (Self { + alloc:Some (arc), + ptr, + }) } }
@@ -158,15 +165,10 @@ pub fn new_from(mut initer: impl FnMut(CpuId) -> T, flags: Flags) -> Option<Self } } -Some (Self { alloc:Some (arc) }) - } -} - -impl <T >DynamicPerCpu <T > { -/// Gets the allocation backing this per-CPU variable. -pub (crate )fn alloc (&self) -> &Arc <PerCpuAllocation <T >> { -// SAFETY: This type's invariant ensures that `self.alloc` is `Some`. -unsafe { self.alloc.as_ref ().unwrap_unchecked () } +Some (Self { + alloc:Some (arc), + ptr, + }) } }
@@ -178,11 +180,11 @@ unsafe fn get_mut(&mut self, guard: CpuGuard) -> PerCpuToken<'_, T> {// exists on the current CPU. // 3. The invariants of `DynamicPerCpu` ensure that the contents of the allocation are // initialized on each CPU. -// 4. The existence of a reference to the `PerCpuAllocation` ensures that the allocation is -// live. +// 4. `&mut self` holds a reference to the `PerCpuAllocation`, so the allocation it manages +// is live. // 5. The invariants of `DynamicPerCpu` ensure that the allocation is sized and aligned for // a `T`. -unsafe {PerCpuToken ::new (guard, &self.alloc.as_ref ().unwrap_unchecked ().0 ) } +unsafe {PerCpuToken ::new (guard, &self.ptr) } } }
@@ -192,11 +194,11 @@ fn get(&self, guard: CpuGuard) -> CheckedPerCpuToken<'_, T> {// 1. Invariants of this type assure that `alloc` is `Some`. // 2. The invariants of `DynamicPerCpu` ensure that the contents of the allocation are // initialized on each CPU. -// 3. The existence of a reference to the `PerCpuAllocation` ensures that the allocation is -// live. +// 3. `&mut self` holds a reference to the `PerCpuAllocation`, so the allocation it manages +// is live. // 4. The invariants of `DynamicPerCpu` ensure that the allocation is sized and aligned for // a `T`. -unsafe {CheckedPerCpuToken ::new (guard, &self.alloc.as_ref ().unwrap_unchecked ().0 ) } +unsafe {CheckedPerCpuToken ::new (guard, &self.ptr) } } }
rust/kernel/percpu/numeric.rs +2 -2
@@ -22,7 +22,7 @@ impl DynamicPerCpu<$ty> {pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> {// The invariant is satisfied because `DynamicPerCpu`'s invariant guarantees that // this pointer is valid and initialized on all CPUs. -PerCpuNumeric { ptr: &self.alloc ().0 } +PerCpuNumeric { ptr: &self.ptr } } }impl StaticPerCpu <$ty> {
@@ -78,7 +78,7 @@ impl DynamicPerCpu<$ty> {pub fn num (&mut self) ->PerCpuNumeric <'_, $ty> {// The invariant is satisfied because `DynamicPerCpu`'s invariant guarantees that // this pointer is valid and initialized on all CPUs. -PerCpuNumeric { ptr: &self.alloc ().0 } +PerCpuNumeric { ptr: &self.ptr } } }impl StaticPerCpu <$ty> {
Signature · 1 line
2.34.1