リンカーとライブラリ

第 8 章 スレッド固有領域 (TLS)

コンパイル環境は、スレッド固有データの宣言をサポートします。このデータはスレッド特有データやスレッド専用データと呼ばれることもありますが、一般には頭字語で TLS と呼ばれます。変数をスレッド固有として宣言すると、コンパイラは自動的にこれらの変数をスレッド単位で割り当てます。

この機能の組み込みサポートには、次に示す 3 つの目的があります。

C/C++ プログラミングインタフェース

以下の例に示すように、__thread キーワードを使用すると、変数をスレッド固有として宣言できます。

__thread int i;
__thread char *p;
__thread struct state s;

ループの最適化の際に、コンパイラは必要に応じてスレッド固有一時領域を作成することがあります。

適用性

__thread キーワードは任意の大域変数、ファイルスコープの静的変数、または関数スコープの静的変数に適用できます。常にスレッド固有である自動変数には影響を与えません。

初期化

C++ では、初期化に静的なコンストラクタが必要となる場合には、スレッド固有変数の初期化が行われないことがあります。静的なコンストラクタを必要としない限り、スレッド固有変数は通常の静的変数に有効な任意の値に初期化できます。

変数は、(スレッド固有であるかどうかにかかわらず) スレッド固有変数のアドレスに静的に初期化することはできません。

結合

スレッド固有変数の宣言と参照は外部的に行えます。この場合、通常のシンボルと同じ割り込み規則に従う必要があります。

動的な読み込みの制限

共有ライブラリは、プロセスの起動時に動的に読み込むことができます。またプロセスの起動後には、遅延読み込み、フィルタ、または dlopen(3DL) を介して、動的に読み込むことができます。スレッド固有変数に対する参照を含む共有ライブラリは、その参照を含むすべての変換ユニットが動的な TLS モデルでコンパイルされた場合に限って、起動後の読み込みが可能です。

静的な TLS モデルは、比較的高速なコードを生成します。しかし、コンパイルされた、このモデルを使用するコードは、起動後に動的に読み込まれるライブラリ内のスレッド固有変数を参照することができません。動的な TLS モデルは、あらゆる TLS を参照できます。これらのモデルについては、スレッド固有領域のアクセスモデルで説明しています。

アドレス演算子

スレッド固有変数には、アドレス演算子 & を使用できます。この演算子は、実行時に評価されて、現在のスレッド内の変数のアドレスを返します。この演算子によって取得されたアドレスは、アドレスを評価したスレッドが存在する限り、プロセス内のあらゆるスレッドで自由に使用できます。スレッドが終了した時点で、そのスレッド内のスレッド固有変数を指すポインタはすべて無効になります。

スレッド固有変数のアドレスを取得するために dlsym(3DL) を使用すると、dlsym() を呼び出したスレッド内におけるその変数のインスタンスのアドレスが返されます。

スレッド固有領域 (TLS) セクション

コンパイル時に割り当てられるスレッド固有データのコピーは、実行される個々のスレッドに、個別に関連付けられる必要があります。このデータを提供するために、TLS セクションを使用してサイズと初期の内容を指定します。

コンパイル環境は、SHF_TLS フラグで識別されるセクション内に TLS を割り当てます。これらのセクションは、領域がどのように宣言されているかにもとづき、初期化された TLS と初期化されていない TLS を提供します。

初期化されていないセクションは、適切な整列になるようにパッドを入れられて、初期化されたセクションの直後に割り当てられます。結合されたこれらのセクションは、新しいスレッドの作成時に TLS の割り当てに使用できる TLS テンプレートとなります。

このテンプレートの初期化された部分を、TLS 初期化イメージと呼びます。初期化されたスレッド固有変数の結果として発生する再配置はすべて、このテンプレートに適用されます。その後、新しいスレッドが初期値を要求すると、再配置された値が使用されます。

TLS シンボルのシンボルタイプは STT_TLS です。これらのシンボルには、TLS テンプレートの先頭からの相対オフセットが割り当てられます。これらのシンボルに関連付けられた実際の仮想アドレスとは無関係です。このアドレスが指すのはテンプレートだけで、各データ項目のスレッドごとのコピーではありません。

動的実行可能ファイルと共有オブジェクトでは、STT_TLS シンボルの st_value フィールドに、定義済みシンボルの場合は割り当てられたオフセットが、未定義シンボルの場合はゼロが含まれます。

TLS へのアクセスをサポートするために、再配置がいくつか定義されます。SPARC: スレッド固有領域の再配置のタイプx86: スレッド固有領域の再配置のタイプを参照してください。タイプ STT_TLS のシンボルは、TLS 再配置によってのみ参照されます。TLS 再配置は、タイプ STT_TLS のシンボルだけを参照します。

動的実行可能ファイルと共有オブジェクトでは、PT_TLS プログラムエントリは TLS テンプレートの説明に使用され、以下の構成要素を持っています。

表 8–1 ELF PT_TLS プログラムヘッダーエントリ

構成要素 

値 

p_offset

TLS 初期化イメージのファイルオフセット

p_vaddr

TLS 初期化イメージの仮想メモリーアドレス

p_paddr

予約済み 

p_filesz

TLS 初期化イメージのサイズ

p_memsz

TLS テンプレートの合計サイズ

p_flags

PF_R

p_align

TLS テンプレートの整列

スレッド固有領域の実行時の割り当て

TLS は、プログラムの存続中に、次の 3 つの機会に作成されます。

スレッド固有データ領域は、図 8–1 に示すように実行時に配置されます。

