リンカーとライブラリ

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

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

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

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

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

プログラムの起動

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

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

tlssizem+1 は動的オブジェクト m の割り当てテンプレートのサイズで、 alignm+1 は整列です。ここで、1 <= m <= M であり、M は読み込まれる動的オブジェクトの合計数です。round(offset, align) 関数は、align の次の倍数に丸められたオフセットを返します。

次に、実行時リンカーは、起動時の TLS に必要な割り当てサイズの tlssizeS を計算します。このサイズは、tlsoffset M512 バイトを加えた値に等しくなります。この加算により、静的な TLS 参照のバックアップ予約が得られます。静的な TLS 参照を作成し、プロセスの初期化後に読み込まれる共有オブジェクトは、このバックアップ予約に割り当てられます。ただし、この予約のサイズは固定および限定されています。また、この予約が対応しているのは、初期化されていない TLS データ項目用の記憶域の提供だけです。柔軟性を最大限高めるため、動的な TLS モデルを使用して、共有オブジェクトがスレッドローカル変数を参照するようにしてください。

計算された TLS サイズ tlssizeS に関連付けられる静的な TLS 領域は、スレッドポインタ tpt の直前に配置されます。TLS データに対するアクセスは、tpt からの減算にもとづいて行われます。

静的な TLS 領域は、初期化レコードのリンクリストに関連付けられます。このリスト内の各レコードは、読み込まれた動的オブジェクトごとにその TLS 初期化イメージを記述するものです。各レコードには、次のフィールドが含まれています。

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

スレッドの作成

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

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

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

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

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


注 –

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


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

起動後の動的読み込み

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

動的な TLS だけが含まれる共有オブジェクトの読み込みが解除されると、その共有オブジェクトが使用している TLS ブロックは解放されます。

静的な TLS が含まれる共有オブジェクトは、プロセスの起動に続いて一定の制限内で読み込むことができます。静的な TLS 参照を満たすことができるのは、残りのバックアップ TLS 予約からだけです。「プログラムの起動」を参照してください。この予約のサイズは制限されています。また、この予約で提供できるのは、初期化されていない 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 and x64)
extern void * ___tls_get_addr(TLS_index * ti);    (32–bit x86)

注 –

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


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