実行時リンカーは、オブジェクトを検出して読み込んだあと、各オブジェクトを処理し、必要な再配置を実行します。また、dlopen(3C) を使用してプロセスのアドレススペースに配置されたオブジェクトは同じ方法で再配置する必要があります。
単純なアプリケーションの場合には、このプロセスはそれほど重要な意味を持ちません。しかし、多くのオブジェクトを含む多数の dlopen(3C) 呼び出しと、共通の依存関係も伴う複雑なアプリケーションを所有するユーザーにとって、このプロセスは非常に重要です。
再配置は、その実行タイミングに基づいて分類できます。実行時リンカーのデフォルトの動作では、初期設定時に即時参照の再配置がすべて処理され、プロセスの実行時に遅延参照がすべて処理されます。後者の処理は通常、遅延結合と呼ばれます。
モードが RTLD_LAZY として定義されているときに dlopen(3C) を使って追加されたすべてのオブジェクトに対して、これと同じメカニズムが適用されます。代替手段は、オブジェクトが追加されるときにただちに実行されるオブジェクトのすべての再配置を要求することです。RTLD_NOW のモードを使用するか、またはリンカーの –z now オプションを使用して構築した際のオブジェクトにこの要件を記録できます。この再配置の必要条件は、オープン状態のオブジェクトの依存関係に伝達されます。
また、再配置は、非シンボリックおよびシンボリックにも分類できます。このセクションの後半では、シンボル再配置がいつ発生するかに関係なく、この再配置に関連した問題について、シンボル検索の詳細に焦点をあてて説明します。
dlopen(3C) によって取得したオブジェクトが大域シンボルを参照する場合は、実行時リンカーは、プロセスを作成するオブジェクトのプールからこのシンボルを配置する必要があります。直接結合がない場合は、dlopen() によって入手されたオブジェクトには、デフォルトのシンボル検索モデルが適用されます。ただし、プロセスを作成するオブジェクトの属性と結合される dlopen() のモードは、代わりのシンボル検索モデルに提供されます。
直接結合を指定されたオブジェクトでは、それに対応する依存関係から直接、シンボルが検索されます。ただし、この後で述べるすべての属性はそのまま有効です。Chapter 6, 直接結合を参照してください。
dlopen(3C) を使用して入手したオブジェクトには、デフォルトでワールドシンボル検索範囲と局所シンボル可視性が割り当てられます。デフォルトのシンボル検索モデルセクションでは、このデフォルトモデルを使用して、典型的なオブジェクトグループのインタラクションについて説明しています。大域オブジェクトの定義、グループの分離、および オブジェクト階層セクションでは、デフォルトのシンボル検索モデルの展開に dlopen(3C) モードとファイル属性を使用する例を示しています。
基本的な dlopen(3C) によって追加された各オブジェクトでは、実行時リンカーは、最初に動的実行可能ファイル内でシンボルを検索します。次に実行時リンカーは、プロセスの初期設定中に提供されたそれぞれのオブジェクト内を検索します。シンボルが見つからない場合、実行時リンカーは検索を続けます。実行時リンカーは次に dlopen(3C) によって取得されたオブジェクトおよびそのオブジェクトと依存関係にあるオブジェクトを検索します。
デフォルトのシンボル検索モデルは、遅延読み込み環境の遷移も行います。現在読み込まれているオブジェクト内でシンボルが見つからない場合は、そのシンボルを特定するために、保留となっている遅延読み込みオブジェクトが処理されます。この読み込みによって、依存関係を完全には定義していないオブジェクトを補います。ただし、これにより遅延読み込みの利点が失われることがあります。
次の例の動的実行可能プログラム prog と共有オブジェクト B.so.1 には、依存関係が付いています。
$ ldd prog A.so.1 => ./A.so.1 $ ldd B.so.1 C.so.1 => ./C.so.1
prog が、dlopen(3C) を使用して共有オブジェクト B.so.1 を入手した場合、共有オブジェクト B.so.1 と C.so.1 の再配置に必要なシンボルが、最初に prog 内で検索され、A.so.1、B.so.1、C.so.1 の順に検索されます。このような単純なケースでは、dlopen(3C) によって入手された共有オブジェクトは、アプリケーションの元のリンク編集の末尾に追加されたと考えます。たとえば、上記のオブジェクトの参照を図示すると、次のようになります。
図 3-1 単一の dlopen() 要求
dlopen(3C) から入手されたオブジェクトによって要求されたシンボル検索は、影付きのブロックで示しています。このシンボル検索は、動的実行可能プログラム prog から、最後の共有オブジェクト C.so.1 へと進みます。
このシンボル検索は、読み込まれたオブジェクトに割り当てられた属性によって確立されます。動的実行可能ファイルとそれと同時に読み込まれたすべての依存関係には、大域シンボル可視性が割り当てられ、新しいオブジェクトにはワールドシンボルの検索範囲が割り当てられることを思い出してください。これによって、新しいオブジェクトは元のオブジェクト内を調べてシンボルを検索できます。また、新しいオブジェクトは、固有のグループを形成し、このグループ内では、各オブジェクトは局所シンボル可視性を持ちます。そのため、グループ内の各オブジェクトは、ほかのグループメンバー内でシンボルを検索できます。
これらの新しいオブジェクトは、アプリケーションまたはアプリケーションの最初の依存関係によって要求される、通常のシンボル検索には影響を与えません。たとえば、上記の dlopen(3C) が実行されたあとで、A.so.1 に関数再配置が必要な場合、実行時リンカーの再配置シンボルの通常の検索は、順に prog と A.so.1 で実施されます。B.so.1 または C.so.1 は検索されません。
このシンボル検索は、読み込まれたときにオブジェクトに割り当てられた属性によって実行されます。ワールドシンボルの検索範囲が、動的実行可能プログラムとこれとともに読み込まれた依存関係に割り当てられます。この検索範囲では、局所シンボル可視性だけを提供する新しいオブジェクト内を検索できません。
これらのシンボル検索とシンボル可視性の属性は、オブジェクト間の関係を保持します。これらの関係は、そのプロセスのアドレススペースへの投入とオブジェクト間の依存の関係に基づいています。指定された dlopen(3C) に関連したオブジェクトを固有のグループに割り当てることにより、同じ dlopen(3C) と関連したオブジェクトだけが、グループ内のシンボルと、関連する依存関係の中の検索ができます。
このオブジェクト間の関係を定義するという概念は、複数の dlopen(3C) を実行するアプリケーション内では、より明確になります。たとえば、共有オブジェクト D.so.1 に次の依存関係があるとします。
$ ldd D.so.1 E.so.1 => ./E.so.1
このとき、prog アプリケーションが、共有オブジェクト B.so.1 に加えてこの共有オブジェクトも dlopen(3C) を使ってロードしたとします。次の図は、オブジェクト間のシンボル検索の関係を示しています。
図 3-2 複数の dlopen() リクエスト
B.so.1 と D.so.1 の両方にシンボル foo の定義が組み込まれ、C.so.1 と E.so.1 にこのシンボルを必要とする再配置が組み込まれているとします。固有のグループに対するオブジェクトの関係によって、C.so.1 は B.so.1 の定義に結合され、E.so.1 は D.so.1 の定義に結合されます。このメカニズムは、dlopen(3C) への複数の呼び出しにより入手されたオブジェクトのもっとも直感的な結合を提供するためのものです。
オブジェクトが、前述した処理の進行の中で使用される場合、それぞれの dlopen(3C) が実施された順序は、結果として発生するシンボル結合には影響しません。ただし、複数のオブジェクトに共通の依存関係がある場合は、結果の結び付きは、dlopen(3C) 呼び出しが実行された順序の影響を受けます。
次に、同じ共通依存関係を持つ共有オブジェクト O.so.1 と P.so.1 の例を示します。
$ ldd O.so.1 Z.so.1 => ./Z.so.1 $ ldd P.so.1 Z.so.1 => ./Z.so.1
この例では、prog アプリケーションは、各共有オブジェクトに dlopen(3C) を使用しています。共有オブジェクト Z.so.1 が、O.so.1 と P.so.1 両方の共通依存関係であるため、Z.so.1 は 2 つの dlopen(3C) 呼び出しに関連する両方のグループに割り当てられます。この依存関係を次の図に示します。
図 3-3 共通依存関係を伴う複数の dlopen() 要求
この結果、O.so.1 と P.so.1 の両方がシンボルの検索に Z.so.1 を使用できます。ここで重要なのは、dlopen(3C)の順序に限って言えば、Z.so.1 も O.so.1 と P.so.1 の両方の中でシンボルを検索できることです。
そのため、O.so.1 と P.so.1 の両方に、Z.so.1 の再配置に必要なシンボル foo の定義が組み込まれている場合、実際に発生する結び付きを予期することはできません。それは、この結び付きが dlopen(3C) 呼び出しの順序の影響を受けるからです。シンボル foo の機能が、シンボルが定義されている 2 つの共有オブジェクト間で異なる場合、Z.so.1 でコードを実行したすべての結果は、アプリケーションの dlopen(3C) の順序によって異なる可能性があります。
dlopen(3C) で取得されるオブジェクトにデフォルトで割り当てられる局所シンボル可視性を大域に拡張するには、モード引数に RTLD_GLOBAL フラグを指定します。このモードでは、dlopen(3C) によって入手されたオブジェクトは、シンボルを配置するための、ワールドシンボル検索範囲が指定されたほかのオブジェクトによって使用されることができます。
また、RTLD_GLOBAL フラグが指定された dlopen(3C) によって入手されたオブジェクトは、dlopen() (値 0 のパス名を指定) を使用したシンボル検索にも使用できます。
dlopen(3C) で取得されるオブジェクトにデフォルトで割り当てられるワールドシンボル検索範囲をグループに縮小するには、モード引数に RTLD_GROUP フラグを指定します。このモードでは、dlopen(3C) によって入手されたオブジェクトは、そのオブジェクト固有のグループ内でしかシンボルの検索ができません。
リンカーの –B group オプションを使用して構築したオブジェクトには、グループのシンボル検索範囲を割り当てることができます。
最初のオブジェクトが dlopen(3C) によって入手され、dlopen() を使用してセカンダリオブジェクトを開いた場合、両方のオブジェクトは同じ固有のグループに割り当てられます。これにより、オブジェクトが互いにシンボルを配置し合うことを防ぐことができます。
実装の中には、最初のオブジェクトの場合、シンボルをセカンダリオブジェクトの再配置用にエクスポートする必要がある場合もあります。この必要条件は、次の 2 つのメカニズムのいずれかによって満たすことができます。
最初のオブジェクトを 2 番目のオブジェクトの明示的な依存関係にする。
セカンダリオブジェクトに対する dlopen(3C) で RTLD_PARENT モードフラグを使用します。
最初のオブジェクトをセカンダリオブジェクトの明示的な依存関係にした場合、これはセカンダリオブジェクトのグループにも割り当てられます。そのため、最初のオブジェクトは、セカンダリオブジェクトの再配置に必要なシンボルも提供できます。
多くのオブジェクトが dlopen(3C) を使って 2 番目のオブジェクトを開くことができ、かつそれらの初期オブジェクトがセカンダリオブジェクトの再配置を満たすために同じシンボルをエクスポートしなければならない場合、そのセカンダリオブジェクトに明示的な依存関係を割り当てることはできません。この場合、セカンダリオブジェクトの dlopen(3C) モードは、RTLD_PARENT フラグを使用して補強できます。このフラグによって、セカンダリオブジェクトのグループが、明示的な依存関係が伝達されたのと同じ方法で、最初のオブジェクトに伝達されます。
これら 2 つの手法の間には、小さな相違点が 1 つ存在します。明示的な依存関係を指定する場合、その依存関係そのものは、セカンダリオブジェクトの dlopen(3C) 依存関係ツリーの一部になるため、dlsym(3C) を使用したシンボル検索が可能になります。RTLD_PARENT を使用してセカンダリオブジェクトを入手する場合、最初のオブジェクトは、dlsym(3C) を使用したシンボルの検索に使用できるようにはなりません。
大域シンボル可視性を持つ初期オブジェクトが dlopen(3C) を使ってセカンダリオブジェクトを取得する場合、RTLD_PARENT モードは冗長かつ無害になります。このような状態は、dlopen(3C) がアプリケーションから呼び出されたとき、またはアプリケーションの中の依存関係の 1 つから呼び出されたときに多く発生します。