図 8–1 スレッド固有領域の実行時のレイアウト

スレッド固有領域の実行時のレイアウト。

プログラムの起動

プログラムの起動時に、実行システムはメインスレッド用の TLS を作成します。

まず実行時リンカーが、読み込まれたすべての動的オブジェクト (動的な実行可能ファイルを含む) の TLS テンプレートを論理的に結合し、単一の静的なテンプレートとしてまとめます。各動的オブジェクトの TLS テンプレートには、結合されたテンプレート内のオフセット tlsoffsetm が次のように割り当てられます。

tlssizem+1 は動的オブジェクト m の割り当てテンプレートのサイズで、alignm+1 は整列です (1 <= m <= MM は読み込まれる動的オブジェクトの合計数)。 round(offset, align) 関数は、align の次の倍数に丸められたオフセットを返します。TLS テンプレートは、スレッドポインタ tpt の直前に配置されます。TLS データに対するアクセスは、tpt からの減算にもとづいて行われます。

次に、実行時リンカーは起動時の TLS 割り当てサイズの合計 tlssizeS を計算します。これは、tlsoffsetM に等しくなります。

続いて実行時リンカーは、初期化レコードのリンクリストを作成します。このリスト内の各レコードは、読み込まれた動的オブジェクトごとにその TLS 初期化イメージを記述するものであり、以下のフィールドを含みます。

スレッドライブラリは、この情報を使用して初期スレッドに領域を割り当てます。この領域が初期化され、初期スレッド用に動的な TLS ベクトルが作成されます。

スレッドの作成

初期スレッドと、新しく作成されるスレッドに対して、スレッドライブラリは読み込まれる動的オブジェクトごとに新しい TLS ブロックを割り当てます。ブロックは、個別に割り当てられることも、単一の連続ブロックとして割り当てられることもあります。

各スレッド t は関連するスレッドポインタを持ち、それはスレッド制御ブロック TCB を指します。スレッドポインタ tp には、常に、現在動作しているスレッドの tpt の値が含まれます。

続いてスレッドライブラリは、現在のスレッド t のためにポインタのベクトル dtvt を作成します。各ベクトルの最初の要素には、ベクトルを拡張すべきタイミングを決定するために使用される生成番号 gent が入ります。スレッド固有領域ブロックの遅延割り当てを参照してください。

ベクトル内の残りの各要素 dtvt,m は、動的オブジェクト m に属する TLS 用に予約されたブロックへのポインタです。

起動後動的に読み込まれたオブジェクトについては、スレッドライブラリは TLS ブロックの割り当てを延期します。この割り当ては、読み込まれたオブジェクト内で TLS 変数に対して最初の参照が行われる時に発生します。起動後動的に読み込まれたオブジェクト内で定義された TLS に対する参照はすべて、動的な TLS モデルを使用する必要があります。割り当てが延期されたブロックの場合、ポインタ dtvt,m は実装が定める特別な値に設定されます。


注 –

実行時リンカーは、すべての起動オブジェクトがベクトル内の単一の要素 dtvt,1 を共有するように、それらのオブジェクトの TLS テンプレートをグループ化することがあります。このグループ化によって、前述のオフセット計算や初期化レコードのリストの作成が影響を受けることはありません。しかし、以下の節では、M の値 (オブジェクトの合計数) は値 1 から始まっています。


続いて、スレッドライブラリが、新しい領域ブロック内の対応する場所に初期化イメージをコピーします。

起動後の動的読み込み

プロセスの起動に続いて TLS を含む共有ライブラリが読み込まれると、実行時リンカーは、初期化レコードのリストを拡張して新しいライブラリの初期化テンプレートを含めます。新しいオブジェクトには、インデックス m = M + 1 が与えられます (カウンタ M は 1 ずつ増える)。しかし、新しい TLS ブロックの割り当ては、それらが実際に参照されるまで延期されます。

TLS を含むライブラリの読み込みが解除されると、そのライブラリによって使用されている TLS ブロックは解放されます。

スレッド固有領域ブロックの遅延割り当て

動的な TLS モデルでは、スレッド t がオブジェクト m の TLS ブロックにアクセスする必要が生じた場合、コードは dtv t を更新し、TLS ブロックの初期割り当てを行います。スレッドライブラリは、動的な TLS 割り当てが行えるように、次のインタフェースを提供します。

typedef struct {
    unsigned long ti_moduleid;
    unsigned long ti_tlsoffset;
} TLS_index;

extern void * __tls_get_addr(TLS_index * ti);     (SPARC)
extern void * ___tls_get_addr(TLS_index * ti);    (x86)

注 –

この関数の SPARC 定義と x86 定義は、同一の関数シグニチャを持ちます。しかし x86 バージョンは、スタック上で引数を渡すデフォルトの x86 呼び出し規約を使用しません。代わりに x86 バージョンは、より効率の良い %eax レジスタを介して引数を渡します。この代替呼び出し手法を使用することを示すため、x86 関数名にはその先頭に 3 つの下線が付いています。


tls_get_addr() の両バージョンとも、スレッドごとの生成カウンタ gent を調べ、ベクトルが更新を必要としていないかを確認します。ベクトル dtvt が古い場合、ルーチンがベクトルを更新し、必要に応じ、追加エントリ用のスペースを確保するため再割り当てを行います。続いてこのルーチンは、dtvt,m に対応する TLS ブロックがすでに割り当てられているかを調べます。まだ割り当てられていない場合、このルーチンは、実行時リンカーが提供する初期化レコードリスト内の情報を使用して、このブロックの割り当てと初期化を行います。ポインタ dtvt,m は、割り当てられたブロックを指すように設定されます。ルーチンは、ブロック内の指定されたオフセットへのポインタを返します。

