リンカーとライブラリ

初期設定と終了の順序

実行時にプロセス内で初期設定および終了コードをどのような順序で実行すべきかを判断することは、依存関係の分析を伴う複雑な問題を含んでいます。この処理は、初期設定セクションと終了セクションの導入以来、大きく発展してきました。この処理は、現代的な言語と現在のプログラミング手法の期待を実現しようとするものです。しかし、ユーザーの期待にこたえるのが難しい状況もあります。これらの状況を理解し、初期設定および終了コードの内容を制限することで、柔軟で予測可能な実行時動作が得られます。

Solaris 2.6 より前のリリースでは、依存関係の初期設定ルーチンが呼び出される順序は、読み込まれた順序の逆、つまり、ldd(1) を使用して表示される依存関係の順序とは逆でした。同様に、依存関係の終了ルーチンが呼び出される順序は、読み込まれた順序と同じでした。しかし、依存関係の階層が複雑化するにつれ、この単純な順序付け手法は適切とは言えなくなりました。

Solaris 2.6 リリースからは、実行時リンカーは、読み込まれたオブジェクトを位相的にソートしてリストを作成するようになりました。このリストは、各オブジェクトが表す依存関係の相関関係に加えて、示された依存関係の外部で発生したシンボル結合から構成されます。

初期設定セクションは、依存関係の位相的な順序とは逆に実行されます。循環性のある依存関係が検出された場合、循環の原因であるオブジェクトは、位相的にソートされません。循環性のある依存関係の初期設定セクションは、読み込まれた順序の逆に実行されます。同様に、終了ルーチンは、依存関係の位相的な順序で呼び出され、循環性のある依存関係は読み込まれた順序で実行されます。

-i オプションを指定した ldd(1) を使用すると、オブジェクトの依存関係の初期設定の順番を表示できます。たとえば、次の動的実行プログラムとその依存関係は、循環性のある依存関係を示しています。


$ dump -Lv B.so.1 | grep NEEDED
[1]     NEEDED      C.so.1
$ dump -Lv C.so.1 | grep NEEDED
[1]     NEEDED      B.so.1
$ dump -Lv main | grep NEEDED
[1]     NEEDED      A.so.1
[2]     NEEDED      B.so.1
[3]     NEEDED      libc.so.1
$ ldd -i main
        A.so.1 =>        ./A.so.1
        B.so.1 =>        ./B.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        C.so.1 =>        ./C.so.1
        libdl.so.1 =>    /usr/lib/libdl.so.1

   cyclic dependencies detected, group[1]:
        ./libC.so.1
        ./libB.so.1

   init object=/usr/lib/libc.so.1
   init object=./A.so.1
   init object=./C.so.1 - cyclic group [1], referenced by:
        ./B.so.1
   init object=./B.so.1 - cyclic group [1], referenced by:
        ./C.so.1

注意 – 注意 –

Solaris 8 10/00 より前のリリースでは、環境変数 LD_BREADTH にヌル以外の値を設定すると、リンカーで初期設定セクションと終了セクションを Solaris 2.6 より前の順序で実行することができました。多数のアプリケーションを初期化すると、依存関係が複雑になり、位相的な並び替えが必要になるため、この機能は Solaris 8 10/00 から無効にされています。LD_BREADTH の設定は無視され、メッセージは表示されません。


初期設定処理は、dlopen(3DL) が指定された実行中のプロセスに追加されたオブジェクトごとに繰り返されます。また、dlclose(3DL) に対する呼び出しの結果としてプロセスから読み込み解除されるすべてのオブジェクトに対して、終了処理も行われます。

依存関係を正確に示さない共有オブジェクトが多数存在するため、シンボルの結合は依存関係分析の一環として組み込まれます。シンボル結合を組み込むことでより正確な依存関係を生成できます。しかし、依存関係をすべて示していないオブジェクトにシンボル結合情報を加えても、オブジェクト全体の依存関係を決定するには不十分な場合があります。オブジェクトの読み込みに使用されるもっとも一般的なモデルは遅延結合です。このモデルの場合、初期設定処理の前に処理されるのは即時参照シンボル結合だけです。遅延参照からのシンボル結合が保留されていて、それまでに確立された依存関係をあとで拡張する可能性もあります。

オブジェクトの依存関係分析は不完全な場合があり、また循環性のある依存関係もしばしば存在するため、実行時リンカーは動的な初期設定も提供しています。この初期設定は、初期設定セクションを、同じオブジェクト内の関数が呼び出される前に実行しようとします。実行時リンカーは、遅延シンボル結合の際に、結合する先のオブジェクトの初期設定セクションがすでに呼び出されているかどうかを判定します。呼び出されていなければ、シンボル結合手順から戻る前にそれを呼び出します。

動的な初期設定は、ldd(1) では確認できません。 しかし、LD_DEBUG 環境変数を設定してトークン basicを含めることにより、実行時に初期設定呼び出しの正確な手順を確認できます。デバッギングライブラリを参照してください。

動的な初期設定を利用できるのは、遅延参照を処理する場合だけです。環境変数 LD_BIND_NOW の使用、-z now オプションで構築されたオブジェクト、または RTLD_NOW モードを使用して dlopen(3DL) によって参照されたオブジェクトでは、あらゆる動的初期設定が迂回されます。


注 –

初期化が保留されて、dlopen(3DL) を使用して参照されたオブジェクトは、この関数から制御を返す前に初期化されます。


これまでは、ユーザーの期待に応えようとする方法で初期設定セクションと終了セクションを実行するさまざまな手法を説明してきました。しかし、依存関係同士の初期設定と終了の関係を単純化するためには、コーディングスタイルとリンク編集の助けも必要です。この単純化によって、初期設定と終了の処理を予測可能にし、不測の依存関係順序付けによる副次的作用を防止しやすくします。

初期設定セクションと終了セクションの内容は最小限に抑えてください。実行時にオブジェクトを初期化することによって、大域的なコンストラクタを避けてください。ほかの依存関係に対する初期設定および終了コードの依存を減らしてください。すべての動的オブジェクトについて依存関係の要件を明示的に定義してください。共有オブジェクト出力ファイルの生成を参照してください。不要な依存関係を定義しないでください。共有オブジェクトの処理を参照してください。依存関係の循環を避けてください。初期設定または終了の順序に頼らないでください。オブジェクトの順序は、共有オブジェクトとアプリケーションの開発によって変更される場合があるからです。依存関係の順序 を参照してください。