c++ __thread 关键词内部

636 阅读2分钟

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的文档可以看出,msrmrs是一对指令,其作用是general registersystem 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类型变量存储的位置。

更加具体的细节查看下面参考文章。

参考文章

Thread-Local Storage Access Models

tls

Thread Local Storage

All about thread-local storage