Safety and Preemption

Relevant source files

This document covers the safety mechanisms and preemption handling in the percpu crate system. It explains how per-CPU data access is protected from preemption-related data races and provides guidance on choosing between different safety models. For information about memory layout and initialization safety, see Memory Layout and Initialization. For details about architecture-specific implementation safety, see Architecture-Specific Code Generation.

Preemption Safety Concepts

Per-CPU data access presents unique safety challenges in preemptible systems. When a task accesses per-CPU data and is then preempted and migrated to another CPU, it may inadvertently access data belonging to a different CPU, leading to data races and inconsistent state.

sequenceDiagram
    participant Task as "Task"
    participant CPU0 as "CPU 0"
    participant CPU1 as "CPU 1"
    participant PerCPUData as "Per-CPU Data"

    Task ->> CPU0: "Access per-CPU variable"
    CPU0 ->> PerCPUData: "Read CPU 0 data area"
    Note over Task,CPU0: "Task gets preempted"
    Task ->> CPU1: "Task migrated to CPU 1"
    Task ->> CPU1: "Continue per-CPU access"
    CPU1 ->> PerCPUData: "Access CPU 1 data area"
    Note over Task,PerCPUData: "Data inconsistency!"

The percpu crate provides two safety models to address this challenge:

Safety ModelDescriptionUse Case
Raw MethodsRequire manual preemption controlPerformance-critical code with existing preemption management
Safe MethodsAutomatic preemption disablingGeneral application code requiring safety guarantees

Sources: percpu_macros/src/lib.rs(L94 - L98)  percpu_macros/src/lib.rs(L109 - L140) 

Safety Models and Method Types

Raw Methods (*_rawfunctions)

Raw methods provide direct access to per-CPU data without automatic preemption control. These methods are marked unsafe and require the caller to ensure preemption is disabled.

flowchart TD
subgraph Examples["Examples"]
    ReadRaw["read_current_raw()"]
    WriteRaw["write_current_raw()"]
    RefRaw["current_ref_raw()"]
    MutRefRaw["current_ref_mut_raw()"]
end
RawAccess["Raw Method Call"]
PreemptCheck["Preemption Disabled?"]
DataAccess["Direct Data Access"]
UnsafeBehavior["Undefined Behavior"]

PreemptCheck --> DataAccess
PreemptCheck --> UnsafeBehavior
RawAccess --> PreemptCheck

Key raw methods include:

  • read_current_raw() - Returns value without preemption protection
  • write_current_raw() - Sets value without preemption protection
  • current_ref_raw() - Returns reference without preemption protection
  • current_ref_mut_raw() - Returns mutable reference without preemption protection

Sources: percpu_macros/src/lib.rs(L113 - L125)  percpu_macros/src/lib.rs(L184 - L197) 

Safe Methods (Automatic Preemption Control)

Safe methods automatically disable preemption during the operation using the NoPreemptGuard mechanism when the preempt feature is enabled.

flowchart TD
subgraph subGraph0["Safe Method Examples"]
    ReadCurrent["read_current()"]
    WriteCurrent["write_current()"]
    WithCurrent["with_current(closure)"]
end
SafeCall["Safe Method Call"]
GuardCreate["NoPreemptGuard::new()"]
PreemptDisabled["Preemption Disabled"]
RawCall["Call corresponding _raw method"]
GuardDrop["Guard dropped (RAII)"]
PreemptReenabled["Preemption Re-enabled"]

GuardCreate --> PreemptDisabled
GuardDrop --> PreemptReenabled
PreemptDisabled --> RawCall
RawCall --> GuardDrop
SafeCall --> GuardCreate

The code generation logic determines preemption handling based on feature flags:

flowchart TD
FeatureCheck["preempt feature enabled?"]
GuardGen["Generate NoPreemptGuard"]
NoGuard["Generate empty block"]
SafeMethod["Safe method implementation"]

FeatureCheck --> GuardGen
FeatureCheck --> NoGuard
GuardGen --> SafeMethod
NoGuard --> SafeMethod

Sources: percpu_macros/src/lib.rs(L129 - L139)  percpu_macros/src/lib.rs(L201 - L207) 

NoPreemptGuard Integration

The NoPreemptGuard provides RAII-based preemption control through integration with the kernel_guard crate. This integration is enabled by the preempt feature flag.

Feature Configuration

Feature StateBehaviorDependencies
preemptenabledAutomatic preemption guardskernel_guardcrate
preemptdisabledNo preemption protectionNo additional dependencies

Guard Implementation

flowchart TD
UserCall["User calls safe method"]
GuardCreation["let _guard = NoPreemptGuard::new()"]
PreemptOff["Preemption disabled"]
DataOperation["Per-CPU data access"]
ScopeEnd["Method scope ends"]
GuardDrop["_guard dropped (RAII)"]
PreemptOn["Preemption re-enabled"]

DataOperation --> ScopeEnd
GuardCreation --> PreemptOff
GuardDrop --> PreemptOn
PreemptOff --> DataOperation
ScopeEnd --> GuardDrop
UserCall --> GuardCreation

The guard is implemented as a module re-export that conditionally includes the kernel_guard::NoPreempt type:

Sources: percpu/src/lib.rs(L14 - L17)  percpu/Cargo.toml(L21 - L22)  percpu_macros/src/lib.rs(L94 - L98) 

Remote Access Safety

Remote CPU access methods (remote_*) require additional safety considerations beyond preemption control, as they access data belonging to other CPUs.

Remote Access Safety Requirements

MethodSafety Requirements
remote_ptr(cpu_id)Valid CPU ID + No data races
remote_ref_raw(cpu_id)Valid CPU ID + No data races
remote_ref_mut_raw(cpu_id)Valid CPU ID + Exclusive access
flowchart TD
subgraph subGraph0["Remote Methods"]
    RemotePtr["remote_ptr(cpu_id)"]
    RemoteRef["remote_ref_raw(cpu_id)"]
    RemoteMut["remote_ref_mut_raw(cpu_id)"]
end
RemoteCall["Remote access method"]
CPUValidation["CPU ID valid?"]
RaceCheck["Data races possible?"]
SafeAccess["Safe remote access"]
UnsafeBehavior["Undefined behavior"]

CPUValidation --> RaceCheck
CPUValidation --> UnsafeBehavior
RaceCheck --> SafeAccess
RaceCheck --> UnsafeBehavior
RemoteCall --> CPUValidation

Sources: percpu_macros/src/lib.rs(L209 - L246) 

Best Practices

Choosing Safety Models

  1. Use safe methods by default - Automatic preemption protection prevents common bugs
  2. Use raw methods for performance-critical paths - When preemption is already managed externally
  3. Enable preempt feature in production - Unless running on single-CPU systems

Safe Usage Patterns

flowchart TD
subgraph Recommendations["Recommendations"]
    Rec1["Prefer safe methods"]
    Rec2["Use with_current for atomicity"]
    Rec3["Minimize raw method usage"]
end
Pattern1["Single Operation"]
Pattern2["Multiple Operations"]
Pattern3["Complex Logic"]
Method1["read_current() / write_current()"]
Method2["with_current(closure)"]
Method3["Manual guard management"]

Pattern1 --> Method1
Pattern2 --> Method2
Pattern3 --> Method3

Feature Flag Configuration

For different deployment scenarios:

ScenarioFeature ConfigurationRationale
Production kernelpreemptenabledFull safety guarantees
Single-CPU embeddedsp-naiveenabledNo preemption needed
Performance testingpreemptdisabledMeasure raw performance

Sources: percpu/Cargo.toml(L15 - L25)  percpu_macros/src/lib.rs(L100 - L145)