Basic Usage Examples
Relevant source files
This section demonstrates the fundamental patterns for defining and accessing per-CPU variables using the def_percpu
macro. It covers the essential initialization sequence, current CPU data access, and remote CPU data manipulation techniques. For detailed information about system architecture and memory layout, see Memory Layout and Initialization. For comprehensive API documentation, see def_percpu Macro.
Variable Definition and Initialization
The core workflow begins with defining per-CPU variables using the #[def_percpu]
attribute macro, followed by system initialization to set up memory areas and CPU registers.
Basic Variable Definition
Per-CPU variables are defined using the #[percpu::def_percpu]
attribute on static variables. The macro supports various primitive types and custom structures:
#[percpu::def_percpu]
static CPU_ID: usize = 0;
#[percpu::def_percpu]
static COUNTER: u64 = 0;
#[percpu::def_percpu]
static FLAG: bool = false;
Sources: README.md(L40 - L41) percpu/tests/test_percpu.rs(L7 - L23)
System Initialization Sequence
Before accessing per-CPU data, the system must be initialized with percpu::init()
and per-CPU registers configured with percpu::init_percpu_reg()
:
// Initialize per-CPU data areas
percpu::init();
// Set the thread pointer register to per-CPU data area 0
percpu::init_percpu_reg(0);
Generated Code Structure
flowchart TD subgraph subGraph2["Generated Methods"] OFFSET["offset() -> usize"] CURRENT_PTR["current_ptr() -> *const T"] READ["read_current() -> T"] WRITE["write_current(val: T)"] WITH["with_current(f: F)"] REMOTE_PTR["remote_ptr(cpu_id) -> *const T"] end subgraph subGraph1["Generated by def_percpu Macro"] INNER["__PERCPU_CPU_IDin .percpu section"] WRAPPER["CPU_ID_WRAPPERstruct with methods"] PUBLIC["CPU_ID: CPU_ID_WRAPPERpublic static instance"] end subgraph subGraph0["User Code"] USERVAR["#[def_percpu]static CPU_ID: usize = 0"] end USERVAR --> INNER USERVAR --> PUBLIC USERVAR --> WRAPPER WRAPPER --> CURRENT_PTR WRAPPER --> OFFSET WRAPPER --> READ WRAPPER --> REMOTE_PTR WRAPPER --> WITH WRAPPER --> WRITE
Sources: percpu_macros/src/lib.rs(L88 - L89) percpu_macros/src/lib.rs(L149 - L159) percpu_macros/src/lib.rs(L161 - L249)
Current CPU Data Access
The generated wrapper provides several methods for accessing per-CPU data on the current CPU. For primitive integer types, direct read/write methods are available.
Direct Read/Write Operations
For primitive types (bool
, u8
, u16
, u32
, u64
, usize
), the macro generates optimized read and write methods:
// Reading current CPU values
let value = CPU_ID.read_current();
let flag_state = FLAG.read_current();
// Writing to current CPU
CPU_ID.write_current(1);
FLAG.write_current(true);
COUNTER.write_current(0xdead_beef);
Sources: README.md(L48 - L51) percpu/tests/test_percpu.rs(L71 - L77) percpu_macros/src/lib.rs(L129 - L139)
Safe Reference-Based Access
For complex data types and when multiple operations are needed, use the with_current()
method which provides safe mutable access:
#[percpu::def_percpu]
static STATS: Struct = Struct { foo: 0, bar: 0 };
// Safe manipulation with automatic preemption handling
STATS.with_current(|stats| {
stats.foo = 0x2333;
stats.bar = 100;
});
Sources: percpu/tests/test_percpu.rs(L78 - L81) percpu_macros/src/lib.rs(L201 - L207)
Unsafe Raw Access
For performance-critical scenarios where preemption is manually managed, raw access methods are available:
unsafe {
// Caller must ensure preemption is disabled
let ptr = CPU_ID.current_ptr();
let value = CPU_ID.read_current_raw();
CPU_ID.write_current_raw(42);
}
Sources: percpu_macros/src/lib.rs(L113 - L125) percpu_macros/src/lib.rs(L174 - L197)
Remote CPU Data Access
The system supports accessing per-CPU data from other CPUs using CPU ID-based addressing. This is essential for cross-CPU coordination and debugging.
Remote Pointer Access
unsafe {
// Access data on CPU 1 from any CPU
let remote_ptr = CPU_ID.remote_ptr(1);
let remote_value = *remote_ptr;
// Modify remote CPU data
let remote_mut_ref = COUNTER.remote_ref_mut_raw(1);
*remote_mut_ref = 0xfeed_feed_feed_feed;
}
Sources: percpu/tests/test_percpu.rs(L110 - L122) percpu_macros/src/lib.rs(L217 - L246)
CPU Context Switching
The per-CPU register can be updated to change the current CPU context:
unsafe {
// Switch to CPU 1's context
percpu::write_percpu_reg(percpu::percpu_area_base(1));
// Now read_current() accesses CPU 1's data
let cpu1_value = CPU_ID.read_current();
}
Sources: percpu/tests/test_percpu.rs(L139 - L154)
Data Type Support Matrix
The def_percpu
macro handles different data types with varying levels of optimization:
Data Type | Direct Read/Write | with_current() | Remote Access | Notes |
---|---|---|---|---|
bool | ✓ | ✓ | ✓ | Optimized methods generated |
u8,u16,u32 | ✓ | ✓ | ✓ | Optimized methods generated |
u64,usize | ✓ | ✓ | ✓ | Optimized methods generated |
Custom structs | ✗ | ✓ | ✓ | Reference-based access only |
Arrays | ✗ | ✓ | ✓ | Reference-based access only |
Per-CPU Data Type Access Patterns
flowchart TD subgraph subGraph2["Access Methods"] DIRECT["read_current()write_current()"] WITH["with_current(|data| ...)"] RAW["current_ptr()current_ref_raw()"] REMOTE["remote_ptr(cpu_id)remote_ref_raw(cpu_id)"] end subgraph subGraph1["Complex Types"] COMPLEX["Custom structsArrays, etc."] end subgraph subGraph0["Primitive Types"] PRIM["bool, u8, u16, u32u64, usize"] end COMPLEX --> DIRECT COMPLEX --> RAW COMPLEX --> REMOTE COMPLEX --> WITH PRIM --> DIRECT PRIM --> RAW PRIM --> REMOTE PRIM --> WITH
Sources: percpu_macros/src/lib.rs(L91 - L93) percpu_macros/src/lib.rs(L100 - L145) percpu/tests/test_percpu.rs(L25 - L31)
Understanding Generated Methods
Each per-CPU variable generates a wrapper struct with a comprehensive set of access methods:
Core Methods (All Types)
offset() -> usize
- Returns variable offset within per-CPU areacurrent_ptr() -> *const T
- Raw pointer to current CPU's datawith_current<F>(f: F)
- Safe closure-based access with preemption handlingremote_ptr(cpu_id: usize) -> *const T
- Raw pointer to specified CPU's data
Primitive Type Methods
For bool
, u8
, u16
, u32
, u64
, usize
only:
read_current() -> T
- Direct value read with preemption handlingwrite_current(val: T)
- Direct value write with preemption handlingread_current_raw() -> T
- Unsafe direct read (no preemption handling)write_current_raw(val: T)
- Unsafe direct write (no preemption handling)
Feature-Dependent Behavior
The generated code adapts based on enabled cargo features:
preempt
feature: AddsNoPreemptGuard
to safe methodssp-naive
feature: Uses global variables instead of per-CPU registersarm-el2
feature: UsesTPIDR_EL2
instead ofTPIDR_EL1
on AArch64
Sources: percpu_macros/src/lib.rs(L94 - L98) README.md(L69 - L79)
Complete Usage Example
This example demonstrates the full workflow from definition to access:
use percpu::*; // Define various per-CPU variables #[def_percpu] static CPU_ID: usize = 0; #[def_percpu] static COUNTER: u64 = 0; struct CpuStats { interrupts: u64, context_switches: u64, } #[def_percpu] static STATS: CpuStats = CpuStats { interrupts: 0, context_switches: 0, }; fn main() { // Initialize the per-CPU system percpu::init(); percpu::init_percpu_reg(0); // Access current CPU data CPU_ID.write_current(0); COUNTER.write_current(100); STATS.with_current(|stats| { stats.interrupts += 1; stats.context_switches += 1; }); // Read values println!("CPU ID: {}", CPU_ID.read_current()); println!("Counter: {}", COUNTER.read_current()); // Access remote CPU data (unsafe) unsafe { *COUNTER.remote_ref_mut_raw(1) = 200; println!("CPU 1 counter: {}", *COUNTER.remote_ptr(1)); } }
Sources: percpu/tests/test_percpu.rs(L34 - L105) README.md(L39 - L52)