スレッド固有領域のアクセスモデル

各 TLS 参照は、 以下のアクセスモデルのどれかになります。ここでは、もっとも一般的なモデル (しかし最適化の程度はもっとも低い) から順に、もっとも高速なモデル (しかし制限度は高い) へと並んでいます。

General Dynamic (GD) - 動的な TLS

このモデルでは、共有オブジェクトまたは動的実行可能ファイルから、すべての TLS 変数を参照できます。このモデルでは、TLS ブロックが特定のスレッドから初めて参照される時まで、このブロックの割り当てを延期することもできます。

Local Dynamic (LD) - ローカルシンボルの動的な TLS

このモデルは、GD モデルを最適化したものです。構築される動的オブジェクト内で変数がローカルに結合されているか、あるいは保護されていることが確認された場合、コンパイラは、動的な tlsoffsetを静的に結合してこのモデルを使用するように、リンカーに指示します。これにより、GD モデルを上回る性能が得られます。dtv0,m のアドレスの確認は、関数ごとに tls_get_addr() を 1 度呼び出すだけです。リンク編集時に結合される動的な TLS オフセットは、参照ごとに dtv0,m アドレスに追加されます。

Initial Executable (IE) - オフセットが割り当てられた静的な TLS

このモデルは、初期の静的な TLS テンプレートの一部として利用できる TLS 変数だけを参照できます。このテンプレートは、プロセスの起動時に使用できるすべての TLS ブロックで構成されます。このモデルでは、変数 x の、スレッドポインタからの相対オフセットは、x の大域オフセットテーブルエントリ内に保存されます。このモデルは、当初のプロセス起動のあとで遅延読み込み、フィルタ、dlopen(3DL) などを介して読み込まれる共有ライブラリからは、TLS 変数を参照できません。このモデルは、遅延割り当てを使用する TLS ブロックにアクセスできません。

Local Executable (LE) - 静的な TLS

このモデルは、動的実行可能ファイル自体の TLS ブロックの一部である TLS 変数だけを参照できます。リンカーは、動的な再配置や大域オフセットテーブルの参照を別途行うことなく、スレッドポインタからの相対オフセットを静的に計算します。このモデルを使用して動的実行可能ファイルの外部に存在する変数を参照することはできません。

リンカーは、妥当と判断する場合は、比較的一般的なアクセスモデルから、より最適化されたモデルへとコードを移行できます。この移行は、独特な TLS 再配置を使用することで行えます。これらの再配置は、更新を要求するだけでなく、どの TLS アクセスモデルが使用されているかの特定もします。

TLS アクセスモデルと、作成されるオブジェクトのタイプを認識することで、リンカーは変換が可能となります。たとえば、GD アクセスモデルを使用している再配置可能オブジェクトが動的実行可能ファイルにリンクされている場合、リンカーは IE アクセスモデルまたは LE アクセスモデルを使用して参照を適宜移行できます。そのあとで、そのモデルに必要な再配置が行われます。

次の図は、それぞれのアクセスモデルと、どの時点でモデル間の移行が可能であるかを示しています。

図 8–2 スレッド固有領域のアクセスモデルと移行

スレッド固有領域のアクセスモデルと移行。

SPARC: スレッド固有変数へのアクセス

SPARC では、スレッド固有変数へのアクセスに以下のコードシーケンスモデルを使用できます。

SPARC: 32 ビットおよび 64 ビットの General Dynamic (GD)

このコードシーケンスはもっとも一般的であり、共有オブジェクトと動的実行可能ファイルのどちらでも使用できます。このコードシーケンスは、共有オブジェクト内および動的実行可能ファイル内の外部 TLS 変数も参照できます。

表 8–2 SPARC: 32 ビットおよび 64 ビットの General Dynamic スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

# %l7 - GOT ポインタに初期化されている

0x00 sethi %hi(@dtlndx(x)), %o0
0x04 add   %o0,%lo(@dtlndx(x)),%o0
0x08 add   %l7, %o0, %o0
0x0c call  x@TLSPLT
 
# %o0 - TLS 変数のアドレスが含まれる
 
 
R_SPARC_TLS_GD_HI22
R_SPARC_TLS_GD_LO22
R_SPARC_TLS_GD_ADD
R_SPARC_TLS_GD_CALL
 
 
x
x
x
x
 

未処理の再配置: 32 ビット 

シンボル 

GOT[n]
GOT[n + 1]
R_SPARC_TLS_DTPMOD32
R_SPARC_TLS_DTPOFF32
x
x
 

未処理の再配置: 64 ビット 

シンボル 

GOT[n]
GOT[n + 1]
R_SPARC_TLS_DTPMOD64
R_SPARC_TLS_DTPOFF64
x
x

sethi 命令は R_SPARC_TLS_GD_HI22 再配置を生成し、add 命令は R_SPARC_TLS_GD_LO10 再配置を生成します。これらの再配置は、変数 x への TLS_index 構造体を保持する領域を大域オフセットテーブル内に割り当てるように、リンカーに指示します。リンカーは、この新しい GOT エントリに GOT からの相対オフセットを代入することによって、この再配置を処理します。

読み込みオブジェクトインデックスと x の TLS ブロックインデックスは実行時まで不明なため、リンカーは、実行時リンカーによって処理されるように、GOT に対する R_SPARC_TLS_DTPMOD32 再配置と R_SPARC_TLS_DPTOFF32 再配置を設定します。

