【问题标题】:Assembly - Thread Safe Local Variables汇编 - 线程安全的局部变量
【发布时间】:2011-05-24 13:25:09
【问题描述】:

我正在尝试在汇编程序中使用线程安全的局部变量。 我在网上搜索过,但没有发现任何简单的东西。

我目前正在使用 GCC 汇编器,因为该程序是 C 代码和汇编的混合体,但最终程序将包含多平台/调用约定的代码。

现在,我已经使用 .lcomm 伪操作声明了我的变量。 据我了解,这些变量将放在.bss 部分。 所以我猜它们会被所有线程共享。

有没有办法在程序集中直接使用一种 TLS 变量,或者我应该使用特定于平台的实现,例如 Windows 上的 pthread__declspec

希望它足够清楚。不要犹豫,询问是否需要更多信息。

谢谢大家,

编辑

这是有问题的代码:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp
    movq    %rsp,   %rbp

    xor     %rax,   %rax

    popq    stack0( %rip )
    popq    stack1( %rip )

    callq   *%rdi

    pushq   stack1( %rip )
    pushq   stack0( %rip )

    leave
    ret

基本上,它用于将调用重定向到 C 函数。

C 原型是:

extern uint64_t XSRuntime_CallMethod( void ( *m )( void * self, ... ), ... );

它将函数指针作为第一个参数,因此是 callq *%rdi,因为我正在使用系统 V ABI 进行测试。

汇编代码非常简单,我想保持这种方式,以便于维护。

问题是:如何使stack0stack1 变量线程安全。

【问题讨论】:

  • 您在用汇编程序编写时为什么担心特定于平台的问题?
  • 因为有一种“跨平台”的方式会更容易,更容易维护。
  • 我不明白汇编是如何跨平台的。我认为每个不同的平台都有不同的规则。
  • 是的,没错。我只是想尽量减少不同实现之间的差异。 :)
  • 下次请尝试先问实际问题,而不是“[我决定用 X 解决它,所以] 我该怎么做 X?”

标签: multithreading assembly thread-local-storage


【解决方案1】:

对汇编程序不太熟悉,所以:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp // save BP
    movq    %rsp,   %rbp // load BP with SP

    xor     %rax,   %rax  // clear AX

    popq    stack0( %rip )  // pop return address into STACK0
    popq    stack1( %rip )  // pop flags into stack1

    callq   *%rdi  // call the indirect procedure, so putting flags/return to         XSRuntime_CallMethod onto stack

    pushq   stack1( %rip ) // put caller flags onto stack
    pushq   stack0( %rip ) // put caller return onto stack

    leave // clean passed parameters from stack
    ret   // and back to caller

这就是它的工作原理,是吗?

如果是这样,直接跳转到间接过程而不是调用它不是更容易吗?然后,您不需要任何额外的变量来保存调用者标志/返回,并且间接过程直接返回给调用者。

只是一个建议——因为我做了汇编程序。

如果您必须将调用方地址存储在某处,请删除 SP(输入?),然后使用堆栈帧。在某些时候,其他任何东西都可能是线程不安全的。

Rgds, 马丁

好吧,使用 TLS 可能不是线程不安全的,但是任何递归调用呢?您最终会在 TLS 中使用另一个堆栈来解决此问题,因此您不妨使用“SP”堆栈

马丁

【讨论】:

  • jmp 是有效的解决方案...非常感谢; )
【解决方案2】:

您认为编译器如何实现线程局部变量?尝试用 -S 或 /FAs 编译这样的程序,你会看到。提示:它必须依赖特定于操作系统的 API 或其他详细信息才能访问 TLS 存储。有时准备步骤隐藏在 CRT 中,但没有单一的方法可以做到。

例如,最近的 MSVC 是这样做的:

_TLS    SEGMENT
?number@@3HA DD 01H DUP (?)             ; number
_TLS    ENDS
EXTRN   __tls_array:DWORD
EXTRN   __tls_index:DWORD
_TEXT   SEGMENT
[...]
mov eax, DWORD PTR __tls_index
mov ecx, DWORD PTR fs:__tls_array
mov edx, DWORD PTR [ecx+eax*4]
mov eax, DWORD PTR ?number@@3HA[edx]

如您所见,它使用由 CRT 初始化的特殊变量。

在最近的 Linux 上,GCC 可以使用 TLS 特定的重定位:

.globl number
    .section    .tbss,"awT",@nobits
number:
    .zero   4
    .text
    [...]
    movl    %gs:number@NTPOFF, %eax

如果您想要可移植性,最好不要依赖此类特定于操作系统的细节,而是使用通用 API(例如 pthread)或使用 Martin 提出的基于堆栈的方法。但我想如果你想要可移植性,你不会使用汇编程序:)

【讨论】:

    【解决方案3】:

    ?? “经典”局部变量,即。通过堆栈偏移量访问的参数/变量/结果本质上是线程安全的。

    如果您需要一个与平台无关的“TLS”,请将一些合适的结构/类实例传递给所有线程,或者作为创建参数,在恢复所有线程之前的线程字段中,第一条消息到线程输入队列或者什么...

    Rgds, 马丁

    【讨论】:

    • 感谢您的回复。请查看编辑...我使用.lcomm 使用两个变量。所以它们不在堆栈中。
    【解决方案4】:

    如前所述,局部变量(基于堆栈)本质上是线程安全的,因为每个线程都有自己的堆栈。

    所有线程都可以访问的线程安全变量(不是基于堆栈的)可能最好使用自旋锁(或 Windows NT 引擎中的等效项,临界区)来实现。这样的变量必须在访问之前锁定,访问然后解锁。一种变体可能是读取是免费的,但写入必须通过锁定/解锁来构建。

    仅 AFAIK 编译器本身不实现线程安全变量。相反,它们提供了访问所需操作系统功能的库函数。

    【讨论】:

      【解决方案5】:

      您可能应该使用(调用)TlsAllocTlsFree(或它们的其他操作系统等效项)来执行此类操作。返回的索引可以存储在全局集中一次,只读变量以便于使用。

      根据变量所包含的内容以及使用它们的代码的作用,您可能能够摆脱原子操作,但这可能会产生其自身的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-03-07
        • 2015-07-07
        • 1970-01-01
        • 2012-02-07
        • 1970-01-01
        • 1970-01-01
        • 2014-12-24
        相关资源
        最近更新 更多