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 TypeDirect Read/Writewith_current()Remote AccessNotes
boolOptimized methods generated
u8,u16,u32Optimized methods generated
u64,usizeOptimized methods generated
Custom structsReference-based access only
ArraysReference-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 area
  • current_ptr() -> *const T - Raw pointer to current CPU's data
  • with_current<F>(f: F) - Safe closure-based access with preemption handling
  • remote_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 handling
  • write_current(val: T) - Direct value write with preemption handling
  • read_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: Adds NoPreemptGuard to safe methods
  • sp-naive feature: Uses global variables instead of per-CPU registers
  • arm-el2 feature: Uses TPIDR_EL2 instead of TPIDR_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)