add 命令は、R_SPARC_TLS_GD_ADD 再配置を生成します。この再配置が使用されるのは、リンカーによって GD コードシーケンスがほかのシーケンスに変更される場合だけです。

call 命令は、R_SPARC_TLS_GD_CALL 再配置を生成します。この再配置は、__tls_get_addr () 関数の呼び出しを結合するようリンカーに指示し、call 命令を GD コードシーケンスに関連付けます。


注 –

add 命令は、call 命令の前に指定する必要があります。この命令を、呼び出しの遅延スロットに配置することはできません。これは、あとで発生するコード変換が既知の順序を必要とするためです。

R_SPARC_TLS_GD_ADD 再配置によってタグが付けられた add 命令の GOT ポインタとして使用されるレジスタは、add 命令内の最初のレジスタでなければなりません。このように指定することで、リンカーはコード変換時に GOT ポインタであるレジスタを識別できるようになります。


SPARC: 32 ビットおよび 64 ビットの Local Dynamic (LD)

このコードシーケンスは、共有オブジェクトまたは動的実行可能ファイルで使用できます。このシーケンスは、参照として同じオブジェクト内に結合された TLS 変数を参照する場合に使用します。動的な tlsoffset はリンク編集時に結合が可能なため、__tls_get_addr() の呼び出しは LD コードシーケンスを介して参照されるすべてのシンボルの関数呼び出しごとに 1 回だけですみます。

表 8–3 SPARC: 32 ビットおよび 64 ビットの Local Dynamic スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

# %l7 - GOT ポインタに初期化されている


0x00 sethi %hi(@tmndx(x1)), %o0
0x04 add   %o0,%lo(@tmndx(x1)),%o0
0x08 add   %l7, %o0, %o0
0x0c call  x@TLSPL
R_SPARC_TLS_LDM_HI22
R_SPARC_TLS_LDM_LO10
R_SPARC_TLS_LDM_ADD
R_SPARC_TLS_LDM_CALL
x1
x1
x1
x1
# %o0 - 現在のオブジェクトの TLS ブロックのアドレスが含まれる
 
0x10 sethi %hi(@dtpoff(x1)), %l1
0x14 xor   %l1, %lo(@dtpoff(x1)), %l1
0x18 add   %o0, %l1, %l1
R_SPARC_TLS_LDO_HIX22
R_SPARC_TLS_LDO_LOX10
R_SPARC_TLS_LDO_ADD
x1
x1
x1
# %l1 - ローカル TLS 変数 x1 のアドレスが含まれる
 
0x20 sethi %hi(@dtpoff(x2)), %l2
0x24 xor   %l2, %lo(@dtpoff(x2)), %l2
0x28 add   %o0, %l2, %l2
R_SPARC_TLS_LDO_HIX22
R_SPARC_TLS_LDO_LOX10
R_SPARC_TLS_LDO_ADD
x2
x2
x2
# %l2 - ローカル TLS 変数 x2 のアドレスが含まれる
 
 

未処理の再配置: 32 ビット 

シンボル 

GOT[n]
GOT[n + 1]
R_SPARC_TLS_DTPMOD32
<なし>
x1
 

未処理の再配置: 64 ビット 

シンボル 

GOT[n]
GOT[n + 1]
R_SPARC_TLS_DTPMOD64
<なし>
x1

最初の sethi 命令は R_SPARC_TLS_LDM_HI22 再配置を生成し、add 命令は R_SPARC_TLS_LDM_LO10 再配置を生成します。これらの再配置は、現在のオブジェクトのTLS_index 構造体を保持する領域を大域オフセットテーブル内に割り当てるように、リンカーに指示します。リンカーは、この新しい GOT エントリに GOT からの相対オフセットを代入することによって、この再配置を処理します。

読み込みオブジェクトインデックスは実行時まで認識されないため、R_SPARC_TLS_DTPMOD32 再配置が作成され、TLS_index 構造体の ti_tlsoffset フィールドにゼロが埋め込まれます。

2 つめの add 命令には R_SPARC_TLS_LDM_ADD 再配置によってタグが付けられ、call 命令には R_SPARC_TLS_LDM_CALL 再配置によってタグが付けられます。

以降の sethi 命令は R_SPARC_LDO_HIX22 再配置を生成し、or 命令は R_SPARC_TLS_LDO_LOX10 再配置を生成します。各ローカルシンボルの TLS オフセットはリンク編集時に認識されるため、これらの値は直接埋め込まれます。add 命令には、R_SPARC_TLS_LDO_ADD 再配置によってタグが付けられます。

手続きが複数のローカルシンボルを参照する場合には、コンパイラは TLS ブロックの基底アドレスを 1 度だけ取得するコードを生成します。以後、各シンボルのアドレスの計算にはこの基底アドレスが使用され、個別にライブラリを呼び出すことはありません。


注 –

R_SPARC_TLS_LDO_ADD によってタグが付けられた add 命令内の TLS オブジェクトアドレスが入ったレジスタは、命令シーケンス内の最初のレジスタでなければなりません。このように指定することで、リンカーはコード変換時にレジスタを識別できるようになります。


SPARC: 32 ビットの Initial Executable (IE)

このコードシーケンスは、動的実行可能ファイルでのみ使用できます。このモデルは、その実行可能ファイル内、またはプロセスの起動時に読み込まれる任意の共有ライブラリ内で定義された TLS 変数を参照できます。このモデルでは、プロセスの起動後に読み込まれる共有ライブラリ内の TLS 変数を参照することはできません。

