ax-percpu
Typed per-CPU layout, initialization, and access for no_std kernels.
ax-percpu is dynamic-only. The final ELF contains one layout template; the
platform allocates one writable runtime area per CPU and initializes the
complete layout before any CPU is bound. Architecture register ownership is
provided by the separate cpu-local crate.
Runtime contract
The initialization sequence is fixed:
- The linker retains exactly one
.percpu.templateplus the.percpu.initand.percpu.aligndescriptor tables. - The platform allocates shutdown-lifetime storage for every CPU area.
initialize_layout(PerCpuRegion)validates the complete geometry and descriptor tables before the first destination write.- Each
CpuAreaPrefixand each typed value is constructed once at its final runtime address. - The layout is frozen; the platform may then install
area(cpu).cpu_area()through its offline-CPU boundary.
There is no linked runtime layout or static per-CPU replication. Changing the runtime CPU count therefore does not change the ELF template size.
The linker contract uses only these output sections:
.percpu.template.percpu.init.percpu.align
Generated storage is placed in .percpu.template.storage; the fixed prefix
and end sentinel use .percpu.template.header and .percpu.template.end.
Linker boundaries use __PERCPU_* and __CPU_LOCAL_* names.
Typed access
static CPU_ID: usize = 0;
Primitive values use the matching atomic storage type. Object initializers are retained as typed descriptor thunks and construct one independent value in each final runtime area; arbitrary Rust object bytes are never duplicated from the ELF template.
Current access requires a scoped CpuPin, which validates the live register,
area self pointer, and CPU index when it is created. A mutable object borrow
additionally requires ExclusiveCpu; only the unsafe guard integration can
create that stronger capability after excluding IRQ/re-entry and conflicting
remote access.
Low-level execution-context owners and offline-bootstrap code can use the hidden
with_current_cpu_area and with_current_cpu_area_mut callbacks before a
CpuPin exists. These callbacks select the architecture-owned CPU area directly
instead of routing CPU-owned state through current execution-context
publication. The caller retains the complete migration, context-switch,
IRQ/re-entry, and remote aliasing contract. No runtime path uses these callbacks
yet; they are reserved for future owner-guard and offline CPU bootstrap
integration.
| Operation | Required protection |
|---|---|
| Atomic scalar | Migration disabled; local IRQs may remain enabled |
Shared T: Sync object |
Migration disabled; the object synchronizes itself |
| Local mutable object | Migration, IRQ/re-entry, and conflicting remote access excluded |
| Pre-pin CPU-owner object | Migration and context switches excluded; mutable access also excludes IRQ/re-entry and remote conflicts |
| Context switch | IRQs and migration disabled; transactional tokens consumed |
| Preemption safe point | IRQs disabled; runtime baton claimed before pending release |
| vCPU run | Migration disabled; exit assembly restores host registers before Rust |
| CPU-area initialization | CPU offline and raw area exclusively owned |
Host tests
The crate exposes one feature, host-test. It provides:
let layout = initialize?;
let area = area?;
unsafe ;
unsafe
The helper owns process-lifetime dynamic storage and initializes it once. Each modeled CPU thread must explicitly install its own binding.
Validation
Licensed under Apache-2.0.