TLS は、プログラムの存続中に、次の 3 つの機会に作成されます。
プログラムの起動時
新しいスレッドの作成時
プログラムの起動に続いて共有ライブラリが読み込まれたあと、スレッドが初めて TLS ブロックを参照する時
スレッド固有データ領域は、図 8–1 に示すように実行時に配置されます。
プログラムの起動時に、実行システムはメインスレッド用の TLS を作成します。
まず実行時リンカーが、読み込まれたすべての動的オブジェクト (動的な実行可能ファイルを含む) の TLS テンプレートを論理的に結合し、単一の静的なテンプレートとしてまとめます。各動的オブジェクトの TLS テンプレートには、結合されたテンプレート内のオフセット tlsoffsetm が次のように割り当てられます。
tlsoffset1 = round(tlssize1, align1)
tlsoffsetm+1 = round(tlsoffsetm + tlssizem+1, alignm+1)
tlssizem+1 は動的オブジェクト m の割り当てテンプレートのサイズで、alignm+1 は整列です (1 <= m <= M。M は読み込まれる動的オブジェクトの合計数)。 round(offset, align) 関数は、align の次の倍数に丸められたオフセットを返します。TLS テンプレートは、スレッドポインタ tpt の直前に配置されます。TLS データに対するアクセスは、tpt からの減算にもとづいて行われます。
次に、実行時リンカーは起動時の TLS 割り当てサイズの合計 tlssizeS を計算します。これは、tlsoffsetM に等しくなります。
続いて実行時リンカーは、初期化レコードのリンクリストを作成します。このリスト内の各レコードは、読み込まれた動的オブジェクトごとにその TLS 初期化イメージを記述するものであり、以下のフィールドを含みます。
TLS 初期化イメージを指すポインタ
TLS 初期化イメージのサイズ
オブジェクトの 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 は、割り当てられたブロックを指すように設定されます。ルーチンは、ブロック内の指定されたオフセットへのポインタを返します。