表 8–4 SPARC: 32 ビットの Initial Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

# %l7 - GOT ポインタに初期化されている

0x00 sethi %hi(@tpoff(x)), %o0
0x04 or    %o0,%lo(@tpoff(x)),%o0
0x08 ld    [%l7 + %o0], %o0
0x0c add   %g7, %o0, %o0
 
# %o0 - TLS 変数のアドレスが含まれる
 
 
R_SPARC_TLS_IE_HI22
R_SPARC_TLS_IE_LO10
R_SPARC_TLS_IE_LD
R_SPARC_TLS_IE_ADD
 
 
x
x
x
x
 

未処理の再配置 

シンボル 

GOT[n]
R_SPARC_TLS_TPOFF32
x

sethi 命令は R_SPARC_TLS_IE_HI22 再配置を生成し、or 命令は R_SPARC_TLS_IE_LO10 再配置を生成します。これらの再配置は、シンボル x の静的な TLS オフセットを保存する領域を大域オフセットテーブル内に作成するように、リンカーに指示します。このとき、GOT に対する R_SPARC_TLS_TPOFF32 の再配置は、未処理の状態に置かれます。あとで、実行時リンカーがシンボル x の負の静的 TLS オフセットを埋め込みます。ld 命令には R_SPARC_TLS_IE_LD 再配置によってタグが付けられ、add 命令には R_SPARC_TLS_IE_ADD 再配置によってタグが付けられます。


注 –

R_SPARC_TLS_IE_ADD 再配置によってタグが付けられた add 命令の GOT ポインタとして使用されるレジスタは、この命令内の最初のレジスタでなければなりません。このように指定することで、リンカーはコード変換時に GOT ポインタであるレジスタを識別できるようになります。


SPARC: 64 ビットの Initial Executable (IE)

このシーケンスは、ld 命令ではなく ldx 命令を使用して 64 ビットアドレスを読み込んでいる点を除き、SPARC 32 ビットシーケンスと同じです。

表 8–5 SPARC: 64 ビットの Initial Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

# %l7 - GOT ポインタに初期化されている

0x00 sethi %hi(@tpoff(x)), %o0
0x04 or    %o0,%lo(@tpoff(x)),%o0
0x08 ldx   [%l7 + %o0], %o0
0x0c add   %g7, %o0, %o0
 
# %o0 - TLS 変数のアドレスが含まれる
 
 
R_SPARC_TLS_IE_HI22
R_SPARC_TLS_IE_LO10
R_SPARC_TLS_IE_LD
R_SPARC_TLS_IE_ADD
 
 
x
x
x
x
 

未処理の再配置 

シンボル 

GOT[n]
R_SPARC_TLS_TPOFF64
x

SPARC: 32 ビットおよび 64 ビットの Local Executable (LE)

このコードシーケンスは、動的実行可能ファイル内から、同じファイル内で定義された TLS 変数を参照するためにのみ使用できます。この場合、静的な tlsoffset はリンク編集時に認識され、実行時の再配置は不要です。

表 8–6 SPARC: 32 ビットおよび 64 ビットの Local Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 sethi %hix(@tpoff(x)), %o0
0x04 xor   %o0,%lo(@tpoff(x)),%o0
0x08 add   %g7, %o0, %o0
 
# %o0 - TLS 変数のアドレスが含まれる
R_SPARC_TLS_LE_HIX22
R_SPARC_TLS_LE_LOX10
<なし>
x
x

sethi 命令は R_SPARC_TLS_LE_HIX22 再配置を生成し、or 命令は R_SPARC_TLS_LE_LOX10 再配置を生成します。リンカーは、実行可能ファイルで定義されたシンボルの静的な TLS オフセットに、これらの再配置を直接結合します。実行時には、再配置処理は不要です。

SPARC: スレッド固有領域の再配置のタイプ

次の表に、SPARC 用に定義された TLS 再配置を示します。この表の説明は、次の表記規則を使用しています。

@dtlndx(x)

TLS_index 構造体を保持するために、大域オフセットテーブル内に連続した 2 つのエントリを割り当てます。この情報は、__tls_get_addr() に渡されます。このエントリを参照する命令は、2 つの GOT エントリのうちの最初のエントリのアドレスに結合されます。

@tmndx(x)

TLS_index 構造体を保持するために、大域オフセットテーブル内に連続した 2 つのエントリを割り当てます。この情報は、__tls_get_addr() に渡されます。この構造体の ti_tlsoffset フィールドは 0 に設定され、ti_moduleid は実行時に埋め込まれます。__tls_get_addr() 呼び出しは、動的な TLS ブロックの開始オフセットを返します。

@dtpoff(x)

TLS ブロックからの相対 tlsoffset を計算します。

@tpoff(x)

静的な TLS ブロックからの負の相対 tlsoffset を計算します。この値は、TLS アドレスを計算するためにスレッドポインタに追加されます。

@dtpmod(x)

シンボル S を含むオブジェクトの識別子を計算します。

表 8–7 SPARC: スレッド固有領域の再配置のタイプ

名前 

値 

フィールド 

計算 

R_SPARC_TLS_GD_HI22

56

T-simm22

@dtlndx(S + A)>> 10

R_SPARC_TLS_GD_LO10

57

T-simm13

@dtlndx(S + A) & 0x3ff

R_SPARC_TLS_GD_ADD

58

なし 

R_SPARC_TLS_GD_ADD を参照

