我试图在程序集程序中使用线程安全的局部变量。 我在网上搜索过,但我找不到任何简单的东西。
我目前正在使用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测试它。
汇编代码非常简单,我希望保持这种方式,因此可以轻松维护。
问题是:如何使stack0
和stack1
变量线程安全。
答案 0 :(得分:1)
您认为编译器如何实现线程局部变量?尝试使用-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提出的基于堆栈的方法。但我想如果你想要可移植性,你就不会使用汇编程序:)
答案 1 :(得分: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
它是如何工作的,是吗?
如果是这样,只是跳转到间接程序而不是调用它会不容易?然后,您不需要任何额外的变量来保存调用者标志/ return&间接过程直接返回给调用者。
只是一个建议 - 从我做汇编后。
如果必须将呼叫者地址存储在某处,请减去SP,(输入?),然后使用堆栈帧。在某些时候,任何其他东西都可能是线程不安全的。
RGDS, 马丁
好吧,使用TLS可能不是线程不安全的,但是任何递归调用呢?您最终会在TLS中使用另一个堆栈来覆盖它,因此您也可以使用“SP”堆栈
马丁
答案 2 :(得分:0)
?? '经典'局部变量,即。堆栈偏移访问的参数/变量/结果本质上是线程安全的。
如果你需要一个与平台无关的'TLS',可以将一些合适的struct / class实例传递给所有线程,作为创建参数,在恢复所有线程之前在线程字段中,第一条消息传递给线程输入队列或者其他......
RGDS, 马丁
答案 3 :(得分:0)
您可能应该使用(拨打电话)TlsAlloc
& TlsFree
(或其他操作系统等价物)做这样的事情。返回的索引可以存储在全局集合中,只读存储,以便于使用。
根据变量所持有的内容以及使用它们的代码所做的事情,您可能能够使用原子操作,但这可能会产生问题。
答案 4 :(得分:0)
如前所述,局部变量(基于堆栈)本质上是线程安全的,因为每个线程都有自己的堆栈。
所有线程都可以访问的线程安全变量(不是基于堆栈的)可能最好使用自旋锁(或Windows NT引擎中的等效项,一个关键部分)来实现。必须在访问,访问和解锁之前锁定此类变量。一种变体可能是读取是免费的,但写入必须通过锁定/解锁来构建。
AFAIK只有编译器本身不实现线程安全变量。相反,它们提供了访问所需操作系统功能的lib函数。