Skip to contentSkip to conversation

rust: Add Per-CPU Variable API

PATCH Superseded Mitchell Levy 3 participants 20 messages Jul 12, 2025 – Jul 16, 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.

---

---
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 (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             | 315 ++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs   |  35 +++++
 rust/kernel/percpu/numeric.rs     | 117 ++++++++++++++
 scripts/generate_rust_analyzer.py |   2 +-
 11 files changed, 673 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 v2 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           | 308 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs |  35 +++++
 6 files changed, 371 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 +308 -0

308 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

PATCH v2 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
#3 lore raw

PATCH v2 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
reviewer
#4 lore raw

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

Quoted 8 lines · 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 ++++++++++++++++++++++++++++++++++++++++++++++++

I don't know if this is the correct place, the code looks much more like a sample, so why not place it there instead?

Quoted 32 lines
 rust/helpers/percpu.c   |  11 +++++
 4 files changed, 141 insertions(+)
lib/percpu_test_rust.rs new file +24 -0
new file mode 100644
index 000000000000..a9652e6ece08
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+//! A simple self test for the rust per-CPU API.
+
+use core::ffi::c_void;
+
+use kernel::{
+    bindings::{on_each_cpu, smp_processor_id},
+    define_per_cpu,
+    percpu::{cpu_guard::*, *},
+    pr_info,
+    prelude::*,
+    unsafe_get_per_cpu,
+};
+
+module! {
+    type: PerCpuTestModule,
+    name: "percpu_test_rust",
+    author: "Mitchell Levy",
+    description: "Test code to exercise the Rust Per CPU variable API",
+    license: "GPL v2",
+}
+
+struct PerCpuTestModule;
+

lib/percpu_test_rust.rs line 34

Quoted 10 lines
lib/percpu_test_rust.rs new file +10 -0
+define_per_cpu!(PERCPU: i64 = 0);
+define_per_cpu!(UPERCPU: u64 = 0);
+
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
+
+        let mut native: i64 = 0;
+        // SAFETY: PERCPU is properly defined
+        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };

I don't understand why we need unsafe here, can't we just create something specially in the define_per_cpu macro that is then confirmed by the get_per_cpu! macro and thus it can be safe?

lib/percpu_test_rust.rs line 36

Quoted 2 lines
lib/percpu_test_rust.rs new file +2 -0
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {

Hmm I also don't like the unsafe part here...

Can't we use the same API that thread_local! in the standard library has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something specifically for that case, eg tokens that are statically known to be unique etc)

---
Cheers,
Benno

Quoted 12 lines
lib/percpu_test_rust.rs new file +12 -0
+            pr_info!("The contents of pcpu are {}\n", val);
+
+            native += -1;
+            *val += -1;
+            pr_info!("Native: {}, *pcpu: {}\n", native, val);
+            assert!(native == *val && native == -1);
+
+            native += 1;
+            *val += 1;
+            pr_info!("Native: {}, *pcpu: {}\n", native, val);
+            assert!(native == *val && native == 0);
+        });
#5 lore raw

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

Quoted 4 lines · Benno Lossin
Quoted 3 lines · Mitchell Levy

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

Quoted 8 lines
Quoted 5 lines
---
 lib/Kconfig.debug       |   9 ++++
 lib/Makefile            |   1 +
 lib/percpu_test_rust.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++

I don't know if this is the correct place, the code looks much more like a sample, so why not place it there instead?

I don't feel particularly strongly either way --- I defaulted to lib/ since that's where the percpu_test.c I was working off of is located. Happy to change for v3

Quoted 38 lines
Quoted 38 lines
 rust/helpers/percpu.c   |  11 +++++
 4 files changed, 141 insertions(+)
lib/percpu_test_rust.rs new file +30 -0
new file mode 100644
index 000000000000..a9652e6ece08
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+//! A simple self test for the rust per-CPU API.
+
+use core::ffi::c_void;
+
+use kernel::{
+    bindings::{on_each_cpu, smp_processor_id},
+    define_per_cpu,
+    percpu::{cpu_guard::*, *},
+    pr_info,
+    prelude::*,
+    unsafe_get_per_cpu,
+};
+
+module! {
+    type: PerCpuTestModule,
+    name: "percpu_test_rust",
+    author: "Mitchell Levy",
+    description: "Test code to exercise the Rust Per CPU variable API",
+    license: "GPL v2",
+}
+
+struct PerCpuTestModule;
+
+define_per_cpu!(PERCPU: i64 = 0);
+define_per_cpu!(UPERCPU: u64 = 0);
+
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
Quoted 8 lines

lib/percpu_test_rust.rs line 34

Quoted 4 lines
lib/percpu_test_rust.rs new file +4 -0
+
+        let mut native: i64 = 0;
+        // SAFETY: PERCPU is properly defined
+        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };

I don't understand why we need unsafe here, can't we just create something specially in the define_per_cpu macro that is then confirmed by the get_per_cpu! macro and thus it can be safe?

As is, something like

    define_per_cpu!(PERCPU: i32 = 0);

    fn func() {
        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };
    }

will compile, but any usage of pcpu will be UB. This is because unsafe_get_per_cpu! is just blindly casting pointers and, as far as I know, the compiler does not do any checking of pointer casts. If you have thoughts/ideas on how to get around this problem, I'd certainly *like* to provide a safe API here :)

Quoted 16 lines

lib/percpu_test_rust.rs line 36

Quoted 2 lines
lib/percpu_test_rust.rs new file +2 -0
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {

Hmm I also don't like the unsafe part here...

Can't we use the same API that thread_local! in the standard library has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

Quoted 7 lines

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something specifically for that case, eg tokens that are statically known to be unique etc)

