跨内核线程迁移后是不是可以强制重新加载 thread_local 变量?
Posted
技术标签:
【中文标题】跨内核线程迁移后是不是可以强制重新加载 thread_local 变量?【英文标题】:Is it possible to force a reload of a thread_local variable after migration across kernel threads?跨内核线程迁移后是否可以强制重新加载 thread_local 变量? 【发布时间】: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++ 代码是什么样的。
【参考方案1】:
我编写了以下 hack,它在我测试的所有情况下都强制重新加载 TLS,但希望得到有关它可能被破坏的所有方式的反馈。
#define SafeTLS(TypeName, name) \
struct name##_SafeClass \
name##_SafeClass& \
__attribute__ ((noinline)) \
operator=(const TypeName& other) \
asm (""); \
name = const_cast<TypeName&>(other); \
return *this; \
\
TypeName& \
__attribute__ ((noinline)) \
operator->() \
asm (""); \
return get(); \
\
operator TypeName() return get(); \
TypeName& \
__attribute__ ((noinline)) \
get() \
asm (""); \
return name; \
\
\
TypeName* \
operator&() \
asm (""); \
return &name; \
\
name##_Safe
这是一个使用它的更复杂的测试用例。
#include <stdio.h>
#include "TLS.h"
struct Foo
int x;
int y;
;
__thread Foo* volatile foo;
__thread int bar;
SafeTLS(Foo* volatile, foo);
SafeTLS(int, bar);
void f2()
asm("nop");
void f()
foo_Safe->x = 5;
f2();
asm volatile("":::"memory");
// We desire a second computation of the address of foo here as an offset
// from the FS register.
(*foo_Safe).y = 7;
bar = 7;
printf("%d\n", bar);
printf("%d %d\n", foo->x, foo->y);
bar = 8;
printf("%d\n", bar_Safe.get());
int main()
foo = new Foo;
f();
delete foo;
【讨论】:
以上是关于跨内核线程迁移后是不是可以强制重新加载 thread_local 变量?的主要内容,如果未能解决你的问题,请参考以下文章
UIWebView 完成加载后,如何强制重新布局 UITableViewCell?