| Crates.io | percpu_macros |
| lib.rs | percpu_macros |
| version | 0.2.2 |
| created_at | 2024-07-15 12:45:37.299653+00 |
| updated_at | 2025-12-25 04:04:36.727994+00 |
| description | Macros to define and access a per-CPU data structure |
| homepage | https://github.com/arceos-org/arceos |
| repository | https://github.com/arceos-org/percpu |
| max_upload_size | |
| id | 1303815 |
| size | 28,829 |
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.
| 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.
#[percpu::def_percpu]
static CPU_ID: usize = 0;
// initialize per-CPU data areas.
percpu::init();
// set the thread pointer register to the per-CPU data area 0.
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 = .;
. = _percpu_load_start + ALIGN(64) * CPU_NUM;
}
. = _percpu_end;
sp-naive: For single-core use. In this case, each per-CPU data is
just a global variable, architecture-specific thread pointer register is
not used.preempt: For preemptible system use. In this case, we need to disable
preemption when accessing per-CPU data. Otherwise, the data may be corrupted
when it's being accessing and the current thread happens to be preempted.arm-el2: For ARM system running at EL2 use (e.g. hypervisors).
In this case, we use TPIDR_EL2 instead of TPIDR_EL1
to store the base address of per-CPU data area.non-zero-vma: Allow the per-CPU data area (section .percpu) to be placed
at a non-zero virtual memory address. By default, the section is placed
at virtual address 0x0 to simplify the calculation of offsets, however, it's
not allowed by some linkers/loaders. Without this feature enabled, it's likely
impossible to use this crate in user-space programs.