【发布时间】:2017-08-22 05:54:52
【问题描述】:
我正在内核和线程之上实现用户线程,并观察到,当用户线程在内核线程之间迁移时,即使变量也被标记为 volatile,也会从先前的内核位置读取 thread_local 变量。
由于编译器仅将用户级swapcontext 视为函数调用,因此下面的示例演示了简单函数调用的问题。
#include <stdio.h>
struct Foo {
int x;
int y;
};
__thread Foo* volatile foo;
void bar() {
asm("nop");
}
void f() {
foo->x = 5;
bar();
asm volatile("":::"memory");
// We desire a second computation of the address of foo here as an offset
// from the FS register.
foo->y = 7;
}
int main(){
foo = new Foo;
f();
delete foo;
}
接下来我们运行以下命令进行编译和反汇编。请注意,-fPIC 标志似乎是重现此问题所必需的,并且对于我的用例也是必需的,因为我正在构建一个库。假设上面的代码在一个名为TL.cc的文件中
g++ -std=c++11 -O3 -fPIC -Wall -g TL.cc -o TL
objdump -d TL
这是函数 f() 的程序集转储。
400760: 53 push %rbx
# Notice this computation happens only once.
400761: 64 48 8b 04 25 00 00 mov %fs:0x0,%rax
400768: 00 00
40076a: 48 8d 80 f8 ff ff ff lea -0x8(%rax),%rax
400771: 48 89 c3 mov %rax,%rbx
400774: 48 8b 00 mov (%rax),%rax
400777: c7 00 05 00 00 00 movl $0x5,(%rax)
40077d: e8 ce ff ff ff callq 400750 <_Z3barv>
# Observe that the value of rbx came from before the function call,
# so if the function bar() actually returned on a different kernel
# thread, we would be referencing the original kernel thread's
# version of foo, instead of the new kernel thread's version.
400782: 48 8b 03 mov (%rbx),%rax
400785: c7 40 04 07 00 00 00 movl $0x7,0x4(%rax)
40078c: 5b pop %rbx
40078d: c3 retq
40078e: 66 90 xchg %ax,%ax
我们观察到寄存器rax 正在从内存中重新加载,但内存位置是在调用bar() 之前确定的。
有没有办法强制重新加载变量的地址作为与fs 寄存器当前值的偏移量?
如果存在,我可以使用特定于 gcc 的 hack。
这是输出g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
【问题讨论】:
-
简单的
asm volatile("":::"memory");对你有用吗? -
不,它不会强制重新计算 TLS 变量的地址,因此它仍然使用旧地址,即使它正在访问内存而不是缓存在寄存器中的值。跨度>
-
能贴出相关源码吗?
-
不是简洁的编译和链接方式,尽管从概念上讲它只不过是 asm 已经显示的内容;计算 TLS 值的地址,然后重复使用。源代码只会显示对线程局部变量的两次访问,一次在线程上下文之前,一次在线程上下文之后,并没有说明问题。
-
@merlin2011:好的,你能发布一个打破的例子还是你只是想让我们猜测?我显然还没有成功猜到导致您的程序集转储的 C++ 代码是什么样的。
标签: c++ multithreading c++11 g++