R_SPARC_TLS_GD_CALL

59

V-disp30

R_SPARC_TLS_GD_CALL を参照

R_SPARC_TLS_LDM_HI22

60 

T-simm22

@tmndx(S + A)>> 10

R_SPARC_TLS_LDM_LO10

61

T-simm13

@tmndx(S + A) & 0x3ff

R_SPARC_TLS_LDM_ADD

62 

なし 

R_SPARC_TLS_LDM_ADD を参照

R_SPARC_TLS_LDM_CALL

63

V-disp30

R_SPARC_TLS_LDM_CALL を参照

R_SPARC_TLS_LDO_HIX22

64

T-simm22

@dtpoff(S + A)>> 10

R_SPARC_TLS_LDO_LOX10

65

T-simm13

@dtpoff(S + A) & 0x3ff

R_SPARC_TLS_LDO_ADD

66

なし 

R_SPARC_TLS_LDO_ADD を参照

R_SPARC_TLS_IE_HI22

67

T-simm22

@got(@tpoff(S + A))>> 10

R_SPARC_TLS_IE_LO10

68

T-simm13

@got(@tpoff(S + A)) & 0x3ff

R_SPARC_TLS_IE_LD

69

なし 

R_SPARC_TLS_IE_LD を参照

R_SPARC_TLS_IE_LDX

70

なし 

R_SPARC_TLS_IE_LDX を参照

R_SPARC_TLS_IE_ADD

71

なし 

R_SPARC_TLS_IE_ADD を参照

R_SPARC_TLS_LE_HIX22

72

T-imm22

(@tpoff(S + A) ^0xffffffffffffffff)>> 10

R_SPARC_TLS_LE_LOX10

73

T-simm13

(@tpoff(S + A) & 0x3ff) | 0x1c00

R_SPARC_TLS_DTPMOD32

74

V-word32

@dtpmod(S + A)

R_SPARC_TLS_DTPMOD64

75

V-word64

@dtpmod(S + A)

R_SPARC_TLS_DTPOFF32

76

V-word32

@dtpoff(S + A)

R_SPARC_TLS_DTPOFF64

77

V-word64

@dtpoff(S + A)

R_SPARC_TLS_TPOFF32

78

V-word32

@tpoff(S + A)

R_SPARC_TLS_TPOFF64

79

V-word64

@tpoff(S + A)

いくつかの再配置型には、単純な計算を超えた意味が存在します。

R_SPARC_TLS_GD_ADD

この再配置は、GD コードシーケンスの add 命令にタグを付けます。GOT ポインタに使用されるレジスタは、シーケンス内の最初のレジスタです。この再配置によってタグが付けられる命令は、R_SPARC_TLS_GD_CALL 再配置によってタグが付けられる call 命令の前に置かれます。これは、リンク編集時に TLS モデルを移行するために使用されます。

R_SPARC_TLS_GD_CALL

この再配置は、__tls_get_addr() 関数を参照する R_SPARC_WPLT30 再配置と同じように処理されます。この再配置は、GD コードシーケンスの一部です。

R_SPARC_LDM_ADD

この再配置は、LD コードシーケンスの最初の add 命令にタグを付けます。GOT ポインタに使用されるレジスタは、シーケンス内の最初のレジスタです。この再配置によってタグが付けられる命令は、R_SPARC_TLS_GD_CALL 再配置によってタグが付けられる call 命令の前に置かれます。これは、リンク編集時に TLS モデルを移行するために使用されます。

R_SPARC_LDM_CALL

この再配置は、__tls_get_addr() 関数を参照する R_SPARC_WPLT30 再配置と同じように処理されます。この再配置は、LD コードシーケンスの一部です。

R_SPARC_LDO_ADD

この再配置は、LD コードシーケンス内の最後の add 命令にタグを付けます。コードシーケンスの先頭で計算されるオブジェクトアドレスを含むレジスタは、この命令における最初のレジスタです。これにより、リンカーはコード変換でこのレジスタを識別できるようになります。

R_SPARC_TLS_IE_LD

この再配置は、32 ビットの IE コードシーケンス内の ld 命令にタグを付けます。これは、リンク編集時に TLS モデルを移行するために使用されます。

R_SPARC_TLS_IE_LDX

この再配置は、64 ビットの IE コードシーケンス内の ldx 命令にタグを付けます。これは、リンク編集時に TLS モデルを移行するために使用されます。

R_SPARC_TLS_IE_ADD

この再配置は、IE コードシーケンス内の add 命令にタグを付けます。GOT ポインタに使用されるレジスタは、シーケンス内の最初のレジスタです。

x86: スレッド固有変数へのアクセス

x86 では、TLS へのアクセスに以下のコードシーケンスモデルを使用できます。

x86: General Dynamic (GD)

このコードシーケンスはもっとも一般的であり、共有オブジェクトと動的実行可能ファイルのどちらでも使用できます。このコードシーケンスは、共有オブジェクト内および動的実行可能ファイル内の外部 TLS 変数を参照できます。

表 8–8 x86: General Dynamic スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 leal  x@tlsgd(,%ebx,1),%eax
0x07 call  x@tlsgdplt  

# %eax - TLS 変数のアドレスが含まれる
R_386_TLS_GD
R_386_TLS_GD_PLT
x
x
 

未処理の再配置 

シンボル 

GOT[n]
GOT[n + 1]
R_386_TLS_DTPMOD32
R_386_TLS_DTPOFF32
x

