According to ARM documentation, the thread ID registers like TPIDR_EL0
or TPIDR_EL1
,
Provide locations to store the IDs of software threads and processes for OS management purposes. These registers have no effect on processor behavior.
Why would someone want to store the thread ID in a special register? Do ARM processors require threads to have special structures in memory just like the MMU has? Is a Thread something special to ARM, something ARM expect to find somewhere? Or can I implement threads (efficiently) without using this register at all?
I'm asking because I found this code on the Zircon Kernel from Fuchsia OS:
static inline void arch_set_current_thread(Thread* t) {
__arm_wsr64("tpidr_el1", (uint64_t)&t->arch_.thread_pointer_location);
__isb(ARM_MB_SY);
}
Right at boot it creates a thread and stores its pointer in tpidr_el1
Everything related to thread-local storage in userspace needs to be kept in a per-thread structure. You need to keep the address of this structure somewhere. In armv8, TPIDR_EL0
can be used for this purpose. In x86_64, typically the fs
segment register was repurposed for this usage.
Fuchsia's ABI for Thread-Local Storage is documented in their website.
In fuchsia, the TPIDR_EL0
will get you the pthread structure. See __allocate_thread
for how some of this memory is allocated.
One usage example (other than thread-local variables), is the SafeStack feature, which stores a second stack pointer in the pthread structure.
For the kernel, in armv8, TPIDR_EL1
is used, for a similar purpose, to hold a pointer for the kernel Thread structure.
Note that in armv8, there's a register for EL0
(userspace) and EL1
(kernelspace). In x86-64, there's no separation, and the handling is kind of awkward: the kernel has an internal place to store the "kernel version" of the gs register, and uses the swapgs
instruction to change between the userspace and kernelspace gs registers.