2017-03-28 10 views
4

は私がカーネルとスレッドの上に、ユーザのスレッドを実装し、ユーザースレッドがカーネルスレッド間を移動する際、thread_local変数は、変数も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固有のハックを使用して元気です。

はここ

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. 
+0

シンプルな 'asm volatile(" ":::" memory ");'あなたのためのトリックですか? – tmyklebu

+0

いや、それはTLS変数のアドレスの再計算を強制しないので、それはレジスタにキャッシュされた値をメモリにアクセスしていないにもかかわらず、それはまだ古いアドレスを使用しています。 – merlin2011

+0

関連するソースコードを投稿できますか? –

答えて

0

g++ --versionは私がテストしたすべてのケースで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; 
} 
関連する問題