leal 命令は R_386_TLS_GD 再配置を生成します。この再配置は、変数 xTLS_index 構造体を保持する領域を大域オフセットテーブル内に割り当てるよう、リンカーに指示します。リンカーは、この新しい GOT エントリに、GOT からの相対オフセットを代入することによって、この再配置を処理します。

読み込みオブジェクトインデックスと x の TLS ブロックインデックスは実行時まで不明なため、リンカーは、実行時リンカーによって処理されるように、GOT に対して R_386_TLS_DTPMOD32 再配置と R_386_TLS_DTPOFF32 再配置を設定します。生成された GOT エントリのアドレスは、___tls_get_addr() 呼び出しのためにレジスタ %eax に読み込まれます。

call 命令は、R_386_TLS_GD_PLT 再配置を生成します。この再配置は、___tls_get_addr () 関数の呼び出しを結合するようリンカーに指示し、call 命令を GD コードシーケンスに関連付けます。

call 命令は、leal 命令の直後に配置する必要があります。これは、コード変換を可能にするために必要です。

x86: Local Dynamic (LD)

このコードシーケンスは、共有オブジェクトまたは動的実行可能ファイルで使用できます。このシーケンスは、参照として同じオブジェクト内に結合された TLS 変数を参照する場合に使用します。動的な tlsoffset はリンク編集時に結合が可能なため、___tls_get_addr() の呼び出しは、LD コードシーケンスを介して参照されるすべてのシンボルの関数呼び出しごとに 1 回だけですみます。

表 8–9 x86: Local Dynamic スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 leal  x1@tlsldm(%ebx), %eax
0x06 call  x@tlsldmplt
R_386_TLS_LDM
R_386_TLS_LDM_PLT
x1
x1
# %eax - 現在のオブジェクトの TLS ブロックのアドレスが含まれる
 
0x10 leal x1@dtpoff(%eax), %edx
R_386_TLS_LDO_32
x1
# %edx - ローカル TLS 変数 x1 のアドレスが含まれる
 
0x20 leal x2@dtpoff(%eax), %edx
R_386_TLS_LDO_32
x2
# %edx - ローカル TLS 変数 x2 のアドレスが含まれる
 
 

未処理の再配置 

シンボル 

GOT[n]
GOT[n + 1]
R_386_TLS_DTPMOD32
<なし>
x

最初の leal 命令は R_386_TLS_LDM 再配置を生成します。この再配置は、現在のオブジェクトの TLS_index 構造体を保持する領域を大域オフセットテーブル内に割り当てるよう、リンカーに指示します。リンカーは、この新しいリンクテーブルエントリに GOT からの相対オフセットを代入することによって、この再配置を処理します。

読み込みオブジェクトインデックスは実行時まで認識されないため、R_386_TLS_DTPMOD32 再配置が作成され、構造体の ti_tlsoffset フィールドにゼロが埋め込まれます。call 命令には、R_386_TLS_LDM_PLT 再配置によってタグが付けられます。

各ローカルシンボルの TLS オフセットはリンク編集時に認識されるため、リンカーはこれらの値を直接埋め込みます。

手続きが複数のローカルシンボルを参照する場合には、コンパイラは TLS ブロックの基底アドレスを取得するコードを 1 度だけ生成します。以後、各シンボルのアドレスの計算にはこの基底アドレスが使用され、個別にライブラリを呼び出すことはありません。

x86: Initial Executable (IE)

IE モデルには 2 つのコードシーケンスが存在します。その 1 つは、GOT ポインタを使用する、位置に依存しないコード用です。もう 1 つのシーケンスは、GOT ポインタを使用しない、位置に依存するコード用です。どちらのコードシーケンスも、動的実行可能ファイルでのみ使用できます。これらのコードシーケンスは、その実行可能ファイル内、またはプロセスの起動時に読み込まれる任意の共有ライブラリ内で定義された TLS 変数を参照できます。このモデルは、プロセスの起動後に読み込まれる共有ライブラリ内の TLS 変数を参照することはできません。

表 8–10 x86: 位置に依存しない Initial Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 movl  %gs:0, %eax
0x06 addl  x@gotntpoff(%ebx), %eax

# %eax - TLS 変数のアドレスが含まれる
<なし>
R_386_TLS_GOTIE
 
x
 

未処理の再配置 

シンボル 

GOT[n]
R_386_TLS_TPOFF
x

addl 命令は R_386_TLS_GOTIE 再配置を生成します。この再配置は、シンボル x の静的な TLS オフセットを保存する領域を大域オフセットテーブル内に作成するよう、リンカーに指示します。このとき、GOT テーブルに対する R_386_TLS_TPOFF 再配置は、未処理の状態に置かれます。あとで、実行時リンカーがシンボル x の静的な TLS オフセットを埋め込みます。

表 8–11 x86: 位置に依存する Initial Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 movl  %gs:0, %eax
0x06 addl  x@indntpoff, %eax

# %eax - TLS 変数のアドレスが含まれる
<なし>
R_386_TLS_IE
 
x
 

未処理の再配置 

シンボル 

GOT[n]
R_386_TLS_TPOFF
x

addl 命令は R_386_TLS_IE 再配置を生成します。この再配置は、シンボル x の静的な TLS オフセットを保存する領域を大域オフセットテーブル内に作成するよう、リンカーに指示します。このシーケンスと位置に依存しない形式との主な違いは、GOT ポインタレジスタのオフセットを介してではなく、作成される GOT エントリに直接、命令が結合されることです。このとき、GOT に対する R_386_TLS_TPOFF 再配置は、未処理の状態に置かれます。あとで、実行時リンカーがシンボル x の静的な TLS オフセットを埋め込みます。

