Thread-Local Storage Access Models
在C/C++ 中,有修饰符
__thread如下:
// __thread 修饰符的作用在于,其制定了变量a 为每个线程私有
// 即每一个线程都会保存一个 a 变量,每一个线程中的a 变量不可被其它线程所访问,
// __thread 可以用于修饰全局变量,函数内的静态变量,不能修饰函数的局部变量或者class的普通成员变量,且__thread变量值只能初始化为编译器常量
__thread int a = 1;
示例代码
不使用__thread:
// Type your code here, or load an example.
int global_value = 1;
int square(int num) {
return num * global_value;
}
在arm-v8-a clang 16.0.0,-O0的情况下,编出来的汇编如下:
square(int): // @square(int)
sub sp, sp, #16
str w0, [sp, #12]
ldr w8, [sp, #12]
adrp x9, global_value
ldr w9, [x9, :lo12:global_value]
mul w0, w8, w9
add sp, sp, #16
ret
global_value:
.word 1 // 0x1
使用__thread的情况下:
// Type your code here, or load an example.
__thread int global_value = 1;
int square(int num) {
return num * global_value;
}
在arm-v8-a clang 16.0.0,-O0的情况下,编出来的汇编如下:
square(int): // @square(int)
sub sp, sp, #16
str w0, [sp, #12]
ldr w8, [sp, #12]
mrs x9, TPIDR_EL0
add x9, x9, :tprel_hi12:global_value
add x9, x9, :tprel_lo12_nc:global_value
ldr w9, [x9]
mul w0, w8, w9
add sp, sp, #16
ret
global_value:
.word 1 // 0x1
可以看到使用了TPIDR_EL0寄存器,以及下面两个奇奇怪怪的add指令,具体解析如下文。
arm system register
关于arm上下文切换参考Context switching。
要理解为什么加上__thread之后的汇编发生了这种变化,首先得了解mrs指令,以及TPIDR_EL0是什么。
从arm的文档可以看出,msr和mrs是一对指令,其作用是general register和system register之间的数据传递,用法参考The system control register。
剩下的TPIDR_EL0是什么?
首先可以肯定的是这是一个system register,其次这个寄存器要分为两个部分来看。
TPIDR: 在AMRv8中这是一个专门用于保存线程ID的寄存器;EL0: 优先级的等级。
简单来说,mrs x9, TPIDR_EL0这条指令,就是以EL0的优先级来从TPIDR寄存器中拿到当前thread的ID。关于这个ID具体是什么,有这一段描述:
For non-global entries, when the TLB is updated and the entry is marked as non-global, a value is stored in the TLB entry in addition to the normal translation information. This value is called the Address Space ID (ASID), which is a number assigned by the OS to each individual task. Subsequent TLB look-ups only match on that entry if the current ASID matches with the ASID that is stored in the entry. This permits multiple valid TLB entries to be present for a particular page marked as non-global, but with different ASID values. In other words, we do not necessarily need to flush the TLBs when we context switch.
可以看出,这个ID其实就是一段每一个线程私有的内存地址。
:tprel_hi12以及:tprel_lo12_nc较为复杂,简单来说就是aarch64对于thread local类型变量存储的位置。
更加具体的细节查看下面参考文章。