Expand description
§percpu
Define and access per-CPU data structures.
All per-CPU data is placed into several contiguous memory regions called
per-CPU data areas, the number of which is the number of CPUs. Each CPU
has its own per-CPU data area. The architecture-specific per-CPU register
(e.g., GS_BASE on x86_64) is set to the base address of the area on
initialization.
When accessing the per-CPU data on the current CPU, it first use the per-CPU register to obtain the corresponding per-CPU data area, and then add an offset to access the corresponding field.
§Supported Architectures
| Architecture | per-CPU Register Used |
|---|---|
| ARM (32-bit) | TPIDRURO (c13) |
| RISC-V | gp |
| AArch64 | TPIDR_ELx |
| x86_64 | GS_BASE |
| LoongArch | $r21 |
Notes for ARM (32-bit): We use
TPIDRURO(User Read-Only Thread ID Register, CP15 c13) to store the per-CPU data area base address. This register is accessed via coprocessor instructionsmrc p15, 0, <Rt>, c13, c0, 3(read) andmcr p15, 0, <Rt>, c13, c0, 3(write).
Notes for RISC-V: Since RISC-V does not provide separate thread pointer registers for user and kernel mode, we temporarily use the
gpregister to point to the per-CPU data area, while thetpregister is used for thread-local storage.
Notes for AArch64: When feature
arm-el2is enabled,TPIDR_EL2is used. Otherwise,TPIDR_EL1is used.
§Features
Mode feature:
-
sp-naive: Single-core mode. Each per-CPU data is just a global variable. Architecture-specific thread pointer register is not used.Feature Mode Per-CPU Data Area sp-naiveSingle-core Global vars (none) Multi-core .percpusection or user-provided memory
Other features (can be combined with any mode):
non-zero-vma: Allows the.percpusection to be placed at a non-zero VMA. Required for Linux user-space programs as some linkers don’t support VMA 0.sp-naivedoes not neednon-zero-vmaas it uses global variables.preempt: For preemptible system use. Disables preemption when accessing per-CPU data to prevent corruption.arm-el2: For ARM system running at EL2 use (e.g. hypervisors). UsesTPIDR_EL2instead ofTPIDR_EL1.
§Usage
§Initialization
Two methods are provided to initialize the per-CPU data areas:
init_in_place(): Initialize using the.percpusection (static allocation), which should be reserved in the linker script. See the example linker script for an example. ReturnsResult<usize, InitError>.init(base, cpu_count): Initialize with user-provided memory (dynamic allocation), user must usepercpu_area_layout_expected(cpu_count)to calculate the required memory size. It’s highly recommended to align the memory to 4KiB page size. ReturnsResult<usize, InitError>.
After initialization, the per-CPU data areas are ready to be used. You can use
init_percpu_reg(cpu_id) on each CPU to set the per-CPU register to the base
address of the corresponding per-CPU data area.
§Accessing Per-CPU Data
To access the per-CPU data on the current CPU, you can use the current_ptr,
current_ref_raw, current_ref, with_current (recommended, it handles
preemption automatically), reset_to_init. Primitive unsigned types and booleans
can be accessed directly using read_current, write_current (with preemption
handling automatically) and read_current_raw, write_current_raw (without
preemption handling).
It’s also possible to access the per-CPU data on other CPUs using remote_ptr,
remote_ref_raw, remote_ref. Such operations are intrinsically unsafe and
it’s the caller’s responsibility to ensure that the CPU ID is valid and that
data races will not happen.
To reset a per-CPU data to the initial value, you can use reset_to_init.
§Examples
#[percpu::def_percpu]
static CPU_ID: usize = 0;
// Option 1: Use init_in_place() to use the `.percpu` section (statically-
// allocated during linking). Enough space must be reserved for the `.percpu`
// section in the linker script.
percpu::init_in_place().unwrap();
percpu::init_percpu_reg(0);
// Option 2: Use init() with user-provided memory and cpu_count for dynamic
// initialization. The caller is responsible for allocating memory for the per-CPU
// data areas. Use `percpu_area_layout_expected()` to calculate the required memory
// size.
let cpu_count = 4;
let layout = percpu::percpu_area_layout_expected(cpu_count);
let base = unsafe { std::alloc::alloc(layout) as usize };
percpu::init(base as *mut u8, cpu_count).unwrap();
percpu::init_percpu_reg(0);
// Access the per-CPU data `CPU_ID` on the current CPU.
println!("{}", CPU_ID.read_current()); // prints "0"
CPU_ID.write_current(1);
println!("{}", CPU_ID.read_current()); // prints "1"Currently, you need to modify the linker script manually, add the following lines to your linker script:
. = ALIGN(4K);
_percpu_start = .;
_percpu_end = _percpu_start + SIZEOF(.percpu);
.percpu 0x0 (NOLOAD) : AT(_percpu_start) {
_percpu_load_start = .;
*(.percpu .percpu.*)
_percpu_load_end = .;
// If you want to use the `.percpu` section for static initialization, you
// need to reserve enough space for the `.percpu` section, as shown below.
. = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
. = _percpu_end;Structs§
- EXAMPLE_
PERCPU_ DATA_ WRAPPER - Wrapper struct for the per-CPU data
EXAMPLE_PERCPU_DATA
Enums§
- Init
Error - Errors that can occur while initializing per-CPU data areas.
Statics§
- EXAMPLE_
PERCPU_ DATA doc - Example per-CPU data for documentation only.
Functions§
- init
- Initialize per-CPU data areas with user-provided memory.
- init_
in_ place - Initialize per-CPU data areas using the
.percpusection. - init_
percpu_ reg - Initializes the per-CPU data register for the current CPU.
- percpu_
area_ base - Returns the base address of the per-CPU data area for the given CPU.
- percpu_
area_ layout_ expected - Returns the expected layout for the per-CPU data area for the given number of CPUs.
- percpu_
area_ num - Returns the number of per-CPU data areas reserved in the
.percpusection. - percpu_
area_ size - Returns the per-CPU data area size for one CPU.
- read_
percpu_ reg - Reads the architecture-specific per-CPU data register.
- write_
percpu_ ⚠reg - Writes the architecture-specific per-CPU data register.
Attribute Macros§
- def_
percpu - Defines a per-CPU static variable.