開発者は、関数の複数のインスタンス (それぞれは特定の機能セット向けに最適化) を 1 つのオブジェクト内に用意したいと考えることがよくあります。インスタンスの選択と使用が消費者に対して透過的であることが望まれます。外部インタフェースとして、汎用的なフロントエンドの関数を作成できます。この汎用インスタンスと最適化されたインスタンスを 1 つのオブジェクトに結合できます。タスクを処理するために汎用インスタンスはgetisax(2)を使用してシステムの機能を判断し、最適化された適切な関数インスタンスを呼び出すことができます。このモデルは適切に機能しますが、汎用性に欠け、実行時のオーバーヘッドが発生します。
シンボル機能はこのようなオブジェクトを作成するために別の方法を提供します。この方法はより簡単で効率が良く、さらにフロントエンドのコードを書く必要がありません。1 つの関数の複数のインスタンスを作成できます。また、さまざまな機能に関連付けることができます。これらのインスタンスと、いずれのシステムにも適した関数のデフォルトのインスタンスは、1 つの動的オブジェクトに結合できます。このファミリのシンボルの中でもっとも適したメンバーが、シンボルの機能情報を使用して、実行時リンカーによって選択されます。
次の例では、x86 オブジェクト foobar.mmx.o および foobar.sse.o が同じ関数 foo() と bar() を保持しており、それぞれ MMX と SSE の命令を使用するようにコンパイルされています。
$ elfdump -H foobar.mmx.o Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID mmx [2] CA_SUNW_HW_1 0x40 [ MMX ] Symbols: index value size type bind oth ver shndx name [10] 0 0x21 FUNC LOCL D 0 .text foo%mmx [16] 0x24 0x1e FUNC LOCL D 0 .text bar%mmx $ elfdump -H foobar.sse.o Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID sse [2] CA_SUNW_HW_1 0x800 [ SSE ] Capabilities symbols: index value size type bind oth ver shndx name [16] 0 0x2f FUNC LOCL D 0 .text foo%sse [18] 0x48 0x30 FUNC LOCL D 0 .text bar%sse
これらの各オブジェクトは機能関数 foo%*() および bar%*() を指定する局所シンボルを含んでいます。また、各オブジェクトは関数 foo() および bar() への大域参照も定義します。foo() または bar() への内部参照はすべて、これらの大域参照を介して、外部参照のように再配置されます。
これら 2 つのオブジェクトは、foo() および bar() のデフォルトのインスタンスと結合できるようになりました。これらのデフォルトインスタンスは大域参照を満たし、どのオブジェクト機能とも互換性を持つ実装を提供します。これらのデフォルトインスタンスは各機能ファミリの先頭になります。オブジェクトの機能が存在しない場合、このデフォルトインスタンスも機能を要求してはいけません。実質的に、foo() と bar() の 3 つのインスタンスが存在し、大域インスタンスはデフォルトを提供し、局所インスタンスは関連機能が使用できる場合に、実行時に使用される実装を提供します。
$ cc -o libfoobar.so.1 -G foobar.o foobar.sse.o foobar.mmx.o $ elfdump -sN.dynsym libfoobar.so.1 | egrep "foo|bar" [2] 0x700 0x21 FUNC LOCL D 0 .text foo%mmx [4] 0x750 0x2f FUNC LOCL D 0 .text foo%sse [8] 0x784 0x1e FUNC LOCL D 0 .text bar%mmx [9] 0x7b0 0x30 FUNC LOCL D 0 .text bar%sse [15] 0x7a0 0x14 FUNC GLOB D 1 .text foo [17] 0x7c0 0x14 FUNC GLOB D 1 .text bar
動的オブジェクトの機能情報には機能シンボルが表示され、利用できる機能ファミリがわかります。
$ elfdump -H libfoobar.so.1 Capabilities Section: .SUNW_cap Symbol Capabilities: index tag value [1] CA_SUNW_ID mmx [2] CA_SUNW_HW_1 0x40 [ MMX ] Symbols: index value size type bind oth ver shndx name [2] 0x700 0x21 FUNC LOCL D 0 .text foo%mmx [8] 0x784 0x1e FUNC LOCL D 0 .text bar%mmx Symbol Capabilities: index tag value [4] CA_SUNW_ID sse [5] CA_SUNW_HW_1 0x800 [ SSE ] Symbols: index value size type bind oth ver shndx name [4] 0x750 0x2f FUNC LOCL D 0 .text foo%sse [9] 0x7b0 0x30 FUNC LOCL D 0 .text bar%sse Capabilities Chain Section: .SUNW_capchain Capabilities family: foo chainndx symndx name 1 [15] foo 2 [2] foo%mmx 3 [4] foo%sse Capabilities family: bar chainndx symndx name 5 [17] bar 6 [8] bar%mmx 7 [9] bar%sse
実行時、foo() と bar() へのすべての参照は、まず大域シンボルに結合されます。しかし、実行時リンカーはこれらの関数が機能ファミリの先頭のインスタンスであることを認識しています。実行時リンカーは各ファミリメンバーを検査して、より適した機能関数が使用できるかどうかを判断します。この処理には 1 回限りのコストがかかり、関数の最初の呼び出し時に発生します。foo() および bar() への後続の呼び出しは、最初の呼び出しで選択された関数のインスタンスに直接結合されます。この関数の選択は実行時リンカーのデバッグ機能を使用すると確認できます。
次の例では、ベースとなるシステムが MMX または SSE をサポートしていません。foo() の先頭のインスタンスには特別な機能のサポートは必要ないため、どの再配置参照も満たします。
$ LD_DEBUG=symbols main .... debug: symbol=foo; lookup in file=./libfoo.so.1 [ ELF ] debug: symbol=foo[15]: capability family default debug: symbol=foo%mmx[2]: capability specific (CA_SUNW_HW_1): [ 0x40 [ MMX ] ] debug: symbol=foo%mmx[2]: capability rejected debug: symbol=foo%sse[4]: capability specific (CA_SUNW_HW_1): [ 0x800 [ SSE ] ] debug: symbol=foo%sse[4]: capability rejected debug: symbol=foo[15]: used
次の例では、MMX は使用できますが、SSE は使用できません。MMX が使用できる foo() のインスタンスは、どの再配置参照も解決します。
$ LD_DEBUG=symbols main .... debug: symbol=foo; lookup in file=./libfoo.so.1 [ ELF ] debug: symbol=foo[15]: capability family default debug: symbol=foo%mmx[2]: capability specific (CA_SUNW_HW_1): [ 0x40 [ MMX ] ] debug: symbol=foo%mmx[2]: capability candidate debug: symbol=foo%sse[4]: capability specific (CA_SUNW_HW_1): [ 0x800 [ SSE ] ] debug: symbol=foo%sse[4]: capability rejected debug: symbol=foo[2]: used
機能関数インスタンスのファミリはプロシージャーのリンクテーブルエントリからアクセスできる必要があります。プロシージャーのリンクテーブル (プロセッサ固有)を参照してください。このプロシージャーリンクの参照には、実行時リンカーが関数を解決する必要があります。このプロセス中、実行時リンカーは関連のシンボル機能情報を処理して、使用できる関数インスタンスファミリから最適な関数を選択できます。
シンボル機能が使用されないときに、リンカーがプロシージャーのリンクテーブルエントリを必要としないでコードへの参照を解決できる場合があります。たとえば、動的実行可能ファイル内では、実行可能ファイル内に存在する関数への参照が、リンク編集時に内部的に結合できます。共有オブジェクト内に隠されて保護されている関数も、リンク編集時に内部的に結合できます。この場合、一般的に、実行時リンカーはこれらの関数への参照の解決に関与する必要はありません。
しかし、シンボル機能が使用された場合、関数はプロシージャーのリンクテーブルエントリから解決される必要があります。このエントリが必要なのは、実行時リンカーが読み取り専用のテキストセグメントを維持しながら、適切な関数を選択することに関与するためです。このメカニズムでは、機能関数へのすべての呼び出しが、プロシージャーのリンクテーブルエントリを介した間接参照になります。この間接参照は、シンボル機能が使用されない場合は必要ない可能性があります。このため、機能関数を呼び出すコストと、機能関数を使用して得られるデフォルトに対する性能の改善との間に小さいトレードオフがあります。