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 Model | Description | Use Case |
---|---|---|
Raw Methods | Require manual preemption control | Performance-critical code with existing preemption management |
Safe Methods | Automatic preemption disabling | General 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 protectionwrite_current_raw()
- Sets value without preemption protectioncurrent_ref_raw()
- Returns reference without preemption protectioncurrent_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 State | Behavior | Dependencies |
---|---|---|
preemptenabled | Automatic preemption guards | kernel_guardcrate |
preemptdisabled | No preemption protection | No 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
Method | Safety 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
- Use safe methods by default - Automatic preemption protection prevents common bugs
- Use raw methods for performance-critical paths - When preemption is already managed externally
- 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:
Scenario | Feature Configuration | Rationale |
---|---|---|
Production kernel | preemptenabled | Full safety guarantees |
Single-CPU embedded | sp-naiveenabled | No preemption needed |
Performance testing | preemptdisabled | Measure raw performance |
Sources: percpu/Cargo.toml(L15 - L25) percpu_macros/src/lib.rs(L100 - L145)