次の 2 つのシーケンスに示すように、メモリー参照にオフセットを直接埋め込むことによって、変数 x の (アドレスではなく) 内容を読み込むことができます。

表 8–12 x86: 位置に依存しない Initial Executable 動的スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 movl  x@gotntpoff(%ebx), %eax
0x06 movl  %gs:(%eax), %eax

# %eax - TLS 変数のアドレスが含まれる
R_386_TLS_GOTIE
<なし>
x
 

未処理の再配置 

シンボル 

GOT[n]
R_386_TLS_TPOFF
x

表 8–13 x86: 位置に依存しない Initial Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 movl  x@indntpoff, %ecx
0x06 movl  %gs:(%ecx), %eax

# %eax - TLS 変数のアドレスが含まれる
R_386_TLS_IE
<なし>
x
 

未処理の再配置 

シンボル 

GOT[n]
R_386_TLS_TPOFF
x

最後のシーケンスで、上記の %ecx ではなく %eax レジスタを使用すると、最初の命令は 5 バイト長または 6 バイト長になる可能性があります。

x86: Local Executable (LE)

このコードシーケンスは、動的実行可能ファイル内から、同じファイル内で定義された TLS 変数を参照するためにのみ使用できます。この場合、静的な tlsoffset はリンク編集時に認識され、実行時の再配置は不要です。

表 8–14 x86: Local Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 movl %gs:0, %eax
0x06 leal x@ntpoff(%eax), %eax

# %eax - TLS 変数のアドレスが含まれる
<なし>
R_386_TLS_LE
 
x

movl 命令は、R_386_TLS_LE_32 再配置を生成します。リンカーは、実行可能ファイルで定義されたシンボルの静的な TLS オフセットに、この再配置を直接結合します。実行時には処理は不要です。

次の命令シーケンスを使用すると、同じ再配置により変数 x の (アドレスではなく) 内容にアクセスできます。

表 8–15 x86: Local Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 movl %gs:0, %eax
0x06 movl x@ntpoff(%eax), %eax

# %eax - TLS 変数のアドレスが含まれる
<なし>
R_386_TLS_LE
 
x

変数のアドレスを計算するのではなく、その変数から読み込むかあるいはその変数に保存したいという場合は、次のシーケンスを使用できます。この例では、x@ntpoff による式を即値としてではなく絶対アドレスとして使用しています。

表 8–16 x86: Local Executable スレッド固有変数のアクセスコード

コードシーケンス 

初期の再配置 

シンボル 

0x00 movl %gs:x@ntpoff, %eax

# %eax - TLS 変数のアドレスが含まれる
R_386_TLS_LE
x

x86: スレッド固有領域の再配置のタイプ

次の表に、x86 用に定義された TLS 再配置を示します。この表の説明は、次の表記規則を使用しています。

@tlsgd(x)

TLS_index 構造体を保持するために、GOT 内に連続した 2 つのエントリを割り当てます。この構造体は、___tls_get_addr() に渡されます。このエントリを参照する命令は、2 つの GOT エントリのうちの最初のエントリに結合されます。

@tlsgdplt(x)

この再配置は、___tls_get_addr() 関数を参照する R_386_PLT32 再配置と同じように処理されます。

@tlsldm(x)

TLS_index 構造体を保持するために、GOT 内に連続した 2 つのエントリを割り当てます。この構造体は、___tls_get_addr() に渡されます。TLS_indexti_tlsoffset フィールドは 0 に設定され、ti_moduleid は実行時に埋められます。___tls_get_addr() 呼び出しは、動的な TLS ブロックの開始オフセットを返します。

@gotntpoff(x)

GOT 内に 1 つのエントリを割り当ててから、静的な TLS ブロックからの負の相対 tlsoffset を使用してこれを初期化します。これは、R_386_TLS_TPOFF 再配置を介して実行時に行われます。

@indntpoff(x)

この式は @gotntpoff に似ていますが、位置に依存するコードで使用されます。@gotntpoff は、movl 命令内または addl 命令内の GOT の先頭からの相対 GOT スロットアドレスに解決されます。@indntpoff は、絶対 GOT スロットアドレスに解決されます。

@ntpoff(x)

静的な TLS ブロックからの負の相対 tlsoffset を計算します。

@dtpoff(x)

TLS ブロックからの相対 tlsoffset を計算します。この値は加数の即値として使用され、特定のレジスタには関連付けられません。

@dtpmod(x)

シンボル S が含まれるオブジェクトの識別子を計算します。

表 8–17 x86: スレッド固有領域の再配置のタイプ

名前 

値 

フィールド 

計算 

R_386_TLS_GD_PLT

12

Word32

@tlsgdplt

R_386_TLS_LDM_PLT

13

Word32

@tlsldmplt

R_386_TLS_TPOFF

14

Word32

@ntpoff(S)

R_386_TLS_IE

15

Word32

@indntpoff(S)

R_386_TLS_GOTIE

16

Word32

@gotntpoff(S)

R_386_TLS_LE

17

Word32

@ntpoff(S)

R_386_TLS_GD

18

Word32

@tlsgd(S)

R_386_TLS_LDM

19

Word32

@tlsldm(S)

R_386_TLS_LDO_32

32

Word32

@dtpoff(S)

R_386_TLS_DTPMOD32

35

Word32

@dtpmod(S)

R_386_TLS_DTPOFF32

36

Word32

@dtpoff(S)