第 39 章安定性では、DTrace を使ってプログラムの安定性属性を計算し、報告する方法について学びました。本来なら、「安定」または「発展中」のインタフェースだけを使用して DTrace プログラムを作成するのが理想です。しかし、残念ながら、下位レベルの問題のデバッグ時やシステムパフォーマンスの測定時には、システムコールのような安定性の高いインタフェースに関連付けられているプローブではなく、カーネル内の関数のような内部オペレーティングシステムルーチンに関連付けられているプローブを有効にしなければならない場合があります。ソフトウェアスタック内の深いところにあるプローブのデータは、通常、実装アーティファクトの集合であって、Solaris システムコールインタフェースのデータのような、安定したデータ構造は備えていません。安定した D プログラムの作成を支援するため、DTrace では、実装アーティファクトを D プログラム文からアクセスできる安定したデータ構造に翻訳する機能を提供します。
「トランスレータ」は、インタフェースの提供元から提供される D 代入文の集合です。これを使って、入力式を構造型のオブジェクトに翻訳できます。以下では、stdio.h に定義された ANSI-C 標準ライブラリルーチンの例を使って、トランスレータの必要性および使用方法について説明します。これらのルーチンは、FILE という名前のデータ構造に作用します。FILE の実装アーティファクトは、C プログラマによって抽出されています。データ構造の抽象オブジェクトは、公開ヘッダーファイルでデータ構造の前方宣言だけを行い、対応する構造体定義を別の非公開ヘッダーファイルに保存することによって作成できます。これが標準の方法です。
C プログラムの作成中に、FILE 構造体のファイル記述子について調べる場合は、FILE 構造体のメンバーを間接参照するのではなく、fileno(3C) 関数を使って記述子を取得します。Solaris ヘッダーファイルは、この規則を徹底させるため、FILE を不透明な前方宣言タグとして定義しています。このため、<stdio.h> を含む C プログラムから FILE を間接参照することはできません。libc.so.1 ライブラリ内の fileno() が、次のような C プログラムによって実装されていると仮定します。
int fileno(FILE *fp) { struct file_impl *ip = (struct file_impl *)fp; return (ip->fd); }
この例の fileno() は、引数として FILE ポインタをとり、これを対応する内部 libc 構造のポインタ struct file_impl へキャストします。そして、実装構造の fd メンバーの値を返します。なぜ Solaris には、このようなインタフェースが実装されているのでしょうか。それは、Sun がクライアントプログラムから現在の libc 実装の詳細を抽出することにより、継続的に libc の内部実装の詳細を発展させ、変更しながら、強力なバイナリ互換性の実現に努めることができるからです。上の例で、fd メンバーのサイズや struct file_impl 内での位置は、パッチで変更されることすらありえます。しかし、fileno(3C) を呼び出す既存のバイナリは、これらのアーティファクトに依存していないため、このような変更の影響を受けません。
残念ながら、DTrace のような監視機能付きソフトウェアでは、実装内部に注目しないと有効な結果が得られません。このため、Solaris ライブラリ内やカーネル内に定義されている任意の C 関数を呼び出す余裕はありません。stdio.h に宣言されているルーチンを実装するために、D プログラム内に struct file_impl のコピーを宣言することも可能ですが、その場合 D プログラムはライブラリの非公開実装アーティファクトに依存することになります。こうした非公開実装アーティファクトは、将来のマイクロリリースやマイナーリリースで、場合によってはパッチを適用しただけで、使用できなくなる可能性があります。理想的な D プログラム用構文は、ライブラリの実装に結合され、適宜更新されながらも、より安定性の高い抽象層を提供できるような構文です。
新しいトランスレータを作成するときは、次のような宣言を使用します。
translator output-type < input-type input-identifier > { member-name = expression ; member-name = expression ; ... };
output-type には、構造体を指定します。これが、翻訳の結果の型になります。input-type には、入力式の型を指定し、山括弧 (< >) で囲んで、トランスレータ式で入力式の別名として使用できる入力識別子 input-identifier を 1 つ追加します。トランスレータの本体は、中括弧 ({ }) 内に記述され、セミコロン (;) で終わります。トランスレータを構成するのは、メンバー名 (member-name) と、翻訳式に対応する識別子です。メンバー宣言では、出力型 output-type の固有のメンバーを指定する必要があります。また、D 代入演算子 (=) の規則に従って、指定したメンバーの型と互換性のある型の式を割り当てる必要があります。
たとえば、使用可能ないくつかの libc インタフェースに基づいて、stdio ファイルについての安定した情報の構造体を定義します。
struct file_info { int file_fd; /* file descriptor from fileno(3C) */ int file_eof; /* eof flag from feof(3C) */ };
続いて、FILE を file_info へ翻訳する D トランスレータを宣言します。
translator struct file_info < FILE *F > { file_fd = ((struct file_impl *)F)->fd; file_eof = ((struct file_impl *)F)->eof; };
このトランスレータでは、FILE * 型の入力式に、入力識別子 F が割り当てられます。その後、識別子 F をトランスレータメンバー式で FILE * 型の変数として使用できます。この変数は、トランスレータ宣言の本体内でしか、可視的に使用されません。出力 file_fd メンバーの値を特定するため、トランスレータは、上記の fileno(3C) の実装例と同じように、キャストと間接参照を行います。EOF 指示子の値を得るときも、同様の翻訳が行われます。
Sun では、D プログラムから起動可能な、Solaris インタフェース用のトランスレータセットを提供しています。これらのトランスレータは、対応するインタフェースの実装が変更されても、以前に定義されたインタフェースの安定性規則に従って保持されます。これらのトランスレータについては、D でトランスレータを呼び出す方法を説明したあと説明します。トランスレータ機能自体は、ソフトウェアパッケージの状態の監視用の独自のトランスレータを D プログラマに提供したいと考える、アプリケーション開発者やライブラリ開発者も、対象としています。
D 演算子 xlate は、入力式を定義済みの翻訳出力構造へ翻訳するときに使用します。xlate 演算子の使用形式は、次のとおりです。
xlate < output-type > ( input-expression )
たとえば、先ほど定義した FILE 構造体のトランスレータを呼び出し、file_fd メンバーにアクセスしたい場合は、次のような式を作成します。
xlate <struct file_info *>(f)->file_fd;
f は FILE * 型の D 変数です。xlate 式そのものには、output-type で定義された型が割り当てられます。トランスレータの定義後は、このトランスレータを使って、入力式を出力構造型またはこの構造型のポインタへ翻訳できます。
入力式を構造体に翻訳する場合は、演算子「.」を使って、特定の出力メンバーを間接参照するか、翻訳済みの構造体全体を別の D 変数に割り当てて、すべてのメンバーの値のコピーを作成するかします。単一のメンバーを間接参照する場合、D コンパイラは、そのメンバーの式に対応するコードしか生成しません。翻訳済みの構造体に & 演算子を適用して、そのアドレスを取得することはできません。というのも、コピーが作成されるか、いずれかのメンバーが参照されるまで、データオブジェクト自体が存在しないからです。
入力式を構造体のポインタに翻訳する場合は、演算子「->」を使って、出力の特定メンバーを間接参照するか、単項演算子「*」を使ってポインタを間接参照するかします。後者の場合、式を構造体に翻訳した場合と同じ結果が得られます。単一のメンバーを間接参照する場合、D コンパイラは、そのメンバーの式に対応するコードしか生成しません。翻訳済みのポインタを別の D 変数に割り当てることはできません。というのも、コピーが作成されるか、いずれかのメンバーが参照されるまで、データオブジェクト自体が存在しないからです。存在しないデータオブジェクトにアドレスを指定することはできません。
トランスレータ宣言では、1 つ以上の出力型のメンバーの式を省略できます。たとえば、xlate 式を使って、翻訳式が定義されていないメンバーにアクセスしようとすると、D コンパイラからエラーメッセージが返され、プログラムのコンパイルが中止されます。構造体の割り当てによって出力型全体をコピーした場合、翻訳式が定義されていないメンバーはゼロで初期化されます。
xlate 操作に適したトランスレータを見つけ出すため、D コンパイラは、使用可能なトランスレータを次の順番でチェックします。
まず、コンパイラは、入力式の型から出力型への翻訳を探します。
次に、コンパイラは、配下の型名の型付きの別名に従って、入力型と出力型の「解決」を行います。その後、解決済み入力型から解決済み出力型への翻訳を探します。
その次に、コンパイラは、互換性のある入力型から解決済みの出力型への翻訳を探します。コンパイラは、関数呼び出しの引数と関数のプロトタイプの互換性について判断するときと同じ規則に従って、入力式の型とトランスレータの入力型に互換性があるかどうかを判断します。
これらの規則に基づいて一致するトランスレータが見つからない場合は、D コンパイラからエラーメッセージが出力され、プログラムのコンパイルが失敗します。
DTrace ライブラリファイル /usr/lib/dtrace/procfs.d は、D プログラム内で使用するトランスレータのセットを提供します。これらのトランスレータは、プロセスやスレッドのオペレーティングシステムカーネルの実装構造を、安定した proc(4) 構造である psinfo や lwpsinfo に翻訳します。これらの構造は、Solaris の /proc ファイルシステムファイル /proc/pid/psinfo や /proc/pid/lwps/lwpid/lwpsinfo でも使用されます。また、これらの定義は、システムヘッダーファイル /usr/include/sys/procfs.h に格納されています。これらの構造は、プロセスやスレッドについての安定した役立つ情報を定義します。たとえば、プロセス ID、LWP ID、初期引数、および ps(1) コマンドで出力されるその他のデータがこれに該当します。構造体のメンバーと意味は、proc(4) のマニュアルページで確認できます。
表 40–1 procfs.d トランスレータ
入力型 |
入力型の属性 |
出力型 |
出力型の属性 |
---|---|---|---|
proc_t * |
非公開/非公開/共通 |
psinfo_t * |
安定/安定/共通 |
kthread_t * |
非公開/非公開/共通 |
lwpsinfo_t * |
安定/安定/共通 |
トランスレータは、情報を安定したデータ構造に変換する機能を備えています。しかし、データの翻訳時に発生するあらゆる安定性の問題を解決できるわけではありません。たとえば、xlate 操作の入力式そのものが、変更の可能性があるデータを参照している場合、結果の D プログラムも変更される可能性があります。これは、プログラムの安定性は、常に、蓄積された D プログラム文と式の最小限の安定性として計算されるためです。そのため、安定したプログラムを作成するためには、トランスレータに安定した入力式を定義する必要があります。D インライン機構は、「安定した翻訳」を促進するために使用されます。
procfs.d ライブラリは、以前に安定した翻訳として紹介した curlwpsinfo 変数と curpsinfo 変数を提供します。たとえば、curlwpsinfo 変数は、実際には次のように inline 宣言されます。
inline lwpsinfo_t *curlwpsinfo = xlate <lwpsinfo_t *> (curthread); #pragma D attributes Stable/Stable/Common curlwpsinfo
curlwpsinfo 変数は、curthread 変数、スレッドを表すカーネルの非公開データ構造のポインタ、安定した lwpsinfo_t 型により、インライン翻訳として定義されています。D コンパイラは、このライブラリファイルを処理し、inline 宣言をキャッシュに格納します。これにより、curlwpsinfo の見た目はその他の D 変数と同じになります。この宣言の後ろの #pragma 文は、curlwpsinfo 識別子の属性を明示的に「Stable/Stable/Common」にリセットし、インライン式に含まれる curthread の参照をマスクします。この D 機能の組み合わせにより、D プログラマは、Solaris 実装に変更が加えられるたびに更新される可能性のある curthread を、翻訳ソースとして安全に使用することができます。