共用体も、ANSI-C と D の両方でサポートされる複合型です。共用体と構造体の間には、密接な関係があります。共用体では、異なった型を持つ複数のメンバーが定義されますが、メンバーオブジェクトはすべて同じ記憶域を占有します。つまり共用体は、時と場合に応じて有効なメンバーが変わる、バリアント型のオブジェクトであり、ある時点で有効なメンバーは、共用体の割り当て方によって決まります。通常、共用体のどのメンバーが有効になるかは、その他の変数や状態によって決定されます。共用体のサイズは、その最大のメンバーのサイズに一致しており、共用体のメモリー配置は、そのメンバーに必要な最大配置に一致しています。
Solaris の kstat フレームワークで定義されている構造体には、C と D の共用体について説明する以下の例で使用されている共用体が含まれます。kstat フレームワークは、メモリーの使用率や入出力スループットなどのカーネル統計情報を表す、名前付きのカウンタのセットをエクスポートするために使用されます。このフレームワークは、mpstat(1M) や iostat(1M) のようなユーティリティーの実装にも使用されます。このフレームワークは、struct kstat_named を使って、名前付きカウンタとその値を表します。このフレームワークの定義は以下のとおりです。
struct kstat_named { char name[KSTAT_STRLEN]; /* name of counter */ uchar_t data_type; /* data type */ union { char c[16]; int32_t i32; uint32_t ui32; long l; ulong_t ul; ... } value; /* value of counter */ };
上記の宣言は、わかりやすくするために省略されています。構造体宣言の全容は、<sys/kstat.h> ヘッダーファイルで確認できます。また、kstat_named(9S) にも説明が記載されています。上記の宣言は、ANSI-C と D のどちらでも有効です。この宣言では、カウンタの型によって異なった型のメンバーを持つ共用体の値をメンバーとして含む、構造体が定義されています。共用体自体が別の型 struct kstat_named の内部に宣言されているので、共用体の正式名は省略されています。この宣言書式は、「無名共用体」として知られています。メンバー value は、先ほどの宣言で記述された共用体型ですが、この共用体型はほかの場所では使用されないので、名前を持っていません。構造体メンバー data_type には、struct kstat_named 型のオブジェクトごとに、どの共用体メンバーが有効になるかを示す値が割り当てられています。data_type の値には、C プリプロセッサトークンのセットが定義されています。たとえば、トークン KSTAT_DATA_CHAR はゼロであり、現在値がメンバー value.c に格納されていることを示しています。
例 7–3 は、ユーザープロセスをトレースすることにより、kstat_named.value という共用体にアクセスする様子を示しています。kstat_data_lookup(3KSTAT) 関数を使って、kstat カウンタの標本がユーザープロセスから収集され、この関数は struct kstat_named へのポインタを返します。mpstat(1M) ユーティリティーは、実行されるたびにこの関数を繰り返し呼び出し、最新のカウンタ値を収集します。シェルで mpstat 1 を実行し、出力結果を確認してください。しばらくしたら、Control-C キーを押して、mpstat を終了させます。収集したカウンタを確認するため、libkstat 内で mpstat コマンドが kstat_data_lookup(3KSTAT) 関数を呼び出すたびに起動するプローブを有効にします。このためには、新しい DTrace プロバイダ pid を使用します。pid プロバイダを使用すると、関数のエントリポイントなど、C のシンボル位置にあるユーザープロセス内で、動的にプローブを作成できます。次のようなプローブ記述を作成して、pid に、ユーザー関数の開始 (entry) 位置と終了 (return) 位置でプローブを作成させることもできます。
pidprocess-ID:object-name:function-name:entry pidprocess-ID:object-name:function-name:return
たとえば、プロセス ID 内に、kstat_data_lookup(3KSTAT) の開始時に起動するプローブを作成したい場合、次のようなプローブ記述を作成します。
pid12345:libkstat:kstat_data_lookup:entry
pid プロバイダは、プローブ記述に対応するプログラム位置で、指定したユーザープロセスに、動的計測機能を挿入します。このプローブ実装により、計測されるプログラム位置に到達したユーザースレッドは、オペレーティングシステムカーネルに割り込んで DTrace に入り、対応するプローブを起動するようになります。したがって、計測機能の場所がユーザープロセスに関連付けられていても、指定した DTrace の述語とアクションは、オペレーティングシステムカーネルのコンテキストで実行されます。pid プロバイダの詳細は、第 30 章pid プロバイダに記載されています。
プログラムのコンパイル時に評価されて dtrace の追加コマンド行引数になる「マクロ変数」という識別子をプログラムに挿入すると、D プログラムを別のプロセスに適用するたびに D プログラムソースを編集する必要がなくなります。マクロ変数を指定するときは、ドル記号 ($) と、識別子として機能する数字を入力します。コマンド dtrace -s script foo bar baz を実行した場合、D コンパイラにより自動的に、マクロ変数 $1、$2、$3 がそれぞれトークン foo、bar、baz として定義されます。マクロ変数は、D プログラム式やプローブ記述で使用します。たとえば、次のプローブ記述では、dtrace の追加引数として指定されているプロセス ID を計測できます。
pid$1:libkstat:kstat_data_lookup:entry { self->ksname = arg1; } pid$1:libkstat:kstat_data_lookup:return /self->ksname != NULL && arg1 != NULL/ { this->ksp = (kstat_named_t *)copyin(arg1, sizeof (kstat_named_t)); printf("%s has ui64 value %u\n", copyinstr(self->ksname), this->ksp->value.ui64); } pid$1:libkstat:kstat_data_lookup:return /self->ksname != NULL && arg1 == NULL/ { self->ksname = NULL; }
マクロ変数と再利用可能なスクリプトの詳細は、第 15 章スクリプトの作成で説明します。プロセス ID を使ってユーザープロセスを計測する方法がわかったところで、共用体の標本の収集を再開しましょう。次のソースコード例をエディタに入力し、kstat.d という名前で保存してください。
pid$1:libkstat:kstat_data_lookup:entry { self->ksname = arg1; } pid$1:libkstat:kstat_data_lookup:return /self->ksname != NULL && arg1 != NULL/ { this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t)); printf("%s has ui64 value %u\n", copyinstr(self->ksname), this->ksp->value.ui64); } pid$1:libkstat:kstat_data_lookup:return /self->ksname != NULL && arg1 == NULL/ { self->ksname = NULL; }
シェルで mpstat 1 コマンドを実行して、統計情報の標本を収集し、毎秒報告するモードで mpstat(1M) の実行を開始します。mpstat の実行開始後、別のシェルでコマンド dtrace -q -s kstat.d `pgrep mpstat` を実行します。アクセス対象の統計情報に対応した出力が得られます。dtrace を強制終了し、シェルプロンプトに戻るには、Control-C キーを押します。
# dtrace -q -s kstat.d `pgrep mpstat` cpu_ticks_idle has ui64 value 41154176 cpu_ticks_user has ui64 value 1137 cpu_ticks_kernel has ui64 value 12310 cpu_ticks_wait has ui64 value 903 hat_fault has ui64 value 0 as_fault has ui64 value 48053 maj_fault has ui64 value 1144 xcalls has ui64 value 123832170 intr has ui64 value 165264090 intrthread has ui64 value 124094974 pswitch has ui64 value 840625 inv_swtch has ui64 value 1484 cpumigrate has ui64 value 36284 mutex_adenters has ui64 value 35574 rw_rdfails has ui64 value 2 rw_wrfails has ui64 value 2 ... ^C # |
各端末ウィンドウ内の出力を取得し、統計情報の値をそれぞれ 1 つ前の値から次々に減算していけば、dtrace の出力と mpstat の出力の相関関係が明らかになります。このプログラム例は、ルックアップ関数に入るときにカウンタ名のポインタを記録し、kstat_data_lookup(3KSTAT) が終了するときにほとんどのトレース作業を実行します。D の組み込み関数 copyinstr() と copyin() は、戻り値 arg1 が NULL 以外のとき、ユーザープロセスから DTrace に関数の実行結果をコピーします。kstat データのコピーが完了すると、共用体の ui64 カウンタ値が報告されます。この単純な例では、mpstat が value.ui64 メンバーを使用するカウンタの標本を収集すると想定しています。練習として、複数の述語を使用し、data_type メンバーに対応する共用体メンバーを出力するよう、kstat.d を書き直してみてください。また、連続するデータ値の差分を計算して mpstat に似た結果を実際に出力するような kstat.d も作成してみてください。