6

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

Andre Holzner
  • 18,333
  • 6
  • 54
  • 63
Guerlando OCs
  • 1,886
  • 9
  • 61
  • 150
  • 3
    The OS needs to remember which thread/process is currently running in a given (logical) CPU. It needs a CPU register that can only be changed in supervisor mode and that can designate an area of memory. On x86 it's the `fs` or `gs` register, ARM has `TPIDR_ELx`. A per-CPU area of memory is necessary as the kernel code is made to be run equally on any CPU (so the code is identical but the pointers are not). – Margaret Bloom Nov 16 '20 at 14:20
  • @MargaretBloom makes total sense, thanks – Guerlando OCs Nov 17 '20 at 01:31
  • 1
    Thread local data can be indexed via EL1. Just like 'PIC', you can have 'static base' code. Where all globals are referenced relative to a fixed register. The implementation of threads is similar to 'static base'. However, all threads in the process can use absolute addressing for globals (shared between threads), but they use EL1 for the 'thread local' variables. To switch threads only involves changing this register (which **can** be done in user space), but the OS needs to record the active thread on a context switch. When a hypervisor is involved it must be trapped. – artless noise Nov 18 '20 at 13:58

1 Answers1

6

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.

Marco
  • 2,796
  • 19
  • 24