I'm open to including a specialization for T: Copy in a similar vein to what I have here for numeric types. Off the top of my head, that shouldn't require any user-facing unsafe. But yes, I believe there is a significant amount of interest in having !Copy per-CPU variables. (At least, I'm interested in having them around for experimenting with using Rust for HV drivers.)

I would definitely like to avoid *requiring* the use of RefCell since, as you mention, it does have a runtime overhead. Per-CPU variables can be used for "logical" reasons rather than just as a performance optimization, so there might be some cases where paying the runtime overhead is ok. But that's certainly not true in all cases. That said, perhaps there could be a safely obtainable token type that only passes a &T (rather than a &mut T) to its closure, and then if a user doesn't mind the runtime overhead, they can choose T to be a RefCell. Thoughts?

For UnsafeCell, if a user of the API were to have something like a PerCpu<UnsafeCell<T>> that safely spits out a &UnsafeCell<T>, my understanding is that mutating the underlying T would require the exact same safety guarantees as what's here, except now it'd need a much bigger unsafe block and would have to do all of its manipulations via pointers. That seems like a pretty big ergonomics burden without a clear (to me) benefit.

Quoted 16 lines

---
Cheers,
Benno

Quoted 12 lines
lib/percpu_test_rust.rs new file +12 -0
+            pr_info!("The contents of pcpu are {}\n", val);
+
+            native += -1;
+            *val += -1;
+            pr_info!("Native: {}, *pcpu: {}\n", native, val);
+            assert!(native == *val && native == -1);
+
+            native += 1;
+            *val += 1;
+            pr_info!("Native: {}, *pcpu: {}\n", native, val);
+            assert!(native == *val && native == 0);
+        });
reviewer
#6 lore raw

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

Quoted 11 lines · Mitchell Levy
Quoted 10 lines · Benno Lossin
Quoted 8 lines · 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 ++++++++++++++++++++++++++++++++++++++++++++++++
Quoted 6 lines
Quoted 2 lines

I don't know if this is the correct place, the code looks much more like a sample, so why not place it there instead?

I don't feel particularly strongly either way --- I defaulted to lib/ since that's where the percpu_test.c I was working off of is located. Happy to change for v3

Since we don't have Rust stuff in lib/ yet (and that the code looks much more like the samples we already have) I think putting it in samples/rust is better.

Quoted 53 lines
Quoted 38 lines
Quoted 38 lines
 rust/helpers/percpu.c   |  11 +++++
 4 files changed, 141 insertions(+)
lib/percpu_test_rust.rs new file +30 -0
new file mode 100644
index 000000000000..a9652e6ece08
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+//! A simple self test for the rust per-CPU API.
+
+use core::ffi::c_void;
+
+use kernel::{
+    bindings::{on_each_cpu, smp_processor_id},
+    define_per_cpu,
+    percpu::{cpu_guard::*, *},
+    pr_info,
+    prelude::*,
+    unsafe_get_per_cpu,
+};
+
+module! {
+    type: PerCpuTestModule,
+    name: "percpu_test_rust",
+    author: "Mitchell Levy",
+    description: "Test code to exercise the Rust Per CPU variable API",
+    license: "GPL v2",
+}
+
+struct PerCpuTestModule;
+
+define_per_cpu!(PERCPU: i64 = 0);
+define_per_cpu!(UPERCPU: u64 = 0);
+
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
Quoted 8 lines

lib/percpu_test_rust.rs line 34

Quoted 4 lines
lib/percpu_test_rust.rs new file +4 -0
+
+        let mut native: i64 = 0;
+        // SAFETY: PERCPU is properly defined
+        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };

I don't understand why we need unsafe here, can't we just create something specially in the define_per_cpu macro that is then confirmed by the get_per_cpu! macro and thus it can be safe?

As is, something like

    define_per_cpu!(PERCPU: i32 = 0);

    fn func() {
        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };
    }
Quoted 5 lines

will compile, but any usage of pcpu will be UB. This is because unsafe_get_per_cpu! is just blindly casting pointers and, as far as I know, the compiler does not do any checking of pointer casts. If you have thoughts/ideas on how to get around this problem, I'd certainly *like* to provide a safe API here :)

I haven't taken a look at your implementation, but you do have the type declared in define_per_cpu!, so it's a bit of a mystery to me why you can't get that out in unsafe_get_per_cpu!...

Maybe in a few weeks I'll be able to take a closer look.

Quoted 24 lines
Quoted 16 lines

lib/percpu_test_rust.rs line 36

Quoted 2 lines
lib/percpu_test_rust.rs new file +2 -0
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {

Hmm I also don't like the unsafe part here...

Can't we use the same API that thread_local! in the standard library has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

Quoted 7 lines

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something specifically for that case, eg tokens that are statically known to be unique etc)

Quoted 6 lines

I'm open to including a specialization for T: Copy in a similar vein to what I have here for numeric types. Off the top of my head, that shouldn't require any user-facing unsafe. But yes, I believe there is a significant amount of interest in having !Copy per-CPU variables. (At least, I'm interested in having them around for experimenting with using Rust for HV drivers.)

What kinds of types would you like to store? Allocations? Just integers in bigger structs? Mutexes?

Quoted 9 lines

I would definitely like to avoid *requiring* the use of RefCell since, as you mention, it does have a runtime overhead. Per-CPU variables can be used for "logical" reasons rather than just as a performance optimization, so there might be some cases where paying the runtime overhead is ok. But that's certainly not true in all cases. That said, perhaps there could be a safely obtainable token type that only passes a &T (rather than a &mut T) to its closure, and then if a user doesn't mind the runtime overhead, they can choose T to be a RefCell. Thoughts?

So I think using an API similar to thread_local! will allow us to have multiple other APIs that slot into that. Cell<T> for T: Copy, RefCell<T> for cases where you don't care about the runtime overhead, plain T for cases where you only need &T. For the case where you need &mut T, we could have something like a TokenCell<T> that gives out a token that you need to mutably borrow in order to get &mut T. Finally for anything else that is too restricted by this, users can also use UnsafeCell<T> although that requires unsafe.

I think the advantage of this is that the common cases are all safe and very idiomatic. In the current design, you *always* have to use unsafe.

Quoted 7 lines

For UnsafeCell, if a user of the API were to have something like a PerCpu<UnsafeCell<T>> that safely spits out a &UnsafeCell<T>, my understanding is that mutating the underlying T would require the exact same safety guarantees as what's here, except now it'd need a much bigger unsafe block and would have to do all of its manipulations via pointers. That seems like a pretty big ergonomics burden without a clear (to me) benefit.

It would require the same amount of unsafe & safety comments, but it wouldn't be bigger comments, since you can just as well create &mut T to the value.

---
Cheers,
Benno

#7 lore raw

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

On Tue, Jul 15, 2025 at 01:31:06PM +0200, Benno Lossin wrote:
[...]

Quoted 24 lines
Quoted 18 lines
Quoted 11 lines

lib/percpu_test_rust.rs line 34

Quoted 7 lines
lib/percpu_test_rust.rs new file +7 -0
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
+
+        let mut native: i64 = 0;
+        // SAFETY: PERCPU is properly defined
+        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };

I don't understand why we need unsafe here, can't we just create something specially in the define_per_cpu macro that is then confirmed by the get_per_cpu! macro and thus it can be safe?

As is, something like

    define_per_cpu!(PERCPU: i32 = 0);

    fn func() {
        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };
    }
Quoted 5 lines

will compile, but any usage of pcpu will be UB. This is because unsafe_get_per_cpu! is just blindly casting pointers and, as far as I know, the compiler does not do any checking of pointer casts. If you have thoughts/ideas on how to get around this problem, I'd certainly *like* to provide a safe API here :)

Quoted 12 lines

I haven't taken a look at your implementation, but you do have the type declared in define_per_cpu!, so it's a bit of a mystery to me why you can't get that out in unsafe_get_per_cpu!...

Maybe in a few weeks I'll be able to take a closer look.

Quoted 6 lines
Quoted 6 lines

lib/percpu_test_rust.rs line 36

Quoted 2 lines
lib/percpu_test_rust.rs new file +2 -0
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {

Hmm I also don't like the unsafe part here...

Can't we use the same API that thread_local! in the standard library

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 10 lines
Quoted 10 lines
Quoted 10 lines

has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something

This sounds reasonable to me.

Quoted 10 lines
Quoted 9 lines
Quoted 2 lines

specifically for that case, eg tokens that are statically known to be unique etc)

I'm open to including a specialization for T: Copy in a similar vein to what I have here for numeric types. Off the top of my head, that shouldn't require any user-facing unsafe. But yes, I believe there is a significant amount of interest in having !Copy per-CPU variables. (At least, I'm interested in having them around for experimenting with using Rust for HV drivers.)

Quoted 3 lines

What kinds of types would you like to store? Allocations? Just integers in bigger structs? Mutexes?

In the VMBus driver, there is a percpu work_struct.

Quoted 19 lines
Quoted 9 lines

I would definitely like to avoid *requiring* the use of RefCell since, as you mention, it does have a runtime overhead. Per-CPU variables can be used for "logical" reasons rather than just as a performance optimization, so there might be some cases where paying the runtime overhead is ok. But that's certainly not true in all cases. That said, perhaps there could be a safely obtainable token type that only passes a &T (rather than a &mut T) to its closure, and then if a user doesn't mind the runtime overhead, they can choose T to be a RefCell. Thoughts?

So I think using an API similar to thread_local! will allow us to have multiple other APIs that slot into that. Cell<T> for T: Copy, RefCell<T> for cases where you don't care about the runtime overhead, plain T for cases where you only need &T. For the case where you need &mut T, we could have something like a TokenCell<T> that gives out a token that you need to mutably borrow in order to get &mut T. Finally for anything else that is too restricted by this, users can also use UnsafeCell<T> although that requires unsafe.

Quoted 3 lines

I think the advantage of this is that the common cases are all safe and very idiomatic. In the current design, you *always* have to use unsafe.

I agree, but like I said, we need to figure out the unsafe interface that C already uses and build API upon it. I think focusing on the unsafe mechanism may be the way to start: you cannot implement something that cannot be implemented, and we don't have the magic pthread_key here ;-)

Regards,
Boqun

Quoted 15 lines
Quoted 7 lines

For UnsafeCell, if a user of the API were to have something like a PerCpu<UnsafeCell<T>> that safely spits out a &UnsafeCell<T>, my understanding is that mutating the underlying T would require the exact same safety guarantees as what's here, except now it'd need a much bigger unsafe block and would have to do all of its manipulations via pointers. That seems like a pretty big ergonomics burden without a clear (to me) benefit.

It would require the same amount of unsafe & safety comments, but it wouldn't be bigger comments, since you can just as well create &mut T to the value.

---
Cheers,
Benno

reviewer
#8 lore raw

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

Quoted 43 lines · Boqun Feng

On Tue, Jul 15, 2025 at 01:31:06PM +0200, Benno Lossin wrote:
[...]

Quoted 24 lines
Quoted 18 lines
Quoted 11 lines

lib/percpu_test_rust.rs line 34

Quoted 7 lines
lib/percpu_test_rust.rs new file +7 -0
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
+
+        let mut native: i64 = 0;
+        // SAFETY: PERCPU is properly defined
+        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };

I don't understand why we need unsafe here, can't we just create something specially in the define_per_cpu macro that is then confirmed by the get_per_cpu! macro and thus it can be safe?

As is, something like

    define_per_cpu!(PERCPU: i32 = 0);

    fn func() {
        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };
    }
Quoted 5 lines

will compile, but any usage of pcpu will be UB. This is because unsafe_get_per_cpu! is just blindly casting pointers and, as far as I know, the compiler does not do any checking of pointer casts. If you have thoughts/ideas on how to get around this problem, I'd certainly *like* to provide a safe API here :)

Quoted 12 lines

I haven't taken a look at your implementation, but you do have the type declared in define_per_cpu!, so it's a bit of a mystery to me why you can't get that out in unsafe_get_per_cpu!...

Maybe in a few weeks I'll be able to take a closer look.

Quoted 6 lines
Quoted 6 lines

lib/percpu_test_rust.rs line 36

Quoted 2 lines
lib/percpu_test_rust.rs new file +2 -0
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {

Hmm I also don't like the unsafe part here...

Can't we use the same API that thread_local! in the standard library

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

Quoted 6 lines
	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

But this is intended to be used by drivers, right? If so, then we should do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint to detect context switches.

Quoted 28 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines

has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something

This sounds reasonable to me.

Quoted 10 lines
Quoted 9 lines
Quoted 2 lines

specifically for that case, eg tokens that are statically known to be unique etc)

I'm open to including a specialization for T: Copy in a similar vein to what I have here for numeric types. Off the top of my head, that shouldn't require any user-facing unsafe. But yes, I believe there is a significant amount of interest in having !Copy per-CPU variables. (At least, I'm interested in having them around for experimenting with using Rust for HV drivers.)

Quoted 5 lines
Quoted 3 lines

What kinds of types would you like to store? Allocations? Just integers in bigger structs? Mutexes?

In the VMBus driver, there is a percpu work_struct.

Do you have a link? Or better yet a Rust struct description of what you think it will look like :)

Quoted 23 lines
Quoted 19 lines
Quoted 9 lines

I would definitely like to avoid *requiring* the use of RefCell since, as you mention, it does have a runtime overhead. Per-CPU variables can be used for "logical" reasons rather than just as a performance optimization, so there might be some cases where paying the runtime overhead is ok. But that's certainly not true in all cases. That said, perhaps there could be a safely obtainable token type that only passes a &T (rather than a &mut T) to its closure, and then if a user doesn't mind the runtime overhead, they can choose T to be a RefCell. Thoughts?

So I think using an API similar to thread_local! will allow us to have multiple other APIs that slot into that. Cell<T> for T: Copy, RefCell<T> for cases where you don't care about the runtime overhead, plain T for cases where you only need &T. For the case where you need &mut T, we could have something like a TokenCell<T> that gives out a token that you need to mutably borrow in order to get &mut T. Finally for anything else that is too restricted by this, users can also use UnsafeCell<T> although that requires unsafe.

Quoted 3 lines

I think the advantage of this is that the common cases are all safe and very idiomatic. In the current design, you *always* have to use unsafe.

Quoted 5 lines

I agree, but like I said, we need to figure out the unsafe interface that C already uses and build API upon it. I think focusing on the unsafe mechanism may be the way to start: you cannot implement something that cannot be implemented, and we don't have the magic pthread_key here ;-)

Sure we can do some experimentation, but I don't think we should put unsafe abstractions upstream that we intend to replace with a safe abstraction later. Otherwise people are going to depend on it and it's going to be a mess. Do the experimenting out of tree and learn there.

---
Cheers,
Benno

#9 lore raw

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

Quoted 44 lines · Benno Lossin
Quoted 43 lines · Boqun Feng

On Tue, Jul 15, 2025 at 01:31:06PM +0200, Benno Lossin wrote:
[...]

Quoted 24 lines
Quoted 18 lines
Quoted 11 lines

lib/percpu_test_rust.rs line 34

Quoted 7 lines
lib/percpu_test_rust.rs new file +7 -0
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
+
+        let mut native: i64 = 0;
+        // SAFETY: PERCPU is properly defined
+        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };

I don't understand why we need unsafe here, can't we just create something specially in the define_per_cpu macro that is then confirmed by the get_per_cpu! macro and thus it can be safe?

As is, something like

    define_per_cpu!(PERCPU: i32 = 0);

    fn func() {
        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };
    }
Quoted 5 lines

will compile, but any usage of pcpu will be UB. This is because unsafe_get_per_cpu! is just blindly casting pointers and, as far as I know, the compiler does not do any checking of pointer casts. If you have thoughts/ideas on how to get around this problem, I'd certainly *like* to provide a safe API here :)

Quoted 12 lines

I haven't taken a look at your implementation, but you do have the type declared in define_per_cpu!, so it's a bit of a mystery to me why you can't get that out in unsafe_get_per_cpu!...

Maybe in a few weeks I'll be able to take a closer look.

Quoted 6 lines
Quoted 6 lines

lib/percpu_test_rust.rs line 36

Quoted 2 lines
lib/percpu_test_rust.rs new file +2 -0
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {

Hmm I also don't like the unsafe part here...

Can't we use the same API that thread_local! in the standard library

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

Quoted 8 lines
Quoted 6 lines
	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

If we would like to model it conceptually, it's more like an array that's index by CpuId to me.

Quoted 30 lines

to detect context switches.

Quoted 28 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines

has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something

This sounds reasonable to me.

Quoted 10 lines
Quoted 9 lines
Quoted 2 lines

specifically for that case, eg tokens that are statically known to be unique etc)

I'm open to including a specialization for T: Copy in a similar vein to what I have here for numeric types. Off the top of my head, that shouldn't require any user-facing unsafe. But yes, I believe there is a significant amount of interest in having !Copy per-CPU variables. (At least, I'm interested in having them around for experimenting with using Rust for HV drivers.)

Quoted 9 lines
Quoted 5 lines
Quoted 3 lines

What kinds of types would you like to store? Allocations? Just integers in bigger structs? Mutexes?

In the VMBus driver, there is a percpu work_struct.

Do you have a link? Or better yet a Rust struct description of what you think it will look like :)

Not Rust code yet, but here is the corresponding C code:

	https://github.com/Rust-for-Linux/linux/blob/rust-next/drivers/hv/vmbus_drv.c#L1396

But please note that we are not solely developing the abstraction for this usage, but more for generally understand how to wrap percpu functionality similar to the usage in C.

Quoted 29 lines
Quoted 23 lines
Quoted 19 lines
Quoted 9 lines

I would definitely like to avoid *requiring* the use of RefCell since, as you mention, it does have a runtime overhead. Per-CPU variables can be used for "logical" reasons rather than just as a performance optimization, so there might be some cases where paying the runtime overhead is ok. But that's certainly not true in all cases. That said, perhaps there could be a safely obtainable token type that only passes a &T (rather than a &mut T) to its closure, and then if a user doesn't mind the runtime overhead, they can choose T to be a RefCell. Thoughts?

So I think using an API similar to thread_local! will allow us to have multiple other APIs that slot into that. Cell<T> for T: Copy, RefCell<T> for cases where you don't care about the runtime overhead, plain T for cases where you only need &T. For the case where you need &mut T, we could have something like a TokenCell<T> that gives out a token that you need to mutably borrow in order to get &mut T. Finally for anything else that is too restricted by this, users can also use UnsafeCell<T> although that requires unsafe.

Quoted 3 lines

I think the advantage of this is that the common cases are all safe and very idiomatic. In the current design, you *always* have to use unsafe.

Quoted 5 lines

I agree, but like I said, we need to figure out the unsafe interface that C already uses and build API upon it. I think focusing on the unsafe mechanism may be the way to start: you cannot implement something that cannot be implemented, and we don't have the magic pthread_key here ;-)

Quoted 3 lines

Sure we can do some experimentation, but I don't think we should put unsafe abstractions upstream that we intend to replace with a safe abstraction later. Otherwise people are going to depend on it and it's

I doubt we can replace the unsafe abstraction with a safe one, if users really care the performance then they would really need to use some unsafe API to build their safe abstraction.

Quoted 1 line

going to be a mess. Do the experimenting out of tree and learn there.

I disagree, Rust as a language its own should be able to do what C does including being able to implement the percpu functionality same as C, there is nothing wrong with a set of Rust primitives in the kernel that provides fundamental percpu functionality the other core facilities can rely on. The better part is that it will have all the safety requirement documented well.

Regards,
Boqun

Quoted 4 lines

---
Cheers,
Benno

reviewer
#10 lore raw

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

Quoted 52 lines · Boqun Feng
Quoted 51 lines · Benno Lossin
Quoted 49 lines · Boqun Feng

On Tue, Jul 15, 2025 at 01:31:06PM +0200, Benno Lossin wrote:
[...]

Quoted 24 lines
Quoted 18 lines
Quoted 11 lines

Review

Quoted 7 lines
(quoted diff) +7 -0
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
+
+        let mut native: i64 = 0;
+        // SAFETY: PERCPU is properly defined
+        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };

I don't understand why we need unsafe here, can't we just create something specially in the define_per_cpu macro that is then confirmed by the get_per_cpu! macro and thus it can be safe?

As is, something like

    define_per_cpu!(PERCPU: i32 = 0);

    fn func() {
        let mut pcpu: StaticPerCpu<i64> = unsafe { unsafe_get_per_cpu!(PERCPU) };
    }
Quoted 5 lines

will compile, but any usage of pcpu will be UB. This is because unsafe_get_per_cpu! is just blindly casting pointers and, as far as I know, the compiler does not do any checking of pointer casts. If you have thoughts/ideas on how to get around this problem, I'd certainly *like* to provide a safe API here :)

Quoted 12 lines

I haven't taken a look at your implementation, but you do have the type declared in define_per_cpu!, so it's a bit of a mystery to me why you can't get that out in unsafe_get_per_cpu!...

Maybe in a few weeks I'll be able to take a closer look.

Quoted 6 lines
Quoted 6 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {

Hmm I also don't like the unsafe part here...

Can't we use the same API that thread_local! in the standard library

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 4 lines
Quoted 1 line

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

That's fair, but then it should be pub(crate).

Quoted 7 lines
Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Yeah. But then we should do that before merging :)

Quoted 11 lines
Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

But then you need some sort of synchronization?

Quoted 2 lines

If we would like to model it conceptually, it's more like an array that's index by CpuId to me.

Gotcha, but this model is missing the access control/synchronization. So I'm not so sure how useful it is.

(I think I asked this somewhere else, but the number of CPUs doesn't change, right?)

Quoted 36 lines
Quoted 36 lines

to detect context switches.

Quoted 33 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines

has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something

This sounds reasonable to me.

Quoted 10 lines
Quoted 9 lines
Quoted 2 lines

specifically for that case, eg tokens that are statically known to be unique etc)

I'm open to including a specialization for T: Copy in a similar vein to what I have here for numeric types. Off the top of my head, that shouldn't require any user-facing unsafe. But yes, I believe there is a significant amount of interest in having !Copy per-CPU variables. (At least, I'm interested in having them around for experimenting with using Rust for HV drivers.)

Quoted 3 lines

What kinds of types would you like to store? Allocations? Just integers in bigger structs? Mutexes?

In the VMBus driver, there is a percpu work_struct.

Quoted 7 lines
Quoted 3 lines

Do you have a link? Or better yet a Rust struct description of what you think it will look like :)

Not Rust code yet, but here is the corresponding C code:

	https://github.com/Rust-for-Linux/linux/blob/rust-next/drivers/hv/vmbus_drv.c#L1396

Thanks!

Quoted 3 lines

But please note that we are not solely developing the abstraction for this usage, but more for generally understand how to wrap percpu functionality similar to the usage in C.

Well, I have to start somewhere for looking at the use-cases :)

If you have more, just let me see. (probably won't have enough time to look at them now, but maybe in a couple weeks)

Quoted 29 lines
Quoted 29 lines
Quoted 28 lines
Quoted 19 lines
Quoted 9 lines

I would definitely like to avoid *requiring* the use of RefCell since, as you mention, it does have a runtime overhead. Per-CPU variables can be used for "logical" reasons rather than just as a performance optimization, so there might be some cases where paying the runtime overhead is ok. But that's certainly not true in all cases. That said, perhaps there could be a safely obtainable token type that only passes a &T (rather than a &mut T) to its closure, and then if a user doesn't mind the runtime overhead, they can choose T to be a RefCell. Thoughts?

So I think using an API similar to thread_local! will allow us to have multiple other APIs that slot into that. Cell<T> for T: Copy, RefCell<T> for cases where you don't care about the runtime overhead, plain T for cases where you only need &T. For the case where you need &mut T, we could have something like a TokenCell<T> that gives out a token that you need to mutably borrow in order to get &mut T. Finally for anything else that is too restricted by this, users can also use UnsafeCell<T> although that requires unsafe.

Quoted 3 lines

I think the advantage of this is that the common cases are all safe and very idiomatic. In the current design, you *always* have to use unsafe.

I agree, but like I said, we need to figure out the unsafe interface that C already uses and build API upon it. I think focusing on the unsafe mechanism may be the way to start: you cannot implement something that cannot be implemented, and we don't have the magic pthread_key here ;-)

Quoted 7 lines
Quoted 3 lines

Sure we can do some experimentation, but I don't think we should put unsafe abstractions upstream that we intend to replace with a safe abstraction later. Otherwise people are going to depend on it and it's

I doubt we can replace the unsafe abstraction with a safe one, if users really care the performance then they would really need to use some unsafe API to build their safe abstraction.

That sounds pretty pessimistic, why do you think that?

Quoted 8 lines
Quoted 1 line

going to be a mess. Do the experimenting out of tree and learn there.

I disagree, Rust as a language its own should be able to do what C does including being able to implement the percpu functionality same as C, there is nothing wrong with a set of Rust primitives in the kernel that provides fundamental percpu functionality the other core facilities can rely on. The better part is that it will have all the safety requirement documented well.

Sure, but we haven't even tried to make it safe, so I don't think we should add them now in this state.

---
Cheers,
Benno

#11 lore raw

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

On Tue, Jul 15, 2025 at 07:44:01PM +0200, Benno Lossin wrote:
[...]

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

That's fair, but then it should be pub(crate).

Fine by me, but please see below.

Quoted 10 lines
Quoted 7 lines
Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Yeah. But then we should do that before merging :)

Well, who's talknig about merging? ;-) I thought we just began reviewing here ;-)

Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Quoted 6 lines
Quoted 3 lines

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

But then you need some sort of synchronization?

Right, but the synchronization can exist either in the percpu operations themselves or outside the percpu operations. Some cases, the data types are small enough to fit in atomic data types, and operations are just load/store/cmpxchg etc, then operations on the current cpu and remote read will be naturally synchronized. Sometimes extra synchronization is needed.

Keyword find all these cases are per_cpu_ptr():

	https://elixir.bootlin.com/linux/v6.15.6/A/ident/per_cpu_ptr
Quoted 9 lines
Quoted 2 lines

If we would like to model it conceptually, it's more like an array that's index by CpuId to me.

Gotcha, but this model is missing the access control/synchronization. So I'm not so sure how useful it is.

(I think I asked this somewhere else, but the number of CPUs doesn't change, right?)

In terms of percpu variable, yes. A percpu variable is even available for an offline CPU.

Quoted 46 lines
Quoted 36 lines
Quoted 36 lines

to detect context switches.

Quoted 33 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines

has:

https://doc.rust-lang.org/std/macro.thread_local.html

So in this example you would store a Cell<i64> instead.

I'm not familiar with per CPU variables, but if you're usually storing Copy types, then this is much better wrt not having unsafe code everywhere.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

If one also often stores !Copy types, then we might be able to get away with RefCell, but that's a small runtime overhead -- which is probably bad given that per cpu variables are most likely used for performance reasons? In that case the user might just need to store UnsafeCell and use unsafe regardless. (or we invent something

This sounds reasonable to me.

Quoted 10 lines
Quoted 9 lines
Quoted 2 lines

specifically for that case, eg tokens that are statically known to be unique etc)

I'm open to including a specialization for T: Copy in a similar vein to what I have here for numeric types. Off the top of my head, that shouldn't require any user-facing unsafe. But yes, I believe there is a significant amount of interest in having !Copy per-CPU variables. (At least, I'm interested in having them around for experimenting with using Rust for HV drivers.)

Quoted 3 lines

What kinds of types would you like to store? Allocations? Just integers in bigger structs? Mutexes?

In the VMBus driver, there is a percpu work_struct.

Quoted 7 lines
Quoted 3 lines

Do you have a link? Or better yet a Rust struct description of what you think it will look like :)

Not Rust code yet, but here is the corresponding C code:

	https://github.com/Rust-for-Linux/linux/blob/rust-next/drivers/hv/vmbus_drv.c#L1396

Thanks!

Quoted 9 lines
Quoted 3 lines

But please note that we are not solely developing the abstraction for this usage, but more for generally understand how to wrap percpu functionality similar to the usage in C.

Well, I have to start somewhere for looking at the use-cases :)

If you have more, just let me see. (probably won't have enough time to look at them now, but maybe in a couple weeks)

If you have time, feel free to take a look at hazard pointers;

https://lore.kernel.org/lkml/[email protected]/
https://lore.kernel.org/lkml/[email protected]/

You can also take a look at existing usage of percpu, e.g. SRCU uses to track how many readers are active:

	https://elixir.bootlin.com/linux/v6.15.6/source/kernel/rcu/srcutree.c#L577

[...]

Regards,
Boqun

reviewer
#12 lore raw

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

Quoted 32 lines · Boqun Feng

On Tue, Jul 15, 2025 at 07:44:01PM +0200, Benno Lossin wrote:
[...]

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

That's fair, but then it should be pub(crate).

Fine by me, but please see below.

Quoted 8 lines
Quoted 7 lines
Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Quoted 5 lines
Quoted 2 lines

Yeah. But then we should do that before merging :)

Well, who's talknig about merging? ;-) I thought we just began reviewing here ;-)

I understand [PATCH] emails as "I want to merge this" and [RFC PATCH] as "I want to talk about merging this". It might be that I haven't seen the RFC patch series, because I often mute those.

Quoted 15 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Quoted 6 lines
Quoted 3 lines

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

But then you need some sort of synchronization?

Quoted 6 lines

Right, but the synchronization can exist either in the percpu operations themselves or outside the percpu operations. Some cases, the data types are small enough to fit in atomic data types, and operations are just load/store/cmpxchg etc, then operations on the current cpu and remote read will be naturally synchronized. Sometimes extra synchronization is needed.

Sure, so we probably want direct atomics support. What about "extra synchronization"? Is that using locks or RCU or what else?

Quoted 3 lines

Keyword find all these cases are per_cpu_ptr():

	https://elixir.bootlin.com/linux/v6.15.6/A/ident/per_cpu_ptr

Could you explain to me how to find them? I can either click on one of the files with horrible C preprocessor macros or the auto-completion in the search bar. But that one only shows 3 suggestions _hyp_sym, _nvhe_sym and _to_phys which doesn't really mean much to me.

---
Cheers,
Benno

#13 lore raw

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

Quoted 33 lines · Benno Lossin
Quoted 32 lines · Boqun Feng

On Tue, Jul 15, 2025 at 07:44:01PM +0200, Benno Lossin wrote:
[...]

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

That's fair, but then it should be pub(crate).

Fine by me, but please see below.

Quoted 8 lines
Quoted 7 lines
Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Quoted 7 lines
Quoted 5 lines
Quoted 2 lines

Yeah. But then we should do that before merging :)

Well, who's talknig about merging? ;-) I thought we just began reviewing here ;-)

I understand [PATCH] emails as "I want to merge this" and [RFC PATCH] as

But it doesn't mean "merge as it is", right? I don't think either I or Mitchell implied that, I'm surprised that you had to mention that, also based on "I often mute those" below, making it "[PATCH]" seems to be a practical way to get more attention if one wants to get some reviews.

Quoted 3 lines

"I want to talk about merging this". It might be that I haven't seen the RFC patch series, because I often mute those.

Well, then you cannot blame people to move from "RFC PATCH" to "PATCH" stage for more reviews, right? And you cannot make rules about what the difference between [PATCH] and [RFC PATCH] if you ignore one of them ;-)

Quoted 22 lines
Quoted 15 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Quoted 6 lines
Quoted 3 lines

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

But then you need some sort of synchronization?

Quoted 6 lines

Right, but the synchronization can exist either in the percpu operations themselves or outside the percpu operations. Some cases, the data types are small enough to fit in atomic data types, and operations are just load/store/cmpxchg etc, then operations on the current cpu and remote read will be naturally synchronized. Sometimes extra synchronization is needed.

Quoted 3 lines

Sure, so we probably want direct atomics support. What about "extra synchronization"? Is that using locks or RCU or what else?

It's up to the users obviously, It could be some sort of locking or RCU, it's case by case.

Quoted 9 lines
Quoted 3 lines

Keyword find all these cases are per_cpu_ptr():

	https://elixir.bootlin.com/linux/v6.15.6/A/ident/per_cpu_ptr

Could you explain to me how to find them? I can either click on one of the files with horrible C preprocessor macros or the auto-completion in the search bar. But that one only shows 3 suggestions _hyp_sym, _nvhe_sym and _to_phys which doesn't really mean much to me.

You need to find the usage of per_cpu_ptr(), which is a function that gives you a pointer to a percpu variable on the other CPU, and then that's usually the case where a "remote" read of percpu variable happens.

Regards,
Boqun

Quoted 3 lines

---
Cheers,
Benno

reviewer
#14 lore raw

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

Quoted 37 lines · Boqun Feng
Quoted 36 lines · Benno Lossin
Quoted 35 lines · Boqun Feng

On Tue, Jul 15, 2025 at 07:44:01PM +0200, Benno Lossin wrote:
[...]

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

That's fair, but then it should be pub(crate).

Fine by me, but please see below.

Quoted 10 lines
Quoted 7 lines
Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Yeah. But then we should do that before merging :)

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Well, who's talknig about merging? ;-) I thought we just began reviewing here ;-)

I understand [PATCH] emails as "I want to merge this" and [RFC PATCH] as

But it doesn't mean "merge as it is", right? I don't think either I or Mitchell implied that, I'm surprised that you had to mention that,

Yeah that is true, but it at least shows the intention :)

Quoted 3 lines

also based on "I often mute those" below, making it "[PATCH]" seems to be a practical way to get more attention if one wants to get some reviews.

That is true, I do usually read the titles of RFC patches though and sometimes take a look eg your atomics series.

Quoted 7 lines
Quoted 3 lines

"I want to talk about merging this". It might be that I haven't seen the RFC patch series, because I often mute those.

Well, then you cannot blame people to move from "RFC PATCH" to "PATCH" stage for more reviews, right? And you cannot make rules about what the difference between [PATCH] and [RFC PATCH] if you ignore one of them ;-)

I'm not trying to blame anyone. I saw a lot of unsafe in the example and thought "we can do better" and since I haven't heard any sufficient arguments showing that it's impossible to improve, we should do some design work.

Quoted 22 lines
Quoted 22 lines
Quoted 21 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Quoted 6 lines
Quoted 3 lines

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

But then you need some sort of synchronization?

Right, but the synchronization can exist either in the percpu operations themselves or outside the percpu operations. Some cases, the data types are small enough to fit in atomic data types, and operations are just load/store/cmpxchg etc, then operations on the current cpu and remote read will be naturally synchronized. Sometimes extra synchronization is needed.

Quoted 6 lines
Quoted 3 lines

Sure, so we probably want direct atomics support. What about "extra synchronization"? Is that using locks or RCU or what else?

It's up to the users obviously, It could be some sort of locking or RCU, it's case by case.

Makes sense, what do you need in the VMS driver?

Quoted 10 lines
Quoted 9 lines
Quoted 3 lines

Keyword find all these cases are per_cpu_ptr():

	https://elixir.bootlin.com/linux/v6.15.6/A/ident/per_cpu_ptr

Could you explain to me how to find them? I can either click on one of the files with horrible C preprocessor macros or the auto-completion in the search bar. But that one only shows 3 suggestions _hyp_sym, _nvhe_sym and _to_phys which doesn't really mean much to me.

Quoted 4 lines

You need to find the usage of per_cpu_ptr(), which is a function that gives you a pointer to a percpu variable on the other CPU, and then that's usually the case where a "remote" read of percpu variable happens.

Ahh gotcha, I thought you pointed me to some definitions of operations on percpu pointers.

---
Cheers,
Benno

#15 lore raw

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

Quoted 59 lines · Benno Lossin
Quoted 37 lines · Boqun Feng
Quoted 36 lines · Benno Lossin
Quoted 35 lines · Boqun Feng

On Tue, Jul 15, 2025 at 07:44:01PM +0200, Benno Lossin wrote:
[...]

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

That's fair, but then it should be pub(crate).

Fine by me, but please see below.

Quoted 10 lines
Quoted 7 lines
Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Yeah. But then we should do that before merging :)

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Well, who's talknig about merging? ;-) I thought we just began reviewing here ;-)

I understand [PATCH] emails as "I want to merge this" and [RFC PATCH] as

But it doesn't mean "merge as it is", right? I don't think either I or Mitchell implied that, I'm surprised that you had to mention that,

Yeah that is true, but it at least shows the intention :)

Quoted 3 lines

also based on "I often mute those" below, making it "[PATCH]" seems to be a practical way to get more attention if one wants to get some reviews.

That is true, I do usually read the titles of RFC patches though and sometimes take a look eg your atomics series.

Quoted 4 lines
Quoted 3 lines

"I want to talk about merging this". It might be that I haven't seen the RFC patch series, because I often mute those.

Quoted 9 lines
Quoted 3 lines

Well, then you cannot blame people to move from "RFC PATCH" to "PATCH" stage for more reviews, right? And you cannot make rules about what the difference between [PATCH] and [RFC PATCH] if you ignore one of them ;-)

I'm not trying to blame anyone. I saw a lot of unsafe in the example and thought "we can do better" and since I haven't heard any sufficient arguments showing that it's impossible to improve, we should do some design work.

I agree with you, and I like what you're proposing, but I think design work can be done at "PATCH" stage, right? And sometimes, it's also OK to do some design work even at some version like "v12" ;-)

Also I want to see more forward-progress actions about the design work improvement. For example, we can examine every case that makes unsafe_get_per_cpu!() unsafe, and see if we can improve that by typing or something else. We always can "do better", but the important part is how to get there ;-)

Quoted 22 lines
Quoted 22 lines
Quoted 22 lines
Quoted 21 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Quoted 6 lines
Quoted 3 lines

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

But then you need some sort of synchronization?

Right, but the synchronization can exist either in the percpu operations themselves or outside the percpu operations. Some cases, the data types are small enough to fit in atomic data types, and operations are just load/store/cmpxchg etc, then operations on the current cpu and remote read will be naturally synchronized. Sometimes extra synchronization is needed.

Quoted 9 lines
Quoted 6 lines
Quoted 3 lines

Sure, so we probably want direct atomics support. What about "extra synchronization"? Is that using locks or RCU or what else?

It's up to the users obviously, It could be some sort of locking or RCU, it's case by case.

Makes sense, what do you need in the VMS driver?

In VMBus driver, it's actually isolate, i.e. each CPU only access it's own work_struct, so synchronization between CPUs is not needed.

Regards,
Boqun

Quoted 21 lines
Quoted 10 lines
Quoted 9 lines
Quoted 3 lines

Keyword find all these cases are per_cpu_ptr():

	https://elixir.bootlin.com/linux/v6.15.6/A/ident/per_cpu_ptr

Could you explain to me how to find them? I can either click on one of the files with horrible C preprocessor macros or the auto-completion in the search bar. But that one only shows 3 suggestions _hyp_sym, _nvhe_sym and _to_phys which doesn't really mean much to me.

Quoted 4 lines

You need to find the usage of per_cpu_ptr(), which is a function that gives you a pointer to a percpu variable on the other CPU, and then that's usually the case where a "remote" read of percpu variable happens.

Ahh gotcha, I thought you pointed me to some definitions of operations on percpu pointers.

---
Cheers,
Benno

reviewer
#16 lore raw

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

Quoted 70 lines · Boqun Feng
Quoted 59 lines · Benno Lossin
Quoted 37 lines · Boqun Feng
Quoted 36 lines · Benno Lossin
Quoted 35 lines · Boqun Feng

On Tue, Jul 15, 2025 at 07:44:01PM +0200, Benno Lossin wrote:
[...]

Quoted 12 lines
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines

First of all, thread_local! has to be implemented by some sys-specific unsafe mechanism, right? For example on unix, I think it's using pthread_key_t:

	https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html

what we are implementing (or wrapping) is the very basic unsafe mechanism for percpu here. Surely we can explore the design for a safe API, but the unsafe mechanism is probably necessary to look into at first.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

But this is intended to be used by drivers, right? If so, then we should

Not necessarily only for drivers, we can also use it for implementing other safe abstraction (e.g. hazard pointers, percpu counters etc)

That's fair, but then it should be pub(crate).

Fine by me, but please see below.

Quoted 10 lines
Quoted 7 lines
Quoted 3 lines

do our usual due diligence and work out a safe abstraction. Only fall back to unsafe if it isn't possible.

All I'm saying is instead of figuring out a safe abstraction at first, we should probably focus on identifying how to implement it and which part is really unsafe and the safety requirement for that.

Yeah. But then we should do that before merging :)

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Well, who's talknig about merging? ;-) I thought we just began reviewing here ;-)

I understand [PATCH] emails as "I want to merge this" and [RFC PATCH] as

But it doesn't mean "merge as it is", right? I don't think either I or Mitchell implied that, I'm surprised that you had to mention that,

Yeah that is true, but it at least shows the intention :)

Quoted 3 lines

also based on "I often mute those" below, making it "[PATCH]" seems to be a practical way to get more attention if one wants to get some reviews.

That is true, I do usually read the titles of RFC patches though and sometimes take a look eg your atomics series.

Quoted 4 lines
Quoted 3 lines

"I want to talk about merging this". It might be that I haven't seen the RFC patch series, because I often mute those.

Quoted 9 lines
Quoted 3 lines

Well, then you cannot blame people to move from "RFC PATCH" to "PATCH" stage for more reviews, right? And you cannot make rules about what the difference between [PATCH] and [RFC PATCH] if you ignore one of them ;-)

I'm not trying to blame anyone. I saw a lot of unsafe in the example and thought "we can do better" and since I haven't heard any sufficient arguments showing that it's impossible to improve, we should do some design work.

Quoted 3 lines

I agree with you, and I like what you're proposing, but I think design work can be done at "PATCH" stage, right? And sometimes, it's also OK to do some design work even at some version like "v12" ;-)

Yeah of course. The thing is just that nobody asked why there was unsafe and thus I got the impression that people thought this would be a good abstraction for percpu. (don't take from this that it's bad :)

Quoted 5 lines

Also I want to see more forward-progress actions about the design work improvement. For example, we can examine every case that makes unsafe_get_per_cpu!() unsafe, and see if we can improve that by typing or something else. We always can "do better", but the important part is how to get there ;-)

Yeah that would be a starting point :)

Quoted 26 lines
Quoted 26 lines
Quoted 26 lines
Quoted 25 lines
Quoted 15 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

I'm not familiar with percpu, but from the name I assumed that it's "just a variable for each cpu" so similar to thread_local!, but it's bound to the specific cpu instead of the thread.

That in my mind should be rather easy to support in Rust at least with the thread_local-style API. You just need to ensure that no reference can escape the cpu, so we can make it !Send & !Sync + rely on klint

Quoted 6 lines
Quoted 3 lines

Not really, in kernel, we have plenty of use cases that we read the other CPU's percpu variables. For example, each CPU keeps it's own counter and we sum them other in another CPU.

But then you need some sort of synchronization?

Quoted 6 lines

Right, but the synchronization can exist either in the percpu operations themselves or outside the percpu operations. Some cases, the data types are small enough to fit in atomic data types, and operations are just load/store/cmpxchg etc, then operations on the current cpu and remote read will be naturally synchronized. Sometimes extra synchronization is needed.

Sure, so we probably want direct atomics support. What about "extra synchronization"? Is that using locks or RCU or what else?

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

It's up to the users obviously, It could be some sort of locking or RCU, it's case by case.

Makes sense, what do you need in the VMS driver?

In VMBus driver, it's actually isolate, i.e. each CPU only access it's own work_struct, so synchronization between CPUs is not needed.

I see, so we could either just start out with no sync support or -- which I would prefer -- get a list of the most common use-cases and implement those too (or at least design the first part compatibly with further extensions).

---
Cheers,
Benno

#17 lore raw

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

In reply to Benno Lossin

On Tue, Jul 15, 2025 at 07:44:01PM +0200, Benno Lossin wrote:
[...]

Quoted 11 lines
Quoted 8 lines
Quoted 4 lines

Sure we can do some experimentation, but I don't think we should put unsafe abstractions upstream that we intend to replace with a safe abstraction later. Otherwise people are going to depend on it and it's

I doubt we can replace the unsafe abstraction with a safe one, if users really care the performance then they would really need to use some unsafe API to build their safe abstraction.

That sounds pretty pessimistic, why do you think that?

I could ask you the similar, you barely know the implementation and usage the percpu, why do you think it's possible? ;-)

Regards,
Boqun

#18 lore raw

PATCH v2 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 7dfceb6aefd7..b97d1d07a614 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 DynamicPerCpu<T> type, as well as the PerCpu<T> trait.
 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
#19 lore raw

PATCH v2 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 b97d1d07a614..7458fe413f25 100644
@@ -26,7 +26,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.
@@ -204,9 +207,10 @@ impl<T: Zeroable> 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 })
     }
 }
 
@@ -217,8 +221,9 @@ impl<T> DynamicPerCpu<T> {
     /// * `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 })
     }
 }
 
@@ -226,7 +231,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
     }
 }
 
@@ -234,6 +239,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