2 Dプログラミング言語
Dシステム・プログラミング言語を使用すると、オペレーティング・システムのAPIおよびハードウェアとのインタフェースを確立できます。この章では、Dプログラムの全体構造と、複数のプローブと一致するプローブ記述を作成するための様々な機能について正式に説明します。この章では、DプログラムでのCプリプロセッサcpp
の使用についても説明します。
Dプログラム構造
Dプログラム(スクリプトとも呼ばれる)は、有効にするプローブと、これらのプローブにバインドする述語およびアクションを記述する一連の節で構成されています。Dプログラムには、変数の宣言と新しい型の定義を含めることもできます。詳細は、「変数」および「型と定数の定義」を参照してください。
プローブ節および宣言
このガイドのこれまでの例からわかるように、Dプログラムのソース・ファイルは、DTraceによって有効化されるインストゥルメンテーションについて記述した1つ以上のプローブ節で構成されます。各プローブ節では、次の一般的な形式が使用されます。
probe descriptions / predicate / { action statements }
述語とアクション文のリストは省略可能です。プローブ節の外側にあるディレクティブは、宣言と呼びます。宣言は、プローブ節の外側でのみ使用できます。宣言は、中カッコ({}
)内では使用できません。また、宣言は、前述の例のプローブ節の要素間に散在できません。Dプログラムの要素間の区切りと、アクション文のインデントには、空白を使用できます。
宣言を使用して、Dの変数および外部のC記号を宣言するか、Dで使用するために新しい型を定義できます。詳細は、「変数」および「型と定数の定義」を参照してください。Dプログラムでは、プラグマと呼ばれる特殊なDコンパイラ・ディレクティブも、プローブ節の外側も含めた任意の場所で使用できます。Dプラグマは、先頭文字が#
の行に指定されます。たとえば、DプラグマはDTraceランタイム・オプションの設定に使用されます。詳細は、「オプションおよびチューニング可能パラメータ」を参照してください。
プローブ記述
プログラム節はすべて、1つ以上のプローブ記述で始まります。プローブ記述は、通常、次の形式をとります。
provider:module:function:name
プローブ記述の一部のフィールドを省略した場合、指定したフィールドは、Dコンパイラによって右から左の順で解釈されます。たとえば、foo:bar
というプローブ記述は、プローブのプロバイダおよびモジュール・フィールドの値に関係なく、foo
という関数とbarという名前のプローブと一致します。このため、プローブ記述は、より正確には、名前に基づいて1つ以上のプローブと一致させるために使用できるパターンとみなすことができます。
Dプローブ記述を作成するときは、左側に目的のプロバイダを指定できるように、4つのフィールドのデリミタをすべて指定する必要があります。プロバイダを指定しない場合、複数のプロバイダが同じ名前のプローブを発行するときに、予想外の結果になる可能性があります。同様に、プローブ記述を部分的に指定する場合、後続のバージョンのDTraceに組み込まれる新しいプロバイダのプローブが予期せずに一致する可能性もあります。プロバイダのみを指定して、モジュール、関数および名前フィールドを空白のままにすると、このプロバイダのすべてのプローブと一致します。たとえば、syscall:::
という記述を使用すると、DTraceのsyscall
プロバイダによって発行されているすべてのプローブと一致します。
プローブ記述では、シェルの展開というパターン・マッチング構文(sh(1)
マニュアル・ページを参照)と類似したパターン・マッチング構文もサポートしています。プローブと記述を照合する前に、各記述フィールドに*
、?
および[
の文字がないかスキャンされます。プローブ記述フィールドにこれらのいずれかの文字が含まれており、その直前の文字が\
でない場合、このフィールドはパターンとみなされます。記述パターンは、指定したプローブの対応するフィールド全体と一致する必要があります。プローブが正常に一致して有効化されるには、完全なプローブ記述で、すべてのフィールドが一致する必要があります。パターンではないプローブ記述フィールドは、対応するプローブのフィールドに完全に一致する必要があります。空の記述フィールドは、任意のプローブに一致します。
次の表に、プローブ名のパターン内で認識される特殊文字を示します。
表2-1 プローブ名のパターン・マッチング文字
記号 | 説明 |
---|---|
|
NULL文字列を含む、任意の文字列に一致します。 |
|
任意の1文字を検索します。 |
|
大カッコで囲まれたいずれかの文字に一致します。文字のペアを |
|
次の文字に特別な意味を持たせず、文字どおりに解釈します。 |
パターン・マッチング文字は、プローブ記述の4つのフィールド全部、またはそのいずれかで使用できます。dtrace -lコマンドとともにコマンドラインでパターンを使用して、一致するプローブをリストすることもできます。たとえば、dtrace -l -f kmem_*コマンドでは、名前が接頭辞kmem_
で始まる関数内のすべてのDTraceプローブがリストされます。
複数のプローブ記述または記述パターンで同じ述語とアクションを指定する場合は、カンマ区切りのリスト形式で記述します。たとえば次のDプログラムでは、read
またはwrite
という文字列を含むシステム・コールのエントリに関連付けられたプローブが起動するたびに、タイムスタンプがトレースされます。
syscall::*read*:entry, syscall::*write*:entry { trace(timestamp); }
プローブ記述では、整数のプローブIDを使用してプローブを指定することもできます。たとえば、次の節を使用して、dtrace -l -i 12345でレポートされる、プローブID 12345
を有効にできます。
12345 { trace(timestamp); }
ノート:
Dプログラムを作成するときは、常に人間が読めるプローブ記述を使用する必要があります。整数のプローブIDは、DTraceプロバイダ・カーネル・モジュールのロード後、アンロード後、またはその再起動後に一貫している保証はありません。
句の述語
述語は、スラッシュのペア(//
)で囲まれた式であり、プローブ起動時に評価され、関連アクションを実行するかどうかを決定します。述語は、Dプログラムでより複雑な制御フローを構築するために使用する主要な条件構成です。任意のプローブで、プローブ節の述語セクション全体を省略できます。この場合、プローブの起動時に常にアクションが実行されます。
述語式では、すべてのD演算子を使用できます。また、変数や定数などの任意のDデータ・オブジェクトを参照できます。述語式は、真または偽の結果を導き出すため、整数型またはポインタ型の値に評価される必要があります。すべてのD式と同じく、ゼロ値は偽、ゼロ以外の値は真と解釈されます。
プローブのアクション
プローブのアクションは、セミコロン(;
)で区切られた文のリストを中カッコ({}
)で囲んだ形式で記述します。中カッコ内が空で、文が含まれない場合は、CPUとプローブを出力するデフォルトのアクションにつながります。
実行の順序
プローブに対するアクションは、それらのアクションが同じ節内にあるか、異なる節にあるかに関係なく、プログラム順に実行されます。
これ以外に順序付けに関する制約は課されません。2つの異なるプローブからの出力が散在したり、プローブの起動順序とは逆の順序で表示されることがあります。また、異なるCPUから生成された出力は表示順序が異なる場合があります。
Cプリプロセッサの使用
Linuxシステム・インタフェースの定義に使用されているCプログラミング言語には、Cプログラムのコンパイルの一連の初期ステップを実行するプリプロセッサが用意されています。Cプリプロセッサは、一般に、マクロ置換(Cプログラム内のトークンを別の事前定義済トークン・セットで置き換える)の定義や、システム・ヘッダー・ファイルのコピーの挿入に使用されます。dtraceコマンドに-cオプションを指定することにより、DプログラムとともにCプリプロセッサを使用できます。このオプションを指定すると、dtraceコマンドはプログラム・ソース・ファイルに対してcppプリプロセッサを実行してから、結果をDコンパイラに渡します。Cプリプロセッサの詳細は、KernighanおよびRitchieによるThe C Programming Language (詳細が「はじめに」に記載されています)を参照してください。
Dコンパイラは、オペレーティング・システム実装に関連付けられたC型の記述セットを自動的にロードします。ただし、プリプロセッサを使用して、独自のCプログラムで使用される型など、他の型定義を含めることができます。また、プリプロセッサを使用して、Dコードのチャンクに拡張されるマクロや他のプログラム要素を作成するなどの他のタスクも実行できます。Dプログラムでプリプロセッサを使用するときは、有効なD宣言を含むファイルのみを組み込みます。Cヘッダー・ファイルは型とシンボルの外部宣言のみを含む場合、Dコンパイラで正しく解釈されます。ただし、C関数のソース・コードのような追加プログラム要素を含むCヘッダー・ファイルは、Dコンパイラでは構文解析できません。このようなファイルを使用した場合、該当するエラー・メッセージが表示されます。
コンパイルおよびインストゥルメンテーション
従来のプログラムを作成する場合は、通常、コンパイラを使用して、ソース・コードを実行可能なオブジェクト・コードに変換します。dtraceコマンドを使用する場合、hello.d
プログラムを作成するために前述の例で使用していたD言語用のコンパイラを呼び出します。プログラムがコンパイルされると、このプログラムはDTraceによって実行されるためにオペレーティング・システムのカーネルに送られます。ここで、プログラム内で名前が付けられたプローブが有効になり、対応するプロバイダによってこれらをアクティブ化するために必要なインストゥルメンテーションが実行されます。
DTraceのインストゥルメンテーションはすべて完全に動的に行われます。プローブが別個に有効になるのは、プローブを使用する場合のみです。アクティブではないプローブにインストゥルメンテーション・コードは存在しないため、DTraceを使用していない場合にパフォーマンスが低下することはありません。試験が完了し、dtraceコマンドが終了すると、使用していたすべてのプローブは自動的に無効になり、そのインストゥルメンテーションは削除され、システムは元の状態に戻ります。DTraceがアクティブではないシステムとDTraceソフトウェアがインストールされていないシステムとの間には、型情報およびDTrace自体に必要な数MBのディスク領域を除き、実質的な差異は存在しません。
各プローブのインストゥルメンテーションは、実行中のオペレーティング・システムまたは選択したユーザー・プロセスで動的に実行されます。システムが静止または一時停止されることはなく、インストゥルメンテーション・コードは有効になっているプローブにのみ追加されます。結果として、DTraceを使用したプローブの影響は、DTraceに実行させる内容のみに制限されます。無関係のデータはトレースされず、システム内で大規模なトレース・スイッチがオンになることはありません。すべてのDTraceのインストゥルメンテーションは、可能なかぎり効率的になるよう設計されています。これらの機能により、DTraceを本番環境で使用することで実際の問題をリアルタイムで解決できます。
DTraceフレームワークでは、任意の数の仮想クライアントもサポートされます。DTrace実行文とコマンドは、システムのメモリー容量の範囲内で、同時にいくつでも実行できます。コマンドはすべて、同じ基礎となるインストゥルメンテーションを使用して独立して動作します。この同じ機能によって、システム上の任意の数の個別ユーザーが同時にDTraceを使用できます。開発者、管理者およびサービス担当者はすべて、共同で作業、または相互に干渉することなく個別に同じシステム上でDTraceを使用して問題に対処できます。
CまたはC++で作成されたプログラムとは異なり、DTraceのDプログラムは、Javaプログラミング言語で作成されたプログラムと同じように、プローブの起動時に実行に使用される安全な中間形式にコンパイルされます。この中間形式は、DTraceカーネル・ソフトウェアがプログラムを最初に調査する際にその安全性が検証されます。また、DTrace実行環境では、Dプログラムの実行中に発生する可能性のあるランタイム・エラー(0 (ゼロ)による除算や無効なメモリーの間接参照など)も処理され、これらがレポートされます。結果として、危険なプログラムを作成したため、オペレーティング・システムのカーネルやシステム上で実行されているプロセスの1つに対してDTraceが意図せずに破損するような危険を回避できます。これらの安全機能により、システムのクラッシュや破損を懸念することなく、本番環境でDTraceを使用できます。プログラミング上の間違いがあると、DTraceによってエラーがレポートされ、インストゥルメンテーションが無効化されるため、間違いを修正して再試行できます。DTraceのエラー・レポートおよびデバッグ機能については、このガイドで後述します。
図2-*に、DTraceアーキテクチャの様々なコンポーネントを示します。
DTraceアーキテクチャおよび構成要素の概要
![この図は、カーネル領域にロードされてDTraceドライバと通信するプローブ・プロバイダ、ユーザー領域のDTraceライブラリ、およびDTraceライブラリにコールするdtraceコマンドを含むDTraceアーキテクチャの様々な構成要素を示しています。 この図は、カーネル領域にロードされてDTraceドライバと通信するプローブ・プロバイダ、ユーザー領域のDTraceライブラリ、およびDTraceライブラリにコールするdtraceコマンドを含むDTraceアーキテクチャの様々な構成要素を示しています。](img/dtrace.png)
DTraceの動作内容が理解できたところで、Dプログラミング言語の説明に戻り、興味あるいくつかのプログラムの作成を開始しましょう。
変数および算術式
次のプログラム例では、DTraceのprofile
プロバイダを使用して、単純な時間ベースのカウンタを実装します。profileプロバイダは、Dプログラム内の記述に基づいて新しいプローブを作成できます。profile:::tick-
n sec
(nは整数)という名前のプローブを作成する場合、profileプロバイダは、n秒ごとに起動するプローブを1つ作成します。次のソース・コードを入力し、counter.d
という名前のファイルに保存します。
/* * Count off and report the number of seconds elapsed */ dtrace:::BEGIN { i = 0; } profile:::tick-1sec { i = i + 1; trace(i); } dtrace:::END { trace(i); }
実行すると、[Ctrl]キーを押しながら[C]キー
を押すまでの経過秒数がカウントされ、最後に合計が出力されます。
# dtrace -s counter.d dtrace: script 'counter.d' matched 3 probes CPU ID FUNCTION:NAME 1 638 :tick-1sec 1 1 638 :tick-1sec 2 1 638 :tick-1sec 3 1 638 :tick-1sec 4 1 638 :tick-1sec 5 1 638 :tick-1sec 6 1 638 :tick-1sec 7 ^C 1 638 :tick-1sec 8 0 2 :END 8
プログラムの最初の3行は、プログラムの内容を説明するコメント行です。Dコンパイラでは、C、C++およびJavaプログラミング言語の場合と同様に、/*
と*/
シンボルで囲まれた文字列はすべて無視されます。コメントは、Dプログラム内のどこでも使用できます。プローブ節の中でも外でも使用できます。
BEGIN
プローブ節では、次の文を使用して、i
という名前の新しい変数が定義され、整数値0が割り当てられています。
i = 0;
counter.d
では、変数i
に整数定数ゼロが最初に割り当てられます。このため、この型はint
に設定されます。Dは、次の表のものを含む、Cと同じ基本的な整数データ型を提供します。
データ型 | 説明 |
---|---|
|
文字または単一バイトの整数 |
|
デフォルト整数 |
|
短整数 |
|
長整数 |
|
拡張された長整数 |
これらのデータ型のサイズは、オペレーティング・システム・カーネルのデータ・モデル(「型、演算子および式」を参照)によって異なります。また、Dでは、オペレーティング・システムごとに定義されている何千もの他のデータ型と同様に、様々な固定サイズの符号付き/符号なし整数型に、覚えやすい組込み名が付けられています。
counter.d
の中央部分には、カウンタi
の値を増分するプローブ節があります。
profile:::tick-1sec { i = i + 1; trace(i); }
この節には、使用可能なプロセッサ上で毎秒1回起動する新しいプローブを作成するようにprofile
プロバイダに指示する、profile:::tick-1sec
というプローブが指定されています。この節に含まれる2つの文のうち、最初の文はi
を増分し、2番目の文は新しいi
の値をトレース(出力)します。Dでは、通常のC算術演算子をすべて使用できます。完全なリストは、「型、演算子および式」を参照してください。trace
関数は、引数として任意のD式を取ります。これにより、counter.d
をさらに簡潔に作成すると、次のようになります。
profile:::tick-1sec { trace(++i); }
変数i
の型を明示的に制御するには、目的の型を割り当てるときに丸カッコで囲みます。これにより、整数0がその型にキャストされます。たとえば、Dでchar
の最大サイズを決定するには、BEGIN
節を次のように変更します。
dtrace:::BEGIN { i = (char)0; }
counter.dの実行を開始してしばらくすると、トレース値が大きくなり、その後再びゼロに戻ります。値が戻るのを待てない場合は、profile
プローブ名をprofile:::tick-100msec
に変更してみてください。こうすれば、カウンタの値は100ミリ秒に1回(毎秒10回)のペースで増分するようになります。
述語の例
ランタイムの安全性については、Dと他のプログラミング言語(C、C++およびJavaプログラミング言語など)の顕著な違いの1つは、Dでは、if
文やループなどの制御フロー構成が存在しない点にあります。Dプログラム節は、固定量のデータ(オプション)をトレースする単一の直線的な文のリストとして作成されます。Dでは、述語という論理式を使用して、条件付きでデータをトレースしたり、制御フローを変更することができます。述語式は、プローブの起動時、対応する節に関連付けられた任意の文が実行される前に評価されます。述語の評価の結果が真(ゼロ以外の値)である場合、文のリストが実行されます。述語の評価の結果が偽(ゼロ)である場合、文は実行されず、プローブの起動は無視されます。
次の例では、次のようにソース・コードを入力し、countdown.d
という名前のファイルに保存してください。
dtrace:::BEGIN { i = 10; } profile:::tick-1sec /i > 0/ { trace(i--); } profile:::tick-1sec /i == 0/ { trace("blastoff!"); exit(0); }
このDプログラムは、述語を使用して、10秒間のカウントダウンを行うタイマーを実装しています。countdown.dを実行すると、10からカウントダウンが開始され、メッセージが出力された後、終了します。
# dtrace -s countdown.d dtrace: script 'countdown.d' matched 3 probes CPU ID FUNCTION:NAME 0 638 :tick-1sec 10 0 638 :tick-1sec 9 0 638 :tick-1sec 8 0 638 :tick-1sec 7 0 638 :tick-1sec 6 0 638 :tick-1sec 5 0 638 :tick-1sec 4 0 638 :tick-1sec 3 0 638 :tick-1sec 2 0 638 :tick-1sec 1 0 638 :tick-1sec blastoff! #
この例では、BEGIN
プローブを使用して、整数i
を10に初期化し、カウントダウンを開始しています。次に、前述の例と同じように、tick-1sec
プローブを使用して、毎秒1回起動するタイマーを実装しています。countdown.d
では、tick-1sec
プローブ記述が、述語とアクション・リストの異なる2つの節で使用されています。述語は、前後をスラッシュ//
で囲まれた論理式で、プローブ名と、節の文リストが入っている中カッコ{}
の間にあります。
最初の述語は、i
がゼロより大きいかどうか(タイマーがまだ実行中かどうか)をテストします。
profile:::tick-1sec /i > 0/ { trace(i--); }
関係演算子>
は、「より大きい」を意味し、偽の場合は整数値0、真の場合は1を返します。Dでは、すべてのC関係演算子がサポートされています。完全なリストは、「型、演算子および式」を参照してください。i
がまだ0になっていない場合、i
がトレースされ、--
演算子を使用して値が1ずつ減らされます。
2番目の述語は、==
演算子を使用して、i
が0 (カウントダウンが完了した)の場合に真を返します。
profile:::tick-1sec /i == 0/ { trace("blastoff!"); exit(0); }
最初のhello.d
の例の場合と同様に、countdown.d
では、二重引用符で囲まれた文字列(文字列定数と呼ばれます)を使用して、カウントダウンの完了時の最終メッセージを出力します。次に、exit
関数を使用してdtraceを終了し、シェル・プロンプトに戻ります。
countdown.d
の構造をよく見ると、プローブの記述は同じであるが述語とアクションが異なる2つの節を作成することによって、次のような論理フローが作成されていることがわかります。
i = 10 once per second, if i is greater than zero trace(i--); if i is equal to zero trace("blastoff!"); exit(0);
述語を使用して複雑なプログラムを作成するときは、まずこの方法でアルゴリズムを視覚化した上で、それぞれの条件構文を別々の節および述語に変換してみてください。
では次に、述語を新しいプロバイダsyscall
と組み合せて、最初のDトレース・プログラムを実際に作成してみましょう。syscall
プロバイダを使用すると、任意のOracle Linuxシステム・コールの開始時または終了時にプローブを有効にすることができます。次の例では、DTraceを使用して、シェルがread()
またはwrite()
システム・コールを毎回実行する様子を観察します。まず、ウィンドウを2つ開きます。1つはDTraceに使用するウィンドウ、もう1つは監視するシェル・プロセスが含まれるウィンドウです。2番目のウィンドウで、次のコマンドを入力して、このシェルのプロセスIDを取得します。
# echo $$ 2860
ここで、最初のウィンドウに戻り、次のDプログラムを入力して、rw.d
という名前のファイルに保存します。プログラムを入力する際、整数定数2860
は、echo
コマンドに応じて出力されたシェルのプロセスIDに置き換えてください。
syscall::read:entry, syscall::write:entry /pid == 2860/ { }
rw.d
のプローブ節の本体は空白のままにします。これは、このプログラムではプローブの起動通知をトレースするだけで、追加データはトレースしないためです。rw.d
を入力したら、dtraceを使用して、試験を開始します。その後、2番目のシェル・ウィンドウに移動し、いくつかのコマンドを入力します。この際、コマンドを1つ入力するたびに[Return]キーを押します。入力に応じて、次の例のように、最初のウィンドウにdtraceのプローブ起動レポートが表示されます。
# dtrace -s rw.d dtrace: script 'rw.d' matched 2 probes CPU ID FUNCTION:NAME 1 7 write:entry 1 5 read:entry 0 7 write:entry 0 5 read:entry 0 7 write:entry 0 5 read:entry 0 7 write:entry 0 5 read:entry 0 7 write:entry 1 7 write:entry 1 7 write:entry 1 5 read:entry ...^C
これにより、シェルがread()
およびwrite()
システム・コールを実行することにより、端末ウィンドウから文字を読み取り、結果をエコー・バックしていることがわかります。この例では、これまでに説明した概念の他に、いくつかの新しい概念が含まれています。まず、read()
とwrite()
を同じ方法でインストゥルメントするため、次のように、複数のプローブ記述をカンマで区切った形式の単一のプローブ節が使用されています。
syscall::read:entry, syscall::write:entry
読みやすさを考慮して、各プローブ記述は単独行に分けられています。この配置は厳密には必須ではありませんが、より読みやすいスクリプトを作成するのに役立ちます。次に、シェル・プロセスによって実行されるシステム・コールのみに一致する述語が定義されています。
/pid == 2860/
この述語では、事前定義済のDTrace変数pid
が使用されています。これは常に、対応するプローブを起動したスレッドに関連付けられたプロセスIDに評価されます。DTraceには、プロセスIDのように役立つ変数定義が多数組み込まれています。次の表に、最初のDプログラムを作成するために使用できるDTrace変数のいくつかをリストします。
変数名 | データ型 | 意味 |
---|---|---|
|
|
システム・コールの現在の |
|
|
現在のプロセスの実行可能ファイルの名前 |
|
|
現在のプロセスのプロセスID |
|
|
現在のスレッドのスレッドID |
|
|
現在のプローブ記述のプロバイダ・フィールド |
|
|
現在のプローブ記述のモジュール・フィールド |
|
|
現在のプローブ記述の関数フィールド |
|
|
現在のプローブ記述の名前フィールド |
これで、インストゥルメンテーション・プログラムが作成されたため、プロセスIDおよびインストゥルメントするシステム・コール・プローブを変更して、システム上で実行中の様々なプロセスを試してみてください。その後、もう少し簡単な変更を加えることで、rw.d
をstraceのような非常に単純なシステム・コール・トレース・ツールに変更できます。空のプローブ記述フィールドは、任意のプローブと一致するワイルドカードのように機能します。このため、プログラムを次のような新しいソース・コードに変更することによって、シェルによって実行される任意のシステム・コールをトレースできるようになります。
syscall:::entry /pid == 2860/ { }
シェルにcd、ls、dateなどのコマンドをいくつか入力して、DTraceプログラムからのレポート内容を確認します。
出力フォーマットの例
システム・コールのトレースは、多くのユーザー・プロセスの動作を監視するための強力な手段です。次の例は、出力をフォーマットすることによって前述のrw.d
プログラムを改善したもので、出力が理解しやすくなっています。次のプログラムを入力し、stracerw.d
という名前のファイルに保存してください。
syscall::read:entry, syscall::write:entry /pid == $1/ { printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2); } syscall::read:return, syscall::write:return /pid == $1/ { printf("\tt = %d\n", arg1); }
この例では、述語ごとに定数2860
がラベル$1
に置き換えられています。このラベルを使用すると、希望するプロセスを引数としてスクリプトに指定できます。$1
は、スクリプトのコンパイル時に最初の引数の値に置き換えられます。stracerw.dを実行するには、dtraceのオプション-qと-sを使用し、その後ろにシェルのプロセスIDを最後の引数として入力します。-qオプションは、dtraceを非表示にし、前の例で示したヘッダー行とCPUおよびID列を抑制する必要があることを示します。この結果、明示的にトレースするデータに関する出力のみが表示されます。次のコマンドを入力し(2860
はシェル・プロセスのプロセスIDに置き換えます)、指定したシェルで[Return]キーを何回か押してください。
# dtrace -q -s stracerw.d 2860 t = 1 write(2, 0x7fa621b9b000, 1) t = 1 write(1, 0x7fa621b9c000, 22) t = 22 write(2, 0x7fa621b9b000, 20) t = 20 read(0, 0x7fff60f74b8f, 1) t = 1 write(2, 0x7fa621b9b000, 1) t = 1 write(1, 0x7fa621b9c000, 22) t = 22 write(2, 0x7fa621b9b000, 20) t = 20 read(0, 0x7fff60f74b8f, 1) t = 1 write(2, 0x7fa621b9b000, 1) t = 1 write(1, 0x7fa621b9c000, 22) t = 22 write(2, 0x7fa621b9b000, 20) t = 20 read(0, 0x7fff60f74b8f, 1)^C #
ここで、Dプログラムとその出力をより詳細に調べてみましょう。最初に、前のプログラムよく似た節により、read()
およびwrite()
に対するシェルの各コールがインストゥルメントされます。ただし、この例では、新しい関数であるprintf
を使用して、データをトレースし、特定のフォーマットで出力します。
syscall::read:entry, syscall::write:entry /pid == $1/ { printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2); }
printf
関数は、前に使用したtrace
関数のように、データをトレースする機能と、記述した特定のフォーマットでデータおよび他のテキストを出力する機能を兼ね備えています。printf
からDTraceに対する指示により、最初の引数の後ろにある各引数に関連付けられたデータがトレースされ、フォーマット文字列と呼ばれる最初のprintf
引数によって記述されるルールを使用して、結果がフォーマットされます。
フォーマット文字列は、任意の数のフォーマット変換が含まれる標準文字列です。このフォーマット変換は、先頭に%
が付いており、対応する引数のフォーマット方法を表します。フォーマット文字列内の最初の変換は2番目のprintf
引数に対応しており、2番目の変換は3番目の引数に対応します(以降同様)。変換と変換の間のテキストは一語一句そのまますべて出力されます。%
変換文字に続く文字は、対応する引数に使用するフォーマットを表します。stracerw.d
で使用される3つのフォーマット変換の意味は、次のとおりです。
フォーマット変換 | 説明 |
---|---|
|
対応する値を10進整数として出力します |
|
対応する値を文字列として出力します |
|
対応する値を16進整数として出力します |
DTraceのprintf
は、Cのprintf()
ライブラリ・ルーチンまたはシェルのprintfユーティリティのように機能します。これまでにprintf
を見たことがない場合、フォーマットおよびオプションの詳細は、「出力フォーマット」を参照してください。すでに別の言語のprintf
の知識がある場合も、この章を熟読する必要があります。Dでは、printf
は組み込まれた機能として提供され、DTraceに特化して設計された新しいフォーマット変換がいくつか用意されています。
正しいプログラムの作成をサポートするために、Dコンパイラにより、printf
の各フォーマット文字列が引数リストに基づいて検証されます。前述の節内のprobefunc
を整数123
に変更してみてください。変更したプログラムを実行すると、文字列フォーマット変換%s
は整数引数での使用に適していないことを示すエラー・メッセージが表示されます。
# dtrace -q -s stracerw.d dtrace: failed to compile script stracerw.d: line 5: printf( ) argument #2 is incompatible with conversion #1 prototype: conversion: %s prototype: char [] or string (or use stringof) argument: int #
readまたはwriteシステム・コールの名前とその引数を出力するには、printf
文を使用します。
printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2);
これにより、現在のプローブ関数の名前と、DTrace変数arg0
、arg1
およびarg2
内で使用可能なシステム・コールに対する最初の3つの整数引数をトレースします。プローブ引数の詳細は、「組込み変数」を参照してください。read()
およびwrite()
に対する最初の引数は、10進数で出力されるファイル記述子です。2番目の引数は、16進数値としてフォーマットされたバッファ・アドレスです。最後の引数は、10進数値としてフォーマットされたバッファ・サイズです。3番目の引数に対してフォーマット指定子%4d
を使用することにより、最小のフィールド幅の4文字で%d
フォーマット変換を使用して値を出力する必要があることを示します。この整数の幅が4文字未満である場合、printf
により、出力の位置を揃えるために余分な空白が挿入されます。
システム・コールの結果を出力し、出力の各行を完了するには、次の節を使用します。
syscall::read:return, syscall::write:return /pid == $1/ { printf("\tt = %d\n", arg1); }
syscall
プロバイダにより、entry
以外に、システム・コールごとにreturn
という名前のプローブも公開されています。syscallのreturn
プローブのDTrace変数arg1
は、システム・コールの戻り値に評価されます。この戻り値は、10進整数としてフォーマットされます。フォーマット文字列内でバックスラッシュで始まる文字シーケンスは、タブ(\t
)および改行(\n
)にそれぞれ拡張されます。これらのエスケープ・シーケンスは、入力が困難な文字列を出力または記録する際に役立ちます。Dでは、C、C++およびJavaプログラミング言語と同じ一連のエスケープ・シーケンスをサポートしています。エスケープ・シーケンスの完全なリストは、「定数」を参照してください。
配列の概要
Dは整数型の変数だけでなく、構造体や共用体と呼ばれる、文字列型や複合型を表す他の型も定義できます。Cプログラミングをよく理解している場合、Dでは、Cで使用できるすべての型を使用できます。Cをよく理解していなくても問題ありません。「型、演算子および式」で、各種データ型についてすべて説明します。
Dは配列もサポートします。Cプログラマにおなじみの直線的に索引付けされたスカラー配列の詳細は、「配列宣言および記憶域」を参照してください。
より強力で一般的に使用される連想配列は、タプルで索引付けされます。各連想配列には、特定の型署名があります。つまり、タプルはすべて同じ数の要素を持ち、それらの要素は一貫した型で同じ順序であり、その値はすべて同じ型です。Dの連想配列の詳細は、「連想配列」を参照してください。
連想配列の例
たとえば、次のD文は、要素を456に設定してから457に増分して、連想配列にアクセスします。この連想配列のすべての値はint
型である必要があり、そのすべてのタプルにはstring,int
の署名が必要です。
a["hello", 123] = 456; a["hello", 123]++;
では、Dプログラム内で連想配列を使用してみましょう。次のプログラムを入力し、rwtime.d
という名前のファイルに保存してください。
syscall::read:entry, syscall::write:entry /pid == $1/ { ts[probefunc] = timestamp; } syscall::read:return, syscall::write:return /pid == $1 && ts[probefunc] != 0/ { printf("%d nsecs", timestamp - ts[probefunc]); }
rwtime.dの実行時には、stracerw.dのときと同じように、シェル・プロセスのIDを指定します。シェル・コマンドをいくつか入力すると、各システム・コールの経過時間が表示されます。次のコマンドを入力し、別のシェルで[Return]キーを何回か押してください。
# dtrace -s rwtime.d `/usr/bin/pgrep -n bash` dtrace: script 'rwtime.d' matched 4 probes CPU ID FUNCTION:NAME 0 8 write:return 51962 nsecs 0 8 write:return 45257 nsecs 0 8 write:return 40787 nsecs 1 6 read:return 925959305 nsecs 1 8 write:return 46934 nsecs 1 8 write:return 41626 nsecs 1 8 write:return 176839 nsecs ... ^C #
各システム・コールの経過時間をトレースするには、read()
とwrite()
の開始時および終了時を両方ともインストゥルメントし、各ポイントで時間を計測する必要があります。次に、特定のシステム・コールの終了時に、最初のタイムスタンプと2番目のタイムスタンプの差異を計算する必要があります。システム・コールごとに別々の変数を使用することもできますが、そうすると新たにシステム・コールを追加してプログラムを拡張するのが難しくなります。そのため、プローブ関数名で索引付けられた連想配列を使用する方が簡単です。最初のプローブ節は、次のとおりです。
syscall::read:entry, syscall::write:entry /pid == $1/ { ts[probefunc] = timestamp; }
この節では、ts
という名前の配列が定義され、適切なメンバーにDTrace変数timestamp
の値が割り当てられています。この変数は、常に増加するナノ秒カウンタの値を返します。entryのタイムスタンプが保存されたら、対応するreturnプローブにより、再度timestamp
が確認され、現在の時間と保存された値の差異がレポートされます。
syscall::read:return, syscall::write:return /pid == $1 && ts[probefunc] != 0/ { printf("%d nsecs", timestamp - ts[probefunc]); }
returnプローブの述語は、DTraceがトレースしているプロセスが適切であることと、対応するentry
プローブがすでに起動していて、ts[probefunc]
にゼロ以外の値が割り当てられていることを要求します。この方法により、DTraceが初めて起動したときの無効な出力を排除できます。dtraceの実行時に、すでにシェルがread()
システム・コール内で入力を待機している場合、最初のread()
のread:entry
を処理せずに、read:return
プローブが起動します。このとき、ts[probefunc]
はまだ割り当てられていないため、値はゼロになります。
外部シンボルおよび型
DTraceのインストゥルメンテーションは、Oracle Linuxオペレーティング・システム・カーネル内で実行されます。そのため、特別なDTrace変数およびプローブ引数にアクセスする以外に、カーネル・データ構造体、シンボルおよび型にもアクセスできます。DTraceのユーザー、管理者、サービス担当者およびドライバ開発者は、これらの機能を利用して、オペレーティング・システム・カーネルやデバイス・ドライバの低レベルの動作を調査できます。Oracle Linuxオペレーティング・システムの内部構造を学習するには、このガイドの冒頭で紹介した関連書籍を参照してください。
Dでは、オペレーティング・システムには定義されているがDプログラム内では定義されていないシンボルにアクセスする際、特別なスコープ演算子として逆引用符(`
)を使用します。たとえば、Oracle Linuxカーネルには、max_pfn
という名前のシステム変数のC宣言が含まれています。この変数は、次のように、カーネル・ソース・コード内にCで宣言されます。
unsigned long max_pfn
この変数の値をDプログラム内でトレースするには、次のようなD文を作成します。
trace(`max_pfn);
DTraceでは、カーネル・シンボルに、対応するオペレーティング・システムのCコード内のシンボルで使用されている型が関連付けられます。このため、ネイティブのオペレーティング・システムのデータ構造にソースから簡単にアクセスできます。
オペレーティング・システムの外部変数を使用するには、対応するオペレーティング・システムのソース・コードにアクセスする必要があります。
カーネル・シンボル名は、D変数および関数識別子とは別の名前空間に格納されます。したがって、カーネル・シンボル名とD変数名が競合することはありません。変数の直前に逆引用符を付けると、一致する変数定義を見つけるために、既知のカーネル・シンボルが検索され、ロードされたモジュールのリストが使用されます。Oracle Linuxカーネルでは、個別のシンボルの名前空間を使用して動的にロードされたモジュールがサポートされるため、有効なオペレーティング・システム・カーネル内で同じ変数名を複数回使用できます。これらの名前の競合を解消するには、アクセスする変数を含むカーネル・モジュール名をシンボル名の逆引用符より前に指定します。たとえば、次のようにして、foo
という名前のカーネル・モジュールによって提供される_bar
関数のアドレスを参照します。
foo`_bar
外部変数には、オペランド型の通常ルールに従い、値を変更するものを除いた任意のD演算子を適用できます。必要に応じて、アクティブなカーネル・モジュールに対応する変数名がロードされるため、これらの変数を宣言する必要はありません。外部変数には、値を変更するような演算子(=
や+=
など)は適用できません。DTraceでは、安全上の理由により、監視対象のソフトウェアの状態を破損することは禁じられています。
Dプログラムから外部変数へアクセスすることは、オペレーティング・システム・カーネルやそのデバイス・ドライバなどの別のプログラム内部実装の詳細へのアクセスを意味します。これらの実装の詳細では、信頼できる安定したインタフェースは提供されません。これらの詳細に依存して作成するDプログラムは、対応するソフトウェアの一部をアップグレードする際に動作しなくなる可能性があります。このため、外部変数は通常、DTraceを使用してパフォーマンスや機能上の問題をデバッグするために使用されます。Dプログラムの安定性の詳細は、「DTraceの安定性機能」を参照してください。
これまでに、DTraceの概要を把握し、より規模が大きく複雑なDプログラムを作成するために必要なDTraceの構成要素の多くについて学習しました。この章の残りの部分では、Dのルールの全容を明らかにし、DTraceを使用して複雑なパフォーマンス測定やシステムの機能分析を単純化する方法を実演していきます。その後、DTraceを使用してユーザー・アプリケーションの動作とシステムの動作を関連付け、ソフトウェア・スタック全体を分析する方法についても学習します。
型、演算子および式
Dでは、様々なデータ・オブジェクトにアクセスし、操作できます。具体的には、変数やデータ構造の作成または変更、オペレーティング・システム・カーネルやユーザー・プロセスに定義されているデータ・オブジェクトへのアクセス、整数定数、浮動小数点定数、文字列定数の宣言などを実行できます。Dには、オブジェクトの操作や複合式の作成に使用されるANSI C演算子のスーパーセットが用意されています。この項では、一連の型、演算子および式のルールの詳細を説明します。
識別子名およびキーワード
D識別子名は、アルファベットの大文字と小文字、数字、および下線で構成されます。先頭文字はアルファベットまたは下線である必要があります。先頭文字が下線(_
)の識別子名はすべて、Dシステム・ライブラリ用に予約されています。これらの名前はDプログラム内で使用しないようにしてください。慣例により、Dプログラムでは、通常、変数名にはアルファベットの大/小文字、定数名にはすべて大文字を使用します。
D言語のキーワードは、プログラミング言語の構文自体で使用するために予約されている特別な識別子です。これらの名前は常にアルファベットの小文字のみで指定し、D変数名には使用しないでください。次の表に、D言語で使用するために予約されているキーワードをリストします。
表2-2 Dのキーワード
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dでは、ANSI Cキーワードのスーパーセットがキーワードとして予約されています。将来D言語で使用するために予約されているキーワードは、*
でマークされています。将来使用するために予約されているキーワードを使用しようとすると、Dコンパイラから構文エラーが生成されます。Dでは定義されているがANSI Cでは定義されていないキーワードは、+
でマークされています。Dには、ANSI Cの型および演算子の完全セットが用意されています。Dプログラミングの主な違いは、制御フロー構造体が存在しない点です。ANSI Cで制御フローに関連付けられたキーワードは、Dでは将来使用するために予約されています。
データ型およびサイズ
Dには、整数および浮動小数点の定数の基本データ型があります。算術は、Dプログラム内の整数に対してのみ実行可能です。Dでは、データ構造を初期化するために浮動小数点定数を使用できますが、浮動小数点算術の使用は許可されていません。Oracle Linuxでは、Dには、プログラムの作成に使用する64ビットのデータ・モデルが用意されています。ただし、32ビットのデータ・モデルはサポートされていません。プログラムの実行時に使用されるデータ・モデルは、アクティブなオペレーティング・システム・カーネルに関連付けられたネイティブ・データ・モデルで、これも64ビットである必要があります。
次の表に、64ビットのデータ・モデルの整数型名とサイズを示します。整数は常に、システムのネイティブ・バイト・エンコーディングの順序で2の補数として表されます。
表2-3 Dの整数データ型
型名 | 64ビットのサイズ |
---|---|
|
1バイト |
|
2バイト |
|
4バイト |
|
8バイト |
|
8バイト |
整数型の前には、signedまたはunsigned修飾子が付けられます。符号修飾子が存在しない場合、その型は符号付きであるとみなされます。Dコンパイラでは、次の表に示す型の別名も用意されています。
表2-4 Dの整数型の別名
型名 | 説明 |
---|---|
|
1バイトの符号付き整数 |
|
2バイトの符号付き整数 |
|
4バイトの符号付き整数 |
|
8バイトの符号付き整数 |
|
ポインタと同じサイズの符号付き整数 |
|
1バイトの符号なし整数 |
|
2バイトの符号なし整数 |
|
4バイトの符号なし整数 |
|
8バイトの符号なし整数 |
|
ポインタと同じサイズの符号なし整数 |
これらの型の別名は、前の表にリストされた対応する基本型の名前の使用と同義であり、データ・モデルごとに適切に定義されています。たとえば、uint8_t
の型名は、符号なしchar
型の別名です。Dプログラム内で使用する型の別名を独自に定義する方法の詳細は、「型と定数の定義」を参照してください。
ノート:
事前定義された型の別名は、プリプロセッサに含まれるファイルでは使用できません。
Dの浮動小数点型は、ANSI Cの宣言および型と互換性があります。Dでは、浮動小数点演算子はサポートされていませんが、printf
関数を使用して、浮動小数点型データ・オブジェクトのトレースおよびフォーマットを行うことができます。次の表に示す浮動小数点型を使用できます。
表2-5 Dの浮動小数点データ型
型名 | 64ビットのサイズ |
---|---|
|
4バイト |
|
8バイト |
|
16バイト |
Dには、ASCII文字列を表す特殊なstring
型も用意されています。文字列の詳細は、「文字列のDTraceサポート」を参照してください。
定数
整数定数は、10進法(12345
)、8進法(012345
)または16進法(0x12345
)の形式で記述できます。8進法(base 8)の定数には、接頭辞として0を付ける必要があります。16進法(base 16)の定数には、接頭辞として0x
または0X
を付ける必要があります。整数定数には、その値を表す型として、int
、long
およびlong long
のうち最も小さい型が割り当てられます。値が負である場合、型の符号付きバージョンが使用されます。値が正で、符号付きの型で表すには値が大きすぎる場合は、符号なしの型が使用されます。Dの型であることを明示的に指定するには、任意の整数定数に、次の表にリストされた接尾辞のいずれかを適用します。
接尾辞 | Dの型 |
---|---|
|
コンパイラによって選択された型の |
|
|
|
|
|
|
|
|
浮動小数点定数は常に10進形式で作成します。さらに、小数点(12.345
)か指数(123e45
)、またはその両方(123.34e-5
)を含める必要があります。浮動小数点定数には、デフォルトでdouble
型が割り当てられます。Dの型であることを明示的に指定するには、任意の浮動小数点定数に、次の表にリストされた接尾辞のいずれかを適用します。
接尾辞 | Dの型 |
---|---|
|
|
|
|
文字定数は単一の文字か、単一引用符で囲んだエスケープ・シーケンス('a'
)として作成します。文字定数には、char
ではなくint
型が割り当てられます。文字定数は、ASCII文字セット内のその文字の値によって値が決定される整数定数と同等です。文字とその値のリストについては、ascii(7)
のマニュアル・ページを参照してください。文字定数には、次の表に示す特殊なエスケープ・シーケンスを使用することもできます。Dでは、ANSI Cと同じエスケープ・シーケンスがサポートされています。
表2-6 文字エスケープ・シーケンス
エスケープ・シーケンス | 意味 | エスケープ・シーケンス | 意味 |
---|---|---|---|
|
アラート |
|
バックスラッシュ |
|
バックスペース |
|
疑問符 |
|
改ページ |
|
単一引用符 |
|
改行 |
|
二重引用符 |
|
キャリッジ・リターン |
|
8進数値0oo |
|
水平タブ |
|
16進数値0xhh |
|
垂直タブ |
|
NULL文字 |
複数の文字指定子を1組の一重引用符で囲むことにより、個々のバイトが対応する文字指定子に応じて初期化された整数を作成できます。これらのバイトは、文字定数の左から右の順に読み取られ、使用しているオペレーティング環境のネイティブのエンディアンに対応する順序で、生成された整数に割り当てられます。1つの文字定数に、最大8個の文字指定子を含めることができます。
文字列定数は、二重引用符で囲むことにより、任意の長さで作成できます("hello"
)。文字列定数には、リテラルの改行文字を含めることはできません。改行が含まれる文字を作成するには、リテラルの改行文字を入力するかわりに、\n
というエスケープ・シーケンスを使用します。文字列定数には、前述の文字定数で示される特殊文字のエスケープ・シーケンスを含めることができます。ANSI Cの場合と同じく、文字列は、NULL文字(\0
)で終わる文字の配列として表されます。このNULL文字は、宣言した文字列定数ごとに暗黙的に追加されます。文字列定数には、Dの特殊なstring
型が割り当てられます。Dコンパイラには、文字列として宣言された文字配列の比較とトレースを行う特別な機能セットが用意されています。詳細は、「文字列のDTraceサポート」を参照してください。
算術演算子
Dには、プログラムで使用するために、次の表で説明するバイナリ算術演算子が用意されています。これらの演算子が整数に対して持つ意味はすべて、ANSI Cの場合と同じです。
表2-7 バイナリ算術演算子
演算子 | 説明 |
---|---|
|
整数の加算 |
|
整数の減算 |
|
整数の乗算 |
|
整数の除算 |
|
整数の剰余演算 |
Dの算術は、整数オペランドまたはポインタに対してのみ実行できます。「ポインタおよびスカラー配列」を参照してください。Dプログラムの浮動小数点オペランドに対して算術演算を実行することはできません。DTrace実行環境では、整数のオーバーフローまたはアンダーフローに対して何の処理も行われません。オーバーフローおよびアンダーフローが発生した場合は、これらの条件について特に確認する必要があります。
ただし、DTrace実行環境では、/
および%
演算子の誤用によるゼロ除算エラーが自動的に確認され、レポートされます。Dプログラムで無効な除算操作が実行されると、その影響を受けるインストゥルメンテーションが自動的に無効化され、エラーがレポートされます。DTraceによって検出されるエラーは、他のDTraceユーザーやオペレーティング・システム・カーネルには影響しません。したがって、Dプログラムにこれらのエラーのいずれかが誤って含まれている場合に損害が発生することを懸念する必要はありません。
これらのバイナリ演算子の他に、+
および-
演算子を単項演算子として使用することもできます。これらの演算子は、どのバイナリ算術演算子よりも優先されます。すべてのD演算子の優先順位と結合プロパティの詳細は、表2-12に記載されています。優先順位を制御するには、式を丸カッコ(()
)で囲んでグループ化します。
関係演算子
Dには、プログラムで使用するために、次の表で説明するバイナリ関係演算子が用意されています。これらの演算子が持つ意味はすべて、ANSI Cの場合と同じです。
表2-8 Dの関係演算子
演算子 | 説明 |
---|---|
|
左のオペランドは右のオペランドより小さい |
|
左のオペランドは右のオペランド以下 |
|
左のオペランドは右のオペランドより大きい |
|
左のオペランドは右のオペランド以上 |
|
左のオペランドは右のオペランドと等しい |
|
左のオペランドは右のオペランドと等しくない |
関係演算子は、Dの述語の作成によく使用されます。各演算子は、int
型の値(条件がtrue
の場合は1、false
の場合は0)に評価されます。
関係演算子は、整数、ポインタまたは文字列のペアに適用できます。ポインタ同士を比較した場合、2つのポインタの整数を符号なし整数とみなして比較した場合と同じ結果になります。文字列同士を比較した場合、結果は、2つのオペランドに対してstrcmp()
を実行した場合と同じになります。次の表は、Dの文字列比較とその結果の例を示しています。
Dの文字列比較 | 結果 |
---|---|
|
1 ( |
|
1 ( |
|
0 ( |
関係演算子は、列挙型に関連付けられたデータ・オブジェクトと、列挙によって定義された列挙子タグとの比較に使用することもできます。列挙は、名前付きの整数定数を作成するための機能です。詳細は、「型および定数の定義」を参照してください。
論理演算子
Dには、プログラムで使用するために、次の表に示すバイナリ論理演算子が用意されています。最初の2つの演算子は、対応するANSI C演算子と同じものです。
表2-9 Dの論理演算子
演算子 | 説明 |
---|---|
|
論理演算子 |
|
論理演算子 |
|
論理演算子 |
論理演算子は、Dの述語の作成によく使用されます。論理演算子AND
は、左のオペランドが偽の場合、右の式は評価されないという、短絡評価を行います。論理演算子OR
も、左のオペランドが真の場合、右の式は評価されないという、短絡評価を行います。論理演算子XOR
は、短絡を行いません。両方の式のオペランドが常に評価されます。
バイナリ論理演算子の他に、単項演算子!
を使用して、単一のオペランドの論理否定を実行することもできます。つまり、0オペランドを1に、0以外のオペランドを0に変換します。慣例上、Dプログラムでは、ブール値を表す整数を操作するときは!
、ブール型以外の整数を操作するときは== 0
を使用しますが、これらの式は同等です。
論理演算子は、整数型またはポインタ型のオペランドに適用できます。論理演算子は、ポインタ・オペランドを符号なしの整数値とみなします。Dのすべての論理演算子および関係演算子の場合と同じく、0以外の整数値を持つオペランドは真、整数値0を持つオペランドは偽です。
ビット演算子
Dには、整数オペランド内の個々のビットを操作するために、次の表に示す次のバイナリ演算子が用意されています。これらの演算子が持つ意味はすべて、ANSI Cの場合と同じです。
表2-10 Dのビット演算子
演算子 | 説明 |
---|---|
|
ビット単位の |
|
ビット単位の |
|
ビット単位の |
|
右のオペランドに指定されたビット数だけ左のオペランドを左にシフトする |
|
右のオペランドに指定されたビット数だけ左のオペランドを右にシフトする |
バイナリ演算子&
を使用すると、整数オペランドからビットをクリアできます。バイナリ演算子|
を使用すると、整数オペランドにビットを設定できます。バイナリ演算子^
は、対応するオペランド・ビットの1つが設定された厳密なビット位置ごとに1を返します。
シフト演算子を使用すると、指定した整数オペランド内のビットを左右に移動できます。左へシフトすると、結果の右側にある空のビット位置にゼロが入力されます。符号なし整数オペランドを使用して右へシフトすると、結果の左側の空のビット位置にゼロが入力されます。符号付き整数オペランドを使用して右へシフトすると、左側の空のビット位置に符号ビットの値が入力されます。この操作は、算術シフト演算とも呼ばれます。
ビット数が負の値である場合や、左側のオペランド自体のビット数より大きい場合、整数値をシフトすると、未定義の結果が得られます。Dプログラムのコンパイル時にこの条件が検出されると、エラー・メッセージが表示されます。
バイナリ論理演算子の他に、単項演算子を使用して、単一のオペランドのビット単位の否定を実行することもできます。つまり、オペランド内の各0ビットを1ビットに、オペランド内の各1ビットを0ビットに変換します。
代入演算子
Dには、D変数を変更するために、次の表に示すバイナリ代入演算子が用意されています。変更できるのは、Dの変数と配列のみです。Dの代入演算子を使用してカーネル・データ・オブジェクトおよび定数は変更することはできません。代入演算子が持つ意味は、ANSI Cの場合と同じです。
表2-11 Dの代入演算子
演算子 | 説明 |
---|---|
|
左のオペランドを右の式の値と等しくする |
|
左のオペランドを右の式の値の分だけ増分する |
|
左のオペランドを右の式の値の分だけ減分する |
|
左のオペランドに右の式の値を掛ける |
|
左のオペランドを右の式の値で割る |
|
左のオペランドを右の式の値で割ったときの剰余を求める |
|
左のオペランドと右の式の値のORをビット単位で計算する |
|
左のオペランドと右の式の値のANDをビット単位で計算する |
|
左のオペランドと右の式の値のXORをビット単位で計算する |
|
右の式の値に指定されたビット数だけ左のオペランドを左にシフトする |
|
右の式の値に指定されたビット数だけ左のオペランドを右にシフトする |
=
以外の代入演算子は、以前に説明した他の演算子のいずれかと=
演算子を組み合せて使用するための省略形として用意されています。たとえば、x = x + 1
という式は、x
の値が1回評価される点を除いて、x += 1
という式と同じです。これらの代入演算子は、以前に説明したバイナリ形式のオペランド型と同じルールに準拠します。
代入演算子の結果は、左側の式の新しい値と同等の式になります。代入演算子やこれまでに説明した他の演算子を組み合せて使用すると、任意の複合式を表現できます。丸カッコ()
を使用すると、複合式の項をグループ化できます。
インクリメント演算子およびデクリメント演算子
++
および--
が用意されています。これらの演算子が持つ意味は、ANSI Cの場合と同じです。これらの演算子は変数にのみ適用でき、変数名の直前または直後に付加します。演算子が変数名の直前にある場合、まず変数が変更され、結果の式は新しい変数の値と等しくなります。たとえば、次の2つのコード部分の結果は同じです。
x += 1; y = x; y = ++x;
y = x; x -= 1; y = x--;
インクリメント演算子とデクリメント演算子を使用すると、変数を宣言せずに新しい変数を作成できます。変数宣言を省略して、変数にインクリメント演算子またはデクリメント演算子を適用すると、この変数の型は暗黙的にint64_t
として宣言されます。
インクリメント演算子とデクリメント演算子は、整数変数またはポインタ変数に適用できます。これらの演算子を整数変数に適用すると、対応する値が1ずつ増分または減分されます。これらの演算子をポインタ変数に適用すると、ポインタが参照しているデータ型のサイズの分だけポインタ・アドレスが増分または減分されます。Dのポインタおよびポインタ演算の詳細は、「ポインタおよびスカラー配列」を参照してください。
条件式
Dでは、if-then-else
構成はサポートされていませんが、?
および:
演算子を使用した単純な条件式がサポートされています。これらの演算子を使用すると、3つ組の式を関連付けることができます。この場合、最初の式を使用して、残り2つの式のいずれかを条件付きで評価します。
たとえば、次のD文を使用して、i
の値に応じて2つの文字列のいずれかに変数x
を設定できます。
x = i == 0 ? "zero" : "non-zero";
前述の例では、最初に式i == 0
が評価され、真であるか偽であるかが決定されます。この式が真である場合、2番目の式が評価され、その値が返されます。この式が偽である場合、3番目の式が評価され、その値が返されます。
他のD演算子の場合と同様に、複数の?:
演算子を1つの式で使用することで、より複雑な式を作成できます。たとえば、次の式は、0-9
、a-f
またはA-F
のいずれかの文字を含むchar
変数c
をとり、この文字を数字とみなした場合の値を16進数(base 16)の整数として返します。
hexval = (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c + 10 - 'a' : c + 10 - 'A';
真の値と評価されるために、?:
が使用されている最初の式が、ポインタまたは整数である必要があります。2番目と3番目の式は、互換性のある型であれば何でもかまいません。ただし、一方が文字列、もう一方が整数を返すような条件式は構成できません。また、2番目と3番目の式では、trace
やprintf
などのトレース関数を呼び出すこともできません。条件付きでデータをトレースする場合は、かわりに述語を使用します。詳細は、「述語の例」を参照してください。
型変換
式を構成する際に使用するオペランドの型が違っていても互換性がある場合、型変換が実行されて、結果として生じる式の型が決まります。Dの型変換のルールは、ANSI Cの整数の算術変換ルールと同じです。多くの場合、これらのルールは通常の算術変換と呼ばれます。
変換ルールについて簡単に説明すると次のようになります。整数型はそれぞれchar
、short
、int
、long
、long long
の順にランク付けされます。符号なし型は、対応する符号付き型より上位にランク付けされますが、1つ上位の整数型よりは下位です。2つの整数オペランドを使用してx + y
のような式を構成するときに、これらのオペランドの型が異なる場合、ランクが高い方のオペランド型が結果の型として使用されます。
変換が必要な場合は、ランクが低い方のオペランドが最初に、ランクの高い方の型に昇格されます。昇格によってオペランドの値が実際に変更されるわけではありません。符号に従って値がより大きいコンテナに拡張されるのみです。符号なしオペランドが昇格される場合、結果として生じる整数の未使用の上位ビットにゼロが入力されます。符号付きオペランドが昇格される場合、符号の拡張を実行することによって未使用の上位ビットが入力されます。符号付き型を符号なし型に変換した場合、最初に符号付き型が拡張され、その後、変換によって決定された新しい符号なし型が割り当てられます。
整数などの型は、別の型に明示的にキャストすることもできます。Dでは、ポインタと整数は、任意の整数型またはポインタ型にキャストできます。ただし、他の型にキャストすることはできません。文字列や文字配列のキャストおよび昇格のルールについては、「文字列のDTraceサポート」を参照してください。
整数またはポインタをキャストする場合、次のような式を使用します。
y = (int)x;
この例では、最終的な型を丸カッコで囲み、元の式の接頭辞として使用します。整数をよりランクの高い型にキャストするには、昇格を実行します。整数をよりランクの低い型にキャストするには、不要な整数の上位ビットをゼロにします。
Dでは、浮動小数点の算術演算は許可されていません。このため、浮動小数点オペランドの変換やキャストは許可されておらず、また、暗黙的な浮動小数点変換のルールも定義されていません。
演算子の優先順位
表2-12に、D言語の演算子の優先順位および結合性のルールをリストします。これらのルールは少し複雑ですが、ANSI Cの演算子の優先順位ルールとの完全な互換性を実現するためには欠かせないものです。次の表の次の項目は、上から優先順位の高い順になっています。
表2-12 D演算子の優先順位および結合性
演算子 | 結合性 |
---|---|
|
左から右 |
|
右から左 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
左から右 |
|
右から左 |
|
右から左 |
|
左から右 |
前の表の中には、まだ説明していない演算子も含まれています。これらの演算子については、以降の章で説明します。次の表に、D言語で提供されるその他の演算子のいくつかをリストします。
演算子 | 説明 | 詳細情報 |
---|---|---|
|
オブジェクトのサイズを計算します。 |
|
|
型のメンバーのオフセットを計算します。 |
|
|
オペランドを文字列に変換します。 |
|
|
データ型を変換します。 |
|
単項の |
オブジェクトのアドレスを計算します。 |
|
単項の |
オブジェクトへのポインタを間接参照します。 |
|
|
構造体または共用体の型のメンバーにアクセスします。 |
表にリストされているカンマ(,
)演算子は、ANSI Cのカンマ演算子との互換性用です。一連の式を左から右の順に評価し、右端の式の値を返すために使用できます。この演算子はCとの互換性を厳密に実現するためのものであり、通常は使用しません。
演算子の優先順位の表にリストされた()
は、関数コールを表します。printf
やtrace
などの関数のコールの例は、「出力フォーマット」を参照してください。Dでは、カンマを使用して、関数の引数を列挙したり、連想配列キーのリストを作成することもできます。このカンマはカンマ演算子とは別のもので、左から右の順で評価を行うとはかぎりません。Dコンパイラが関数の引数を評価する順番や、連想配列のキーを評価する順番は、特に決まっていません。このため、式i
とi++
を組み合せる場合など、複数の式を使用する場合は、相互の副作用に注意してください。
演算子の優先順位の表にリストされた[]
は、配列または連想配列の参照を表します。連想配列の例は、「連想配列」に記載されています。集積体と呼ばれる特殊な連想配列については、「集積体」で説明されています。[]
演算子を使用して、固定サイズのC配列に索引を付けることもできます。「ポインタおよびスカラー配列」を参照してください。
変数
Dには、トレース・プログラムで使用する基本型の変数としてスカラー変数と連想配列の2種類が用意されています。集積体は特殊な種類の配列変数です。集積の詳細は、「集積体」を参照してください。
変数のスコープを理解するために、次の図を考えてみます。
変数のスコープ
![この図は、グローバル、スレッド・ローカルおよび節ローカルのスコープを示しています。 この図は、グローバル、スレッド・ローカルおよび節ローカルのスコープを示しています。](img/scopevar.png)
この図では、システムの実行が図示され、水平軸に沿って経過時間、垂直軸に沿ってスレッド番号が示されます。Dプローブは、異なるスレッドで異なる時間に起動し、プローブが起動するたびにDスクリプトが実行されます。D変数には、次の表で説明するスコープのいずれかが含まれます。
スコープ | 構文 | 初期値 | スレッド・セーフ対応 | 説明 |
---|---|---|---|---|
グローバル |
|
0 |
いいえ |
いずれかのスレッドで起動するプローブは、変数の同じインスタンスにアクセスします。 |
スレッド・ローカル |
|
0 |
はい |
スレッドで起動するプローブは、変数のスレッド固有のインスタンスにアクセスします。 |
節ローカル |
|
定義なし |
はい |
起動するプローブは、プローブのその特定の起動に固有の変数のインスタンスにアクセスします。 |
ノート:
次の追加情報に注意してください。
-
スカラー変数と連想配列はスコープがグローバルであるため、マルチプロセッサ・セーフ(MPセーフ)ではありません。このような変数の値は複数のプロセッサによる変更が可能であるため、複数のプローブによって変更されると変数が破損する可能性があります。
-
集積体は、グローバル・スコープであってもMPセーフです。これは、最終的な集積体でグローバル結果が生成される前に、独立したコピーがローカルで更新されるためです。
スカラー変数
スカラー変数を使用して、整数やポインタなど固定サイズのデータ・オブジェクトを個別に表現します。スカラー変数は、1つ以上のプリミティブ型や複合型で構成された固定サイズのオブジェクトに使用することもできます。Dには、オブジェクトの配列や複合構造体を作成する機能があります。DTraceでは、事前定義された最大長まで拡張することによって文字列を固定サイズのスカラーとして表現することもできます。Dプログラム内の文字列長の制御の詳細は、「文字列のDTraceサポート」を参照してください。
スカラー変数は、Dプログラム内の未定義の識別子に初めて値を割り当てるときに、自動的に作成されます。たとえば、int
型のx
という名前のスカラー変数を作成するには、次のように、任意のプローブ節内でint
型の値を割り当てるのみです。
BEGIN { x = 123; }
このように作成されたスカラー変数はグローバル変数です。それぞれ一度定義し、Dプログラムのすべての節で参照可能です。x
識別子を参照するたびに、この変数に関連付けられた単一の記憶域を参照します。
ANSI Cとは異なり、Dでは、明示的変数宣言は必要ありません。グローバル変数を使用する前に、グローバル変数を宣言して、その名前と型を明示的に割り当てる場合は、次の例のように、プログラム内のプローブ節の外側に宣言を配置します。
int x; /* declare an integer x for later use */ BEGIN { x = 123; ... }
ほとんどのDプログラムでは明示的変数宣言は必要ありませんが、明示的変数宣言が便利な場合もあります。たとえば、変数の型を慎重に制御する場合や、プログラムの冒頭に一連の宣言とコメントを配置して、プログラムの変数やその意味を記述する場合などです。
ANSI Cとは異なり、Dの変数宣言では、初期値を割り当てることができません。初期値を割り当てるには、BEGIN
プローブ節を使用する必要があります。すべてのグローバル変数の記憶域は、最初にこの変数を参照する前に、DTraceによってゼロが入力されます。
D言語定義では、D変数のサイズと数は制限されません。制限は、DTraceの実装、およびシステムで使用可能なメモリーによって定義されます。プログラムのコンパイル時に適用可能な制限は、Dコンパイラによって適用されます。プログラムの制限に関連するオプションのチューニング方法の詳細は、「オプションおよびチューニング可能パラメータ」を参照してください。
連想配列
連想配列を使用して、キーと呼ばれる名前を指定することによって取得できるデータ要素の集合を表現します。Dの連想配列キーは、タプルと呼ばれるスカラー式の値のリストで構成されます。配列のタプルは、配列の参照時に対応する配列値を取得するためにコールされる関数の架空のパラメータ・リストのようなものです。Dの各連想配列には、それぞれ特定の型の決まった一定数のタプル要素で構成される、固定のキー署名が割り当てられます。Dプログラムでは、配列ごとに異なるキー署名を定義できます。
連想配列は、通常の固定サイズの配列とは異なり、要素数が事前に制限されていません。単に整数をキーとして使用するのではなく、任意のタプルで要素に索引を付けることができます。また、要素は、事前に割り当てられた連続した記憶域に格納されるわけではありません。連想配列は、C、C++またはJava言語プログラム内でハッシュ表や他の単純な辞書データ構造を使用する場合に便利です。連想配列では、より複雑な制御フローを作成するために使用できる、Dプログラム内で捕捉されたイベントや状態の動的な履歴を作成できます。
連想配列を定義するには、次のような形式の代入式を作成します。
name [ key ] = expression ;
nameは任意の有効なD識別子であり、keyは1つ以上の式をカンマで区切ったリストです。
たとえば、次の式は、キー署名[ int, string
]を持つ連想配列aを定義し、[123, "hello"]
というタプルで指定した場所に整数値456
を格納しています。
a[123, "hello"] = 456;
配列に含まれる各オブジェクトの型は、その配列内のすべての要素に対しても固定されます。最初に整数456
が割り当てられているため、その後この配列に格納される値もすべてint
型になります。「型、演算子および式」に定義されている代入演算子を使用すると、演算子ごとに定義されたオペランド・ルールに応じて、連想配列の要素を変更できます。互換性のない割当てを実行しようとすると、該当するエラー・メッセージが返されます。連想配列のキーや値には、スカラー変数で使用できる任意の型を使用できます。
連想配列は、配列キー署名と互換性のある任意のタプルを使用して参照できます。タプル互換性のルールは、関数コールおよび変数代入のルールに似ています。つまり、タプルの長さが同じで、実際のパラメータのリスト内のそれぞれの型である必要があり、正式なキー署名内の対応する型と互換性がある必要があります。たとえば、連想配列x
を次のように定義するとします。
x[123ull] = 0;
キー署名はunsigned long long
型、値はint
型になります。この配列は、式x['a']
を使用して参照することもできます。これは、算術変換ルールに応じて、長さが1でint
型の文字定数'a'
で構成されたタプルと、unsigned long long
型のキー署名に互換性があるためです。これらのルールは、「型変換」で説明されています。
Dの連想配列を明示的に宣言してから使用する必要がある場合は、次のように、プログラム・ソース・コードのプローブ節の外側で配列名とキー署名の宣言を作成できます。
int x[unsigned long long, char]; BEGIN { x[123ull, 'a'] = 456; }
記憶域は、ゼロ以外の値を持つ配列要素に対してのみ割り当てられます。
ノート:
連想配列が定義されたら、互換性のあるキー署名を持つタプルがすべて参照可能になります。以前に割り当てられていないタプルでも参照可能です。割り当てられていない連想配列要素にアクセスすると、定義により、ゼロが入力されたオブジェクトが返されます。この定義により、連想配列要素の基礎となる記憶域は、この要素にゼロ以外の値が割り当てられるまでは割り当てられません。反対に、連想配列要素にゼロを割り当てると、基礎となる記憶域の割当てが解除されます。
この動作は重要です。これは、連想配列要素の割当て元の動的な変数領域には限りがあるためです。割り当てようとしたときに領域が足りない場合、割当ては失敗し、動的な変数の欠落を示すエラー・メッセージが表示されます。使用していない連想配列要素には、常にゼロを割り当ててください。動的な変数の欠落を解消するために使用できる技術の詳細は、「オプションおよびチューニング可能パラメータ」を参照してください。
スレッド・ローカル変数
DTraceには、この章で前に説明したグローバル変数とは対照的に、各オペレーティング・システム・スレッドに対してローカルな変数記憶域を宣言する機能が用意されています。スレッド・ローカル変数は、プローブを有効にし、プローブを起動するすべてのスレッドにタグや他のデータでマークする場合に便利です。Dでは、このような課題に対応するためのプログラムを簡単に作成できます。これは、スレッド・ローカル変数は、Dコード内で共通の名前を共有するが、各スレッドに関連付けられた個別のデータ記憶域を参照するためです。
スレッド・ローカル変数を参照するには、次のように、特殊な識別子self
に->
演算子を適用します。
syscall::read:entry { self->read = 1; }
このDコードの例では、read()
システム・コールでプローブを有効にし、このプローブを起動する各スレッドに、read
という名前のスレッド・ローカル変数を関連付けています。スレッド・ローカル変数は、グローバル変数と同じく、最初の割当て時に自動的に作成され、最初の代入文の右側で使用される型(この例ではint
)が適用されます。
Dプログラム内でself->read
変数が参照されるたびに、対応するDTraceプローブが起動したときに実行されていたオペレーティング・システム・スレッドに関連付けられたデータ・オブジェクトが参照されます。スレッド・ローカル変数は、システム内のスレッドのアイデンティティを示すタプルによって暗黙的に索引が付けられた連想配列とみなすことができます。スレッドのアイデンティティは、システムの存続期間中は一意です。スレッドが終了し、同じオペレーティング・システムのデータ構造を使用して新しいスレッドが作成された場合、このスレッドでは同じDTraceのスレッド・ローカル記憶域のアイデンティティは再利用されません。
スレッド・ローカル変数を定義したら、この変数を参照してシステム内の任意のスレッドを参照できます。これは、この変数がそのスレッドに以前に割り当てられていない場合も同様です。スレッド・ローカル変数のスレッドのコピーがまだ割り当てられていない場合、コピーのデータ記憶域にゼロが入力されて定義されます。連想配列要素の場合と同じく、スレッド・ローカル変数の基礎となる記憶域は、この変数にゼロ以外の値が割り当てられるまでは割り当てられません。また、連想配列要素の場合と同じく、スレッド・ローカル変数にゼロを割り当てると、基礎となる記憶域の割当てが解除されます。使用していないスレッド・ローカル変数には、常にゼロを割り当ててください。スレッド・ローカル変数の割当て元の動的な変数領域を微調整するその他の技術は、「オプションおよびチューニング可能パラメータ」を参照してください。
Dプログラムでは、連想配列を含む任意の型のスレッド・ローカル変数を定義できます。次に、スレッド・ローカル変数の定義例を示します。
self->x = 123; /* integer value */ self->s = "hello"; /* string value */ self->a[123, 'a'] = 456; /* associative array */
任意のD変数の場合と同じく、スレッド・ローカル変数は、使用する前に明示的に宣言する必要はありません。あえて宣言を作成する場合は、次のように、キーワードself
を前に付けて、プログラム節の外側に配置します。
self int x; /* declare int x as a thread-local variable */ syscall::read:entry { self->x = 123; }
スレッド・ローカル変数はグローバル変数とは別の名前空間に格納されるため、変数名を再利用できます。プログラム内で名前をオーバーロードした場合、x
とself->x
は同じ変数にはなりません。
次の例は、スレッド・ローカル変数を使用する方法を示しています。エディタで次のプログラムを入力し、rtime.d
という名前のファイルに保存します。
syscall::read:entry { self->t = timestamp; } syscall::read:return /self->t != 0/ { printf("%d/%d spent %d nsecs in read()\n", pid, tid, timestamp - self->t); /* * We are done with this thread-local variable; assign zero to it * to allow the DTrace runtime to reclaim the underlying storage. */ self->t = 0; }
次に、シェルでプログラムの実行を開始します。数秒待つと、出力が表示され始めます。何も出力されない場合は、いくつかのコマンドを実行してみてください。
# dtrace -q -s rtime.d 3987/3987 spent 12786263 nsecs in read() 2183/2183 spent 13410 nsecs in read() 2183/2183 spent 12850 nsecs in read() 2183/2183 spent 10057 nsecs in read() 3583/3583 spent 14527 nsecs in read() 3583/3583 spent 12571 nsecs in read() 3583/3583 spent 9778 nsecs in read() 3583/3583 spent 9498 nsecs in read() 3583/3583 spent 9778 nsecs in read() 2183/2183 spent 13968 nsecs in read() 2183/2183 spent 72076 nsecs in read() ... ^C #
rtime.dプログラムは、指定されたスレッド・ローカル変数を使用して、任意のスレッドによるread()
へのエントリ時のタイムスタンプを捕捉します。続いて、リターン節で、現在のタイムスタンプからself->t
を減算してread()
での経過時間が出力されます。組込みD変数pid
およびtid
は、read()
を実行するスレッドのプロセスIDおよびスレッドIDをレポートします。self->t
はこの情報がレポートされた後は不要になるため、0
が割り当てられます。これにより、t
に関連付けられた基礎となる記憶域を現在のスレッドで再利用できるようになります。
通常、サーバー・プロセスとデーモンによってread()
がバックグラウンドで常に実行されているため、ユーザーが何も実行していなくても多数の出力行が表示されます。次のように、rtime.d
の2番目の節を変更してexecname
変数を使用し、read()
を実行するプロセスの名前を出力してみてください。
printf("%s/%d spent %d nsecs in read()\n", execname, tid, timestamp - self->t);
特に興味のあるプロセスが見つかったら、述語を追加して、read()
の動作を詳細に調べることができます。次はその例です。
syscall::read:entry /execname == "Xorg"/ { self->t = timestamp; }
節ローカル変数
D変数の値は、プローブが起動するたびにアクセスできます。「変数」では、変数が異なるスコープを持つ方法について説明します。グローバル変数の場合、変数の同じインスタンスがすべてのスレッドからアクセスされます。スレッド・ローカルの場合、変数のインスタンスはスレッド固有です。
一方で、節ローカル変数の場合は、変数のインスタンスは、プローブのその特定の起動に固有です。節ローカルが最も狭いスコープです。プローブがCPUで起動すると、Dスクリプトがプログラム順に実行されます。各節ローカル変数は、スクリプトで初めて使用するときに未定義の値でインスタンス化されます。Dスクリプトがプローブの特定の起動の実行を完了するまで、変数の同じインスタンスがすべての節で使用されます。
this->
の接頭辞を付けて、節ローカル変数を参照して割り当てることができます。
BEGIN { this->secs = timestamp / 1000000000; ... }
節ローカル変数を使用する前に明示的に宣言するには、this
キーワードを使用します。
this int x; /* an integer clause-local variable */ this char c; /* a character clause-local variable */ BEGIN { this->x = 123; this->c = 'D'; }
プログラム内の単一のプローブに複数の節が含まれる場合、次の例に示すように、これらの節の実行中はすべての節ローカル変数がそのままの状態を保持します。次のソース・コードを入力し、clause.d
という名前のファイルに保存します。
int me; /* an integer global variable */ this int foo; /* an integer clause-local variable */ tick-1sec { /* * Set foo to be 10 if and only if this is the first clause executed. */ this->foo = (me % 3 == 0) ? 10 : this->foo; printf("Clause 1 is number %d; foo is %d\n", me++ % 3, this->foo++); } tick-1sec { /* * Set foo to be 20 if and only if this is the first clause executed. */ this->foo = (me % 3 == 0) ? 20 : this->foo; printf("Clause 2 is number %d; foo is %d\n", me++ % 3, this->foo++); } tick-1sec { /* * Set foo to be 30 if and only if this is the first clause executed. */ this->foo = (me % 3 == 0) ? 30 : this->foo; printf("Clause 3 is number %d; foo is %d\n", me++ % 3, this->foo++); }
節は常にプログラム順に実行され、同じプローブを有効にする複数の節を通して節ローカル変数は変更されないため、前述のプログラムを実行したときの出力は常に同じになります。
# dtrace -q -s clause.d Clause 1 is number 0; foo is 10 Clause 2 is number 1; foo is 11 Clause 3 is number 2; foo is 12 Clause 1 is number 0; foo is 10 Clause 2 is number 1; foo is 11 Clause 3 is number 2; foo is 12 Clause 1 is number 0; foo is 10 Clause 2 is number 1; foo is 11 Clause 3 is number 2; foo is 12 Clause 1 is number 0; foo is 10 Clause 2 is number 1; foo is 11 Clause 3 is number 2; foo is 12 ^C
節ローカル変数は、同じプローブを有効にする複数の節を通して変更されないため、特定のプローブに対して実行される最初の節では、その値は未定義になります。各節ローカル変数を使用する前に、必ず適切な値を割り当ててください。そうでない場合、プログラムの結果が予想どおりにならない可能性があります。
節ローカル変数は、任意のスカラー変数型を使用して定義できますが、連想配列は、節ローカル・スコープを使用して定義できません。節ローカル変数のスコープは、対応する変数データにのみ適用されます。この変数に定義された名前や型のアイデンティティには適用されません。節ローカル変数が定義されたら、以降のすべてのDプログラム節で、この名前と型署名を使用できます。
節ローカル変数を使用して、計算の途中結果を累積できます。また、節ローカル変数を他の変数の一時コピーとして使用することもできます。節ローカル数には、連想配列よりも高速にアクセスできます。このため、同じDプログラム節内で連想配列値を複数回参照する必要がある場合は、最初にこの値を節ローカル変数にコピーしてから、ローカル変数を繰り返し参照すると、効率がよくなります。
組込み変数
次の表に、組込みD変数の完全なリストを示します。これらの変数はすべてスカラー・グローバル変数です。
表2-13 DTraceの組込み変数
変数 | 説明 |
---|---|
|
現在のプローブに対する型付き引数(存在する場合)。 |
|
64ビットのRAW整数として表現された、プローブに対する最初の10個の入力引数。値は、現在のプローブに定義されている引数に対してのみ有効です。 |
|
プローブが起動した時点における現在のカーネル・スレッドのプログラム・カウンタの場所。 |
|
現在の物理チップのCPUチップ識別子。 |
|
現在のCPUのCPU識別子。詳細は、「schedプロバイダ」を参照してください。 |
|
現在のCPUのCPU情報。「schedプロバイダ」を参照してください。 |
|
現在のスレッドのプロセス状態。「procプロバイダ」を参照してください。 |
|
現在のスレッドに関連付けられているプロセスのプロセス状態。「procプロバイダ」を参照してください。 |
|
インターネットで"task_struct"を検索してメンバーを検出できる |
|
現在のスレッドに関連付けられているプロセスの現在の作業ディレクトリ名。 |
|
現在のプローブの有効なプローブID (EPID)。この整数は、特定の述語と一連のアクションによって有効化された特定のプローブを一意に識別します。 |
|
このスレッドによって実行されたシステム・コールから返されるエラー値。 |
|
現在のプロセスを実行するために |
|
ファイル記述子番号によって索引付けされた ノート:
|
|
現在のプロセスの実グループID。 |
|
現在のプローブのプローブID。このIDは、DTraceによって発行されたシステム全体にわたるプローブの一意の識別子であり、dtrace -lの出力にリストされます。 |
|
現在のCPUでプローブが起動した際の割込み優先レベル(IPL)。 ノート: この値は、割込みが発生した場合はゼロ以外で、それ以外の場合はゼロです。ゼロ以外の値は優先使用が有効であるかどうか(および他の要因)によって決まり、カーネルのリリースやカーネルの構成によって変わる可能性があります。 |
|
現在のCPUをメンバーに持つ待機時間グループの待機時間グループID。この値は常にゼロです。 |
|
現在のプロセスのプロセスID。 |
|
現在のプロセスの親プロセスID。 |
|
現在のプローブの記述の関数名部分。 |
|
現在のプローブの記述のモジュール名部分。 |
|
現在のプローブの記述の名前部分。 |
|
現在のプローブの記述のプロバイダ名部分。 |
|
現在のCPUが含まれるプロセッサ・セットのプロセッサ・セットID。この値は常にゼロです。 |
|
現在のスレッドに関連付けられているプロセスの |
|
現在のスレッドのプローブ起動時のスタック・フレームの深さ。 |
|
現在のスレッドのタスクID。 |
|
ナノ秒タイムスタンプ・カウンタの現在の値。このカウンタは、過去の任意の時点から増分するため、相対計算専用として使用する必要があります。 |
|
プローブが起動した時点における現在のユーザー・スレッドのプログラム・カウンタの場所。 |
|
現在のプロセスの実ユーザーID。 |
|
プローブ起動時の現在のスレッドの保存されたユーザー・モード・レジスタ値。 |
|
ナノ秒タイムスタンプ・カウンタの現在の値。CPU上で現在のスレッドが実行されている合計時間から、DTraceの述語およびアクションで使用された時間を引いた値として仮想化されます。このカウンタは、過去の任意の時点から増分するため、相対時間計算専用として使用する必要があります。 |
|
1970年1月1日の協定世界時00:00から現在までのナノ秒単位の経過時間。 |
trace
などのD言語の組込み関数の詳細は、「アクションおよびサブルーチン」を参照してください。
外部変数
D言語では、オペレーティング・システムには定義されているがDプログラム内では定義されていない変数にアクセスする際、特殊なスコープ演算子として逆引用符(`
)を使用します。詳細は、「外部シンボルおよび型」を参照してください。
ポインタおよびスカラー配列
ポインタは、オペレーティング・システム・カーネル内またはユーザー・プロセスのアドレス空間内のデータ・オブジェクトのメモリー・アドレスです。Dでは、ポインタを作成および操作し、変数や連想配列内に格納できます。この項では、ポインタのD構文、ポインタの作成やアクセスに使用できる演算子、およびポインタと固定サイズのスカラー配列との関係について説明します。また、異なるアドレス空間でのポインタの使用についても説明します。
ノート:
Dポインタ構文は、対応するANSI C構文と同じあるため、CやC++のプログラマとしての経験がある場合、この項はざっと目を通すのみでかまいません。ただし、「ポインタおよびアドレス」および「DTraceオブジェクトへのポインタ」は読む必要があります。これらの項では、DTraceに固有の機能や問題が説明されています。
ポインタおよびアドレス
Linuxオペレーティング・システムでは、仮想メモリーと呼ばれる技術を使用して、ユーザー・プロセスごとにシステム上のメモリー・リソースの独自の仮想表示を提供します。メモリー・リソースの仮想表示は、アドレス空間と呼ばれます。アドレス空間は、アドレス値の範囲(32ビットのアドレス空間の場合は[0 ... 0xffffffff]
、64ビットのアドレス空間の場合は[0 ... 0xffffffffffffffff]
)と変換セットを関連付けます。変換セットは、オペレーティング・システムやハードウェアが各仮想アドレスを対応する物理メモリーの場所に変換するために使用されます。Dのポインタはデータ・オブジェクトで、整数型の仮想アドレス値を格納し、対応するメモリーの場所に格納されているデータのフォーマットを説明するD型と関連付けます。
D変数をポインタ型として明示的に宣言するには、参照されるデータの型を最初に指定してから、型名にアスタリスク(*
)を付加します。これを行うと、次の文に示すように、ポインタ型を宣言することを示します。
int *p;
この文では、整数に対するポインタであるp
というDグローバル変数を宣言しています。この宣言は、p
が64ビットの整数で、その値はメモリー内のいずれかの場所にある別の整数のアドレスであることを意味します。コンパイルされた形式のDコードは、オペレーティング・システム・カーネル自体の内部でプローブの起動時に実行されます。Dポインタは通常、カーネルのアドレス空間に関連付けられたポインタです。archコマンドを使用すると、アクティブなオペレーティング・システム・カーネルによってポインタに使用されるビット数を確認できます。
カーネル内のデータ・オブジェクトへのポインタを作成するには、&
演算子を使用して、そのアドレスを計算します。たとえば、オペレーティング・システム・カーネルのソース・コードにunsigned long max_pfn
変数が宣言されているとします。この変数のアドレスをトレースするには、Dでのオブジェクト名に&
演算子を付加した結果をトレースします。
trace(&`max_pfn);
*
演算子は、ポインタによってアドレス指定されたオブジェクトを参照するときに使用でき、&
演算子と正反対に機能します。たとえば、次の2つのDのコード部分は、意味的に同じです。
q = &`max_pfn; trace(*q); trace(`max_pfn);
この例では、最初のコード部分で、Dグローバル変数ポインタq
を作成しています。max_pfn
オブジェクトはunsigned long
型であるため、&`max_pfn
の型はunsigned long *
(つまり、unsigned long
へのポインタ)となり、q
の型が暗黙的に設定されます。*q
の値をトレースすることにより、ポインタをデータ・オブジェクトmax_pfn
まで戻してトレースします。したがって、このコード部分は、データ・オブジェクトの名前を使用してその値を直接トレースする2番目のコード部分と同じです。
ポインタの安全性
CやC++プログラマの方は、前の項をお読みになって、ポインタの使用方法を間違えるとプログラムがクラッシュするのではないかと、少し不安になったかもしれません。しかし、DTraceは、Dプログラムを実行するための堅牢で安全な環境です。このような型の間違いによってプログラムがクラッシュすることはないことに注意してください。バグのあるDプログラムを作成した場合でも、無効なDポインタ・アクセスが原因でDTraceやオペレーティング・システム・カーネルに障害やクラッシュが発生することはありません。かわりに、DTraceソフトウェアにより、無効なポインタ・アクセスが検出され、インストゥルメンテーションが無効になり、デバッグのために問題がレポートされます。
以前にJavaプログラミング言語でプログラミングした経験をお持ちであればご存知でしょうが、Java言語では、まったく同じ安全上の理由により、ポインタはサポートされていません。ポインタは、Cでのオペレーティング・システムの実装の本質的な部分であるため、Dに必要です。しかし、DTraceには、Javaプログラミング言語と同様の安全性メカニズムが実装されているため、バグのあるプログラムがプログラム自体や他のプログラムに危害を与えることはありません。DTraceのエラー・レポート機能は、Javaプログラミング言語のランタイム環境と同じように、プログラミング・エラーを検出し、例外をレポートします。
DTraceのエラー処理およびレポート機能について観察するために、ポインタを使用して意図的に不正なDプログラムを作成します。たとえば、エディタで次のDプログラムを入力し、badptr.d
という名前のファイルに保存します。
BEGIN { x = (int *)NULL; y = *x; trace(y); }
badptr.d
プログラムでは、int
へのポインタであるx
という名前のDポインタが作成されています。このポインタには、特殊かつ無効なポインタ値NULL
が割り当てられており、この値はアドレス0を表す組込みの別名です。慣例上、アドレス0
は常に無効と定義されるため、CプログラムやDプログラムでは、NULL
を標識値として使用できます。このプログラムでは、キャスト式を使用してNULL
が整数のポインタに変換されています。このポインタは式*x
を使用して間接参照され、その結果が別の変数y
に割り当てられています。その後、変数y
のトレースが試行されています。Dプログラムを実行すると、y = *x
という文が実行されたところで無効なポインタ・アクセスが検出され、エラーがレポートされます。
# dtrace -s badptr.d dtrace: script 'badptr.d' matched 1 probe dtrace: error on enabled probe ID 1 (ID 1: dtrace:::BEGIN): invalid address (0x0) in action #2 at DIF offset 4 ^C #
Dプログラムはエラーを無視し、実行を続行することに注目してください。システムおよび監視されたすべてのプロセスは引き続き影響を受けません。ERROR
プローブをスクリプトに追加して、Dエラーを処理することもできます。DTraceのエラー・メカニズムの詳細は、「ERRORプローブ」を参照してください。
配列宣言および記憶域
Dは、「変数」で説明した動的連想配列以外に、スカラー配列もサポートしています。スカラー配列は、それぞれに同じ型の値が格納される、連続する固定長のメモリーの場所のグループです。スカラー配列にアクセスするには、ゼロから始まる整数を使用して各場所を参照します。スカラー配列の概念と構文は、CやC++の配列と直接対応しています。Dでは、スカラー配列は、連想配列およびその高度な対応する集積体として頻繁に使用されることはありません。ただし、Cで宣言される既存のオペレーティング・システムの配列データ構造にアクセスするには、スカラー配列を使用する必要がある場合があります。集積については、「集積体」で説明されています。
次のように、int
型を使用して5つの整数によるDスカラー配列を宣言します。宣言には接尾辞として要素数を大カッコで囲んだものを付加します。
int a[5];
図2-*は、配列の記憶域を視覚的に表現したものを示しています。
スカラー配列の表現
![この図は、a[5]として宣言されたa[0]からa[4]までの要素をメモリー内に並べて配置した様子を示しています。 この図は、a[5]として宣言されたa[0]からa[4]までの要素をメモリー内に並べて配置した様子を示しています。](img/array.png)
D式a[0]
は最初の配列要素を参照し、a[1]
は2番目の配列要素を参照しています(以下同様)。構文を見ると、スカラー配列と連想配列は大変類似しています。次のように、整数によって参照される整数の連想配列を宣言できます。
int a[int];
この配列は、式a[0]
を使用して参照することもできます。ただし、記憶域と実装の観点からは、2つの配列は非常に異なります。静的配列a
は、ゼロから順に番号付けされた連続した5つのメモリーの場所で構成されています。索引は、この配列に割り当てられた記憶域内のオフセットを参照しています。これに対して、連想配列の場合、サイズは事前定義されておらず、要素は連続したメモリーの場所には格納されません。また、連想配列のキーは、対応する値の記憶域の場所とは関係ありません。連想配列の要素a[0]
およびa[-5]
にアクセスすると、DTraceによって2ワード分のみの記憶域が割り当てられますが、これらの記憶域は連続しているとはかぎりません。連想配列のキーは、対応する値の抽象名であり、値の記憶域の場所とは関係ありません。
初期値割当てを使用して配列を作成し、単一の整数式を配列索引として使用する場合(たとえば、a[0] = 2
)、この式ではa
をスカラー配列への代入として解釈することも可能ですが、Dコンパイラは常に新しい連想配列を生成します。この場合、Dコンパイラに配列のサイズの定義を認識させ、この配列がスカラー配列であると推断させるには、スカラー配列を事前に宣言する必要があります。
ポインタと配列の関係
Dでも、ANSI Cの場合と同様に、ポインタとスカラー配列には特別な関係があります。スカラー配列を表す変数は、最初の記憶域の場所を示すアドレスに関連付けられています。ポインタは、定義された型の記憶域の場所のアドレスでもあります。したがって、Dでは、ポインタ変数と配列変数の両方で、配列索引の[]
表記を使用できます。たとえば、次の2つのDのコード部分は、意味的に同じです。
p = &a[0]; trace(p[2]); trace(a[2]);
最初のコード部分では、&
演算子を式a[0]
を適用することにより、ポインタp
をスカラー配列a
内の最初の要素のアドレスに割り当てています。式p[2]
では、3番目の配列要素(索引2)の値がトレースされます。p
には、a
に関連付けられたものと同じアドレスが含まれるため、この式では、2番目のコード部分に示されるa[2]
と同じ値が導き出されます。このような等価性の結果、CとDでは、任意のポインタまたは配列の任意の索引へのアクセスが可能になります。配列境界のチェックは、コンパイラでもDTrace実行環境でも実行されません。スカラー配列に事前定義されたサイズの範囲外のメモリーにアクセスすると、予想外の結果になるか、前の例のように、アドレスが無効であることを示すエラーがレポートされます。DTrace自体やオペレーティング・システムが破損することはありませんが、Dプログラムをデバッグする必要があります。
ポインタと配列の違いは、ポインタ変数の場合、他の記憶域の整数アドレスが含まれる記憶域の別の部分を参照する点です。これに対し、配列変数は、配列の場所がかわりに含まれる整数の場所ではなく、配列の記憶域自体を指定します。図2-*に、この違いを示します。
ポインタおよび配列の記憶域
![この図は、a[5]として宣言された配列の最初の要素(a[0])のアドレスである値0x12345678を持つポインタpを示しています。 この図は、a[5]として宣言された配列の最初の要素(a[0])のアドレスである値0x12345678を持つポインタpを示しています。](img/arrptr.png)
この違いは、ポインタとスカラー配列を割り当てるときのD構文から明らかです。x
およびy
がポインタ変数である場合、式x = y
は正当です。この式では、y
内のポインタ・アドレスが、x
で指定された記憶域の場所にコピーされます。x
およびy
がスカラー変数である場合、式x = y
は不正です。Dでは、配列を全体として割り当てることはできません。ただし、配列変数やシンボル名は、ポインタが許可されていればあらゆる状況で使用できます。p
がポインタで、a
がスカラー配列である場合、文p = a
を使用できます。この文は、文p = &a[0]
と同等です。
ポインタ演算
ポインタは、メモリー内の他のオブジェクトのアドレスとして使用される整数にすぎません。このため、Dには、ポインタに対する演算を実行するための一連の機能が用意されています。ただし、ポインタ演算は、整数の演算とは別のものです。ポインタ演算では、ポインタで参照されている型のサイズとオペランドの乗除によって、基礎となるアドレスが暗黙的に変更されます。
次のDのコード部分は、この特性を示しています。
int *x; BEGIN { trace(x); trace(x + 1); trace(x + 2); }
このコード部分では、整数ポインタx
が作成され、その値、その値を1増分した値、およびその値を2増分した値がトレースされています。このプログラムを作成して実行すると、整数値0
、4
および8
がレポートされます。
x
はint
(サイズは4バイト)のポインタであるため、x
を増分すると、基礎となるポインタ値に4が加算されます。この特性は、ポインタを使用して配列などの連続した記憶域の場所を参照する場合に便利です。たとえば、図2-*に示すような配列a
のアドレスにx
が割り当てられた場合、式x + 1
は式&a[1]
と同等になります。同様に、式*(x + 1)
は、値a[1]
を参照します。+
、++
または=+
演算子を使用してポインタ値が増分される場合は常に、ポインタ演算が実装されます。ポインタ演算は、左側のポインタから整数が減算される場合、ポインタから別のポインタが減算される場合、ポインタに--
演算子が適用される場合も適用されます。
たとえば、次のDプログラムでは、結果として2
がトレースされます。
int *x, *y; int a[5]; BEGIN { x = &a[0]; y = &a[2]; trace(y - x); }
汎用ポインタ
Dプログラムでは、ポインタによって参照されるデータ型を指定せず、汎用ポインタ・アドレスを表現または操作すると便利な場合があります。汎用ポインタを指定するには、キーワードvoid
が特定の型情報が存在しないことを示すvoid *
型を使用するか、現在のデータ・モデル内のポインタに適したサイズの符号なし整数型を示す組込み型の別名uintptr_t
を使用します。void *
型のオブジェクトにポインタ演算を適用することはできません。これらのポインタを間接参照するには、最初に別の型にキャストする必要があります。ポインタ値に対して整数演算を実行する必要がある場合は、ポインタをuintptr_t
型にキャストします。
void
へのポインタは、連想配列のタプルの式や代入文の右側など、別のデータ型へのポインタが必要な場合はいつでも使用できます。同様に、void
へのポインタが必要な場合は、任意のデータ型へのポインタを使用できます。void
以外の型へのポインタを、void
以外の別のポインタ型のかわりに使用するには、明示的なキャストが必要です。ポインタをuintptr_t
などの整数型に変換したり、これらの整数を適切なポインタ型に変換し戻す場合は常に、明示的キャストを使用する必要があります。
多次元配列
Dでは、多次元のスカラー配列はあまり使用されません。しかし、ANSI Cとの互換性を実現したり、Cでこの機能を使用して作成されたオペレーティング・システム・データ構造を監視およびこれにアクセスするために提供されています。多次元配列は、基本型の後ろに、連続する一連のスカラー配列サイズを大カッコ[]
で囲んだ形式で宣言されます。たとえば、12行x34列の整数値で構成される固定サイズの2次元長方形配列を宣言するには、次の宣言を記述します。
int a[12][34];
多次元スカラー配列にも、同様の表記法でアクセスできます。たとえば、行0
列1
に格納されている値にアクセスするには、次のようなD式を記述します。
a[0][1]
多次元スカラー配列値の記憶域の場所を計算するには、行番号に、宣言された列数の合計を乗算してから、列番号を加算します。
多次元配列の構文と、連想配列アクセスのD構文を混同しないように注意してください(つまり、a[0][1]
はa[0,1]
と同じではありません)。連想配列に互換性のないタプルを使用したり、スカラー配列の連想配列アクセスを試行すると、適切なエラー・メッセージが表示され、プログラムのコンパイルが拒否されます。
DTraceオブジェクトへのポインタ
Dコンパイラでは、連想配列、組込み関数および変数などのDTraceオブジェクトへのポインタを、&
演算子を使用して取得することは禁じられています。DTrace実行環境では、次のプローブが起動するまでの間に必要に応じて変数のアドレスを自由に再配置できるように、これらのアドレスを取得することは禁じられています。このように、DTraceは、プログラムに必要なメモリーをより効率的に管理できます。複合構造を作成すれば、DTraceオブジェクト記憶域のカーネル・アドレスを取得する式を作成することは可能です。このような式はDプログラム内で作成しないようにしてください。このような式を使用する必要がある場合、プローブを起動するたびにアドレスが同じであると想定しないでください。
ANSI Cでは、ポインタを使用して、間接的な関数コールや代入を実行できます。たとえば、代入演算子の左側に、単項の間接参照演算子*
を配置できます。Dでは、ポインタを使用したこのような式は許可されていません。値は、名前を指定するか、Dスカラー配列または連想配列に配列索引演算子[]
を適用することによってのみ、D変数に直接代入できます。「アクションおよびサブルーチン」に指定されているように、DTrace環境に定義されている関数のみがコール可能です。Dでは、ポインタを使用した間接的な関数コールは許可されていません。
ポインタおよびアドレス空間
ポインタは、仮想アドレス空間内で物理メモリーへの変換を提供するアドレスです。DTraceでは、オペレーティング・システム・カーネル自体のアドレス空間内でDプログラムが実行されます。Linuxシステムでは、多数のアドレス空間が管理されます。これには、オペレーティング・システム・カーネル用のアドレス空間と、各ユーザー・プロセス用のアドレス空間があります。各アドレス空間は、システム上のすべてのメモリーにアクセスできるように見えるため、複数のアドレス空間で同じ仮想アドレス・ポインタ値を再使用しても、それぞれ異なる物理メモリーに変換できます。したがって、ポインタを使用するDプログラムを作成する場合、使用するポインタに対応するアドレス空間を意識する必要があります。
たとえば、syscall
プロバイダを使用して、引数として整数または整数配列へのポインタをとるシステム・コール(pipe()
など)のエントリをインストゥルメントする場合、*
または[]
演算子を使用してそのポインタまたは配列を間接参照することはできません。これは、そのアドレスが、システム・コールを実行したユーザー・プロセスのアドレス空間内のアドレスであるためです。Dで、このアドレスに*
または[]
演算子を適用すると、カーネル・アドレス空間がアクセスされ、無効なアドレスによるエラーが発生します。または、このアドレスが有効なカーネル・アドレスと偶然同じであるかどうかに応じて、Dプログラムに予想外のデータが返されます。
DTraceプローブからユーザー・プロセス・メモリーにアクセスするには、「アクションおよびサブルーチン」に記載されているcopyin
、copyinstr
またはcopyinto
の関数のいずれかを、ユーザー・アドレス空間ポインタに適用する必要があります。混乱を避けるために、Dプログラムの作成時には、ユーザー・アドレスを適切に格納する変数を指定し、コメントを付けるようにしてください。ユーザー・アドレスを間接参照するようなDコードを誤ってコンパイルすることがないように、ユーザー・アドレスをuintptr_t
として格納することもできます。DTraceをユーザー・プロセスで使用する技術の詳細は、「ユーザー・プロセスのトレース」を参照してください。
文字列のDTraceサポート
DTraceでは、文字列のトレースおよび操作をサポートしています。この項では、文字列を宣言および操作するためのD言語機能の完全セットについて説明します。ANSI Cの場合とは異なり、Dの文字列では、独自の組込み型と演算子がサポートされています。このため、これらはトレース・プログラム内で簡単かつ明白に使用できます。
文字列表現
DTraceでは、文字列はNULLバイト(値がゼロのバイトで、通常は'\0'
と記述されます)で終わる文字配列として表現されます。文字列の可視部分は可変長で、NULLバイトの場所によって長さが決まります。しかし、DTraceでは、各プローブで一定量のデータがトレースされるように、各文字列が固定サイズの配列に格納されます。文字列の長さは、事前に定義された文字列制限を超えることはできません。ただし、この制限はDプログラム内で変更できます。また、dtraceコマンドラインでstrsize
オプションをチューニングして変更することもできます。チューニング可能なDTraceオプションの詳細は、「オプションおよびチューニング可能パラメータ」を参照してください。デフォルトの文字列制限は256バイトです。
D言語では、文字列を参照するために、char *
型を使用するかわりに、明示的なstring
型が用意されています。string型は、文字シーケンスのアドレスであるという点ではchar *
型と等価です。しかし、Dコンパイラやtrace
などのD関数は、string型の式に適用される場合は拡張機能を備えています。たとえば、string型では、文字列の実バイトをトレースする必要があるときに、char *
型のような曖昧さがありません。
次のD文で、s
がchar *
型である場合、ポインタs
の値がトレースされます(つまり、整数アドレス値がトレースされます)。
trace(s);
次のD文では、*
演算子の定義により、ポインタs
が間接参照され、その場所にある単一の文字がトレースされます。
trace(*s);
このような動作を利用すると、単一の文字や、文字列でなくNULLバイトでは終わらないバイト・サイズの整数配列を参照する文字ポインタを操作できます。
次のD文では、s
がstring
型である場合、このstring型からDコンパイラに対して、アドレスが変数s
に格納されているNULLで終わる文字列をトレースするよう指示されます。
trace(s);
文字列型の式の字句の比較を実行することもできます。「文字列比較」を参照してください。
文字列定数
文字列定数は、二重引用符のペア(""
)で囲まれ、自動的にstring
型が割り当てられます。システムでの使用がDTraceによって許可されたメモリー量の範囲内で、任意の長さの文字列定数を定義できます。文字列定数を宣言すると、自動的に終了NULLバイト(\0
)が付加されます。文字列定数オブジェクトのサイズは、この文字列に関連付けられたバイト数に1バイト(終了NULLバイト用)を加えた長さです。
文字列定数には、リテラルの改行文字を含めることはできません。改行が含まれる文字列を作成するには、リテラルの改行のかわりに、\n
エスケープ・シーケンスを使用します。文字列定数には、文字定数に定義された特殊文字のエスケープ・シーケンスを含めることもできます。表2-6を参照してください。
文字列代入
char *
変数の代入の場合とは異なり、文字列は、参照ではなく値ごとにコピーされます。文字列代入演算子=
により、元のオペランドの文字列の実バイトとNULLバイトが、左側の変数(これはstring
型である必要があります)にコピーされます。string型の変数を新しく作成するには、この変数にstring
型の式を割り当てます。
たとえば、次のようなD文があるとします。
s = "hello";
この場合、string
型の新しい変数s
が作成され、そこに6バイトの文字列"hello"
(出力可能文字5バイトとNULLバイト)がコピーされます。文字列代入は、Cライブラリ関数strcpy()
と類似していますが、元の文字列がコピー先の文字列の記憶域制限を超えた場合、最終的な文字列はこの制限上のNULLバイトで自動的に切り捨てられる点が異なります。
文字列変数には、文字列と互換性のある型の式を代入することもできます。この場合、元の式が自動的にstring型に昇格され、文字列代入が行われます。Dコンパイラでは、char *
型またはchar[n]
型(任意のサイズのchar
型のスカラー配列)の任意の式をstring型に昇格できます。
文字列変換
他の型の式は、キャスト式を使用したり、特殊なstringof
演算子を適用することにより、string
型に明示的に変換できます。どちらの場合も次の意味で同等です。
s = (string) expression; s = stringof (expression);
この式は、文字列へのアドレスとして解釈されます。
stringof
演算子は、右側のオペランドに非常に緊密にバインドされます。通常、わかりやすくするために丸カッコを使用して式を囲みます。ただし、厳密には必要ありません。
ポインタ、整数またはスカラー配列アドレスなどのスカラー型の式はすべて、文字列に変換できます。void
などの他の型の式は、string
には変換できません。無効なアドレスを誤って文字列に変換した場合でも、DTraceの安全機能により、システムやDTraceが破損することはありません。ただし、解読不能な文字シーケンスをトレースする可能性があります。
文字列比較
Dでは、バイナリ関係演算子が多重定義されており、これらを使用して、整数の比較のみでなく文字列の比較を行うこともできます。関係演算子では、両方のオペランドがstring
型であるか、一方のオペランドがstring
型でもう一方のオペランドがstring
に昇格可能である場合は常に、文字列比較が行われます。詳細は、「文字列代入」を参照してください。文字列の比較に使用できる関係演算子がリストされた表2-14も参照してください。
表2-14 文字列用のDの関係演算子
演算子 | 説明 |
---|---|
|
左のオペランドは右のオペランドより小さい |
|
左のオペランドは右のオペランド以下 |
|
左のオペランドは右のオペランドより大きい |
|
左のオペランドは右のオペランド以上 |
|
左のオペランドは右のオペランドと等しい |
|
左のオペランドは右のオペランドと等しくない |
整数の場合と同じく、各演算子は、int
型の値(条件が真の場合は1、偽の場合は0)に評価されます。
関係演算子では、Cライブラリ・ルーチンstrcmp()
の場合と同様に、2つの入力文字列がバイト単位で比較されます。各バイトは、ASCII文字セット内の対応する整数値を使用して比較されます。NULLバイトが読み取られるか最大文字列長に達したら、比較は終了します。詳細は、ascii(7)
マニュアル・ページを参照してください。Dの文字列比較とその結果の例を次の表に示します。
Dの文字列比較 | 結果 |
---|---|
|
1 (真)を返す |
|
1 (真)を返す |
|
0 (偽)を返す |
ノート:
表面上は同一であるUnicode文字列であっても、どちらかが正規化されていない場合、異なる文字列であると判断される可能性があります。
構造体および共用体
関連する変数の集合は、構造体および共用体と呼ばれる複合データ・オブジェクトにグループ化できます。Dでこれらのオブジェクトを定義するには、これらに対して新しい型定義を作成します。新しい型は、連想配列の値を含む任意のD変数で使用できます。この項では、このような複合型を作成し操作するための構文とセマンティクスを紹介し、これらと相互に作用するD演算子について説明します。
構造体
他の型のグループから構成される新しい型を作成するには、Dのキーワードstruct
(structure (構造体)の略)を使用します。この新しいstruct
型をDの変数や配列の型として使用することにより、関連する変数のグループを単一の名前で定義できます。Dの構造体は、CやC++の対応する構造体と同じです。以前にJavaプログラミング言語でプログラミングした経験がある方であれば、Dの構造体を、メソッドを持たずデータ・メンバーのみが含まれるクラスのようなものと考えてください。
シェルによって実行されるread()
およびwrite()
システム・コールに関するいくつかの事項(経過時間、コール回数、引数として渡される最大バイト数など)を記録する、より洗練されたシステム・コールのトレース・プログラムをDで作成するとします。
この場合、次の例のように、3つの個別連想配列を使用してこれらのプロパティを記録するD節を作成できます。
int maxbytes[string]; /* declare maxbytes */ syscall::read:entry, syscall::write:entry /pid == 12345/ { ts[probefunc] = timestamp; calls[probefunc]++; maxbytes[probefunc] = arg2 > maxbytes[probefunc] ? arg2 : maxbytes[probefunc]; }
ただし、この節では、3つの個別連想配列を作成し、それぞれにprobefunc
に対応する同一のタプル値の個別コピーを格納する必要があるため、効率的ではありません。かわりに、構造体を使用すると、スペースを節約できるとともに、プログラムも読みやすく管理しやすいものになります。
まず、Dプログラムのソース・ファイルの冒頭で新しいstruct
型を宣言します。
struct callinfo { uint64_t ts; /* timestamp of last syscall entry */ uint64_t elapsed; /* total elapsed time in nanoseconds */ uint64_t calls; /* number of calls made */ size_t maxbytes; /* maximum byte count argument */ };
struct
キーワードの後ろに、この新しい型を参照するために使用するオプションの識別子を付けて、struct callinfo
としています。次に、構造体のメンバーを中カッコ{}
で囲み、宣言全体はセミコロン(;
)で終わります。各構造体メンバーは、D変数宣言と同じ構文を使用して定義されます。最初にメンバーの型、次にメンバーを指定する識別子名、最後に別のセミコロン(;
)が並びます。
struct
宣言は、単に新しい型を定義します。変数を作成したり、DTraceに記憶域を割り当てることはありません。宣言すると、残りのDプログラム全体でstruct callinfo
を型として使用できます。struct callinfo
型の各変数には、構造テンプレートで記述されている4つの変数のコピーが格納されます。メンバーは、メンバー・リストの順序に従ってメモリー内に配置されます。データ・オブジェクトの配置に必要な場合は、メンバー間にパディング・スペースが挿入されます。
個々のメンバー値にアクセスするには、.
演算子とともにメンバーの識別子名を使用して、次のような形式の式を作成します。
variable-name.member-name
次の例は、新しい構造体型を使用した改良版のプログラムを示します。テキスト・エディタで、次のDプログラムを入力し、rwinfo.d
という名前のファイルに保存します。
struct callinfo { uint64_t ts; /* timestamp of last syscall entry */ uint64_t elapsed; /* total elapsed time in nanoseconds */ uint64_t calls; /* number of calls made */ size_t maxbytes; /* maximum byte count argument */ }; struct callinfo i[string]; /* declare i as an associative array */ syscall::read:entry, syscall::write:entry /pid == $1/ { i[probefunc].ts = timestamp; i[probefunc].calls++; i[probefunc].maxbytes = arg2 > i[probefunc].maxbytes ? arg2 : i[probefunc].maxbytes; } syscall::read:return, syscall::write:return /i[probefunc].ts != 0 && pid == $1/ { i[probefunc].elapsed += timestamp - i[probefunc].ts; } END { printf(" calls max bytes elapsed nsecs\n"); printf("------ ----- --------- -------------\n"); printf(" read %5d %9d %d\n", i["read"].calls, i["read"].maxbytes, i["read"].elapsed); printf(" write %5d %9d %d\n", i["write"].calls, i["write"].maxbytes, i["write"].elapsed); }
プログラムを入力したら、シェル・プロセスの1つを指定して、dtrace -q -s rwinfo.dコマンドを実行します。次に、シェルにいくつかのコマンドを入力します。シェル・コマンドの入力が終了したら、[Ctrl]キーを押しながら[C]キーを押し、END
プローブを起動して結果を出力します。
# dtrace -q -s rwinfo.d `pgrep -n bash` ^C calls max bytes elapsed nsecs ------ ----- --------- ------------- read 25 1024 8775036488 write 33 22 1859173
構造体へのポインタ
CやDでは、しばしば、ポインタを使用して構造体を参照します。演算子->
を使用すると、ポインタを使用して構造体のメンバーにアクセスできます。構造体struct s
がメンバーm
を持つときに、この構造体にsp
という名前のポインタがある(ここで、sp
はstruct s *
型の変数)場合、メンバーにアクセスする方法は2通りあります。1つは、演算子*
を使用して最初にポインタsp
を間接参照する方法です。
struct s *sp; (*sp).m
もう1つは、この表記の短縮形として演算子->
を使用する方法です。sp
が構造体へのポインタである場合、次の2つのDコード部分は等価です。
(*sp).m sp->m
DTraceでは、構造体へのポインタとしていくつかの組込み変数が用意されています。たとえば、ポインタcurpsinfo
は、struct
psinfo
を参照します。その内容は、現在のプローブを起動したスレッドに関連付けられているプロセスの状態に関する情報のスナップショットを提供します。次の表に、curpsinfo
を使用する式の例とその型および意味をリストします。
式の例 | タイプ | 意味 |
---|---|---|
|
|
現在のプロセスID |
|
|
実行可能ファイルの名前 |
|
|
最初のコマンドライン引数 |
詳細は、「psinfo_t」を参照してください。
次の例では、pr_fname
メンバーを使用して、特定のプロセスを識別しています。エディタで次のスクリプトを入力し、procfs.d
という名前のファイルに保存します。
syscall::write:entry / curpsinfo->pr_fname == "date" / { printf("%s run by UID %d\n", curpsinfo->pr_psargs, curpsinfo->pr_uid); }
この節では、curpsinfo->pr_fname
という式を使用して、コマンド名にアクセスし、これを照合しています。これにより、引数をトレースする前に正しいwrite()
リクエストを選択できるようになります。ここでは、==
演算子を使用し、左側にはchar
の配列である引数、右側には文字列である引数を配置しています。これにより、Dコンパイラは、左側の引数を文字列に昇格し、文字列比較を行う必要があると判断します。1つのシェルでコマンドdtrace -q -s procs.dを入力し、別のシェルでdateコマンドを複数回入力します。DTraceによって表示される出力は、次のようになります。
# dtrace -q -s procfs.d date run by UID 500 /bin/date run by UID 500 date -R run by UID 500 ... ^C #
Cプログラムでは複雑なデータ構造体がよく使用されます。このため、Dから構造体を記述および参照する機能には、Oracle Linuxオペレーティング・システム・カーネルおよびそのシステム・インタフェースを監視するための強力な機能も用意されています。
共用体
共用体は、ANSI CとDでサポートされるもう1つの複合型であり、構造体との間に密接な関係があります。共用体は複合型であり、ここでは、異なる型の一連のメンバーが定義され、メンバー・オブジェクトはすべて同じ記憶域のリージョンを占有します。したがって、共用体はバリアント型のオブジェクトです。この場合、任意の時点で有効なメンバーは1つのみであり、このメンバーは共用体の割当て方法によって決まります。通常、他の変数や状態を使用して、共用体のどのメンバーが現在有効であるかを示します。共用体のサイズは、その最大のメンバーのサイズです。共用体に使用されるメモリー位置合せは、共用体のメンバーに必要な最大位置合せになります。
メンバー・サイズおよびオフセット
sizeof
演算子を使用すると、struct
またはunion
を含むあらゆるD型または式のサイズ(バイト単位)を特定できます。sizeof
演算子は、次の2つの例のように、式、または丸カッコで囲んだ型名に適用できます。
sizeof expression sizeof (type-name)
たとえば、式sizeof (uint64_t)
は値8
を返します。また、式sizeof (callinfo.ts)
も、前述のプログラム例のソース・コードに挿入した場合、8
を返します。sizeof
演算子の正式な戻り型は、型の別名size_t
です。これは、現在のデータ・モデル内のポインタと同じサイズの符号なし整数として定義されており、バイト数を表現するために使用されます。式にsizeof
演算子を適用すると、この式はDコンパイラによって評価されますが、結果のオブジェクト・サイズはコンパイル時に計算され、式のコードは生成されません。sizeof
は、整数定数が必要な任意の場所で使用できます。
比較演算子offsetof
を使用すると、struct
またはunion
型のオブジェクトに関連付けられた記憶域の開始位置から、構造体または共用体メンバーのオフセットをバイト単位で特定できます。offsetof
演算子は、次の形式の式で使用します。
offsetof (type-name, member-name)
この場合、type-nameは、任意のstruct
またはunion
型の名前、または型の別名です。member-nameは、構造体または共用体のメンバーの名前を表す識別子です。sizeof
と同様に、offsetof
は、size_t
を返し、整数定数を使用できるDプログラム内の任意の場所で使用できます。
ビットフィールド
Dでは、ビットフィールドと呼ばれる、任意のビット数の整数の構造体または共用体メンバーを定義することもできます。ビットフィールドを宣言するには、次の例に示すように、符号付きまたは符号なしの整数基本型、メンバー名、およびフィールドに割り当てるビット数を示す接尾辞を指定します。
struct s { int a : 1; int b : 3; int c : 12; };
ビットフィールド幅は、メンバー名の後ろのコロンに続く整数定数です。ビットフィールド幅は正の数であるとともに、対応する整数基本型の幅を超えないビット数である必要があります。Dでは、64ビットを超えるビットフィールドは宣言できません。Dのビットフィールドは、対応するANSI Cの機能と互換性があり、この機能にアクセスできます。通常、ビットフィールドは、メモリー記憶域が不足している場合や、構造体レイアウトがハードウェア・レジスタ・レイアウトと一致する必要がある場合に使用します。
ビットフィールドは、整数やマスク・セットのレイアウトを自動化し、メンバー値を抽出するコンパイラ構成です。単純にマスク自体を定義し、&
演算子を使用しても、同じ結果が得られます。CおよびDのコンパイラはなるべく効率的にビットを圧縮することを試みますが、その順序や方法は自由です。したがって、コンパイラやアーキテクチャが異なる場合に、あるビットフィールドに対して同一のビット・レイアウトが生成されることは保証されません。安定したビット・レイアウトが必要な場合は、ビット・マスクを作成し、&
演算子を使用して値を抽出してください。
ビットフィールド・メンバーにアクセスするには、他の任意の構造体または共用体のメンバーの場合と同じく、その名前とともに.
または->
演算子を指定するのみです。ビットフィールドは、任意の式で使用できるように、次に大きい整数型に自動的に昇格されます。ビットフィールド記憶域はバイト境界上には配置できません。また、そのサイズをバイト数の概数にすることもできません。このため、ビットフィールド・メンバーにsizeof
またはoffsetof
演算子は適用できません。Dコンパイラでは、&
演算子を使用してビットフィールド・メンバーのアドレスを指定することも禁じられています。
型と定数の定義
この項では、Dで型の別名と名前付き定数を宣言する方法について説明します。Dプログラムの型と名前空間の管理、およびオペレーティング・システムの型と識別子についても説明します。
typedef
typedef
は、既存の型の別名として識別子を宣言するために使用します。D型の宣言がすべてそうであるように、typedef
は、次の形式の宣言で、プローブ節の外側で使用します。
typedef existing-type new-type ;
existing-typeは任意の型の宣言であり、new-typeはこの型の別名として使用する識別子です。たとえば、Dコンパイラで次の宣言を内部使用し、uint8_t
型の別名を作成します。
typedef unsigned char uint8_t;
型の別名は、標準の型が使用可能な任意の場所で使用できます。たとえば、変数、連想配列値、またはタプルのメンバーの型として使用できます。また、typedef
は、次の例のように、新しいstruct
の定義など、より綿密な宣言と組み合せることもできます。
typedef struct foo { int x; int y; } foo_t;
前述の例では、struct foo
は、その別名foo_t
と同じ型を使用して定義されています。LinuxのCシステム・ヘッダーではしばしば、接尾辞_t
を使用してtypedef
の別名を表します。
列挙
プログラム内の定数のシンボル名を定義すると、プログラムが読みやすく、将来的に管理しやすくなります。その方法の1つとして、列挙を定義します。これにより、一連の整数と、列挙子と呼ばれる一連の識別子を関連付けます。この列挙子が認識され、対応する整数値に置き換えられます。列挙は、次のような宣言を使用して定義します。
enum colors { RED, GREEN, BLUE };
この列挙の最初の列挙子RED
には値ゼロが割り当てられ、後続の列挙子にはそれぞれ次の整数値が割り当てられます。
任意の列挙子に明示的に整数値を指定することもできます。これを行うには、次の例のように、列挙子の後ろに等号と整数定数を付けます。
enum colors { RED = 7, GREEN = 9, BLUE };
列挙子BLUE
には値10
が割り当てられます。これは、値が指定されておらず、前の列挙子が9
に設定されているためです。列挙が定義されたら、整数定数が使用される場所であればDプログラム内のどこでも列挙子を使用できます。また、列挙enum colors
は、int
と同等の型としても定義されています。enum
型の変数は、int
を使用できる場所であればどこでも使用できます。enum
型の変数には、任意の整数値を割り当てることができます。また、型名が不要なときは、宣言内でenum
名を省略することもできます。
列挙子はプログラム内の後続のすべての節および宣言で参照可能です。したがって、複数の列挙で同じ列挙子識別子を定義できません。ただし、1つの列挙または複数の列挙内に同じ値を持つ列挙子を複数定義することは可能です。また、列挙型の変数に対応する列挙子を持たない整数を割り当てることもできます。
Dの列挙の構文は、対応するANSI Cの構文と同じです。Dでも、オペレーティング・システム・カーネルとそのロード可能なモジュールで定義された列挙にアクセスできます。これらの列挙子は、Dプログラムでグローバルに参照可能でないことに注意してください。カーネル列挙子が表示されるのは、対応する列挙型のオブジェクトとの比較を行う際に引数として指定する場合のみです。この機能により、Dプログラム内で識別子名を誤って使用し、オペレーティング・システム・カーネルに定義されている大量の列挙と競合するのを防ぐことができます。
次のDプログラムの例では、I/Oリクエストに関する情報を表示します。プログラムで、列挙子B_READ
およびB_WRITE
を使用して、読取り操作と書込み操作を区別しています。
io:::done, io:::start, io:::wait-done, io:::wait-start { printf("%8s %10s: %d %16s (%s size %d @ sect %d)\n", args[1]->dev_statname, probename, timestamp, execname, args[0]->b_flags & B_READ ? "R" : args[0]->b_flags & B_WRITE ? "W" : "?", args[0]->b_bcount, args[0]->b_blkno); }
インライン
Dの名前付き定数は、inline
ディレクティブを使用して定義することもできます。このディレクティブを使用すると、より一般的な方法で、コンパイル時に事前に定義された値や式で置き換えられる識別子を作成できます。インライン・ディレクティブは、Cプリプロセッサで提供される#define
ディレクティブよりも強力な語彙置換形式です。これは、置換には実際の型が割り当てられ、単なる一連の語彙トークンではなくコンパイルされた構文ツリーを使用して置換を実行できるためです。inline
ディレクティブは、次の形式の宣言を使用して指定します。
inline type name = expression;
typeは既存の型の型定義であり、nameは以前にインラインまたはグローバル変数として定義されていない任意の有効なD識別子、expressionは任意の有効なD式です。インライン・ディレクティブの処理後、プログラム・ソース内のnameの後続の各インスタンスが、コンパイルされた形式expressionで置き換えられます。
たとえば、次のDプログラムでは、文字列"hello
"と整数値123
がトレースされます。
inline string hello = "hello"; inline int number = 100 + 23; BEGIN { trace(hello); trace(number); }
インライン名は、対応する型のグローバル変数が使用される場所であればどこでも使用できます。コンパイル時に、インライン式が整数定数または文字列定数に評価された場合、スカラー配列次元など、定数式を必要とする状況でもインライン名を使用することもできます。
インライン式は、ディレクティブの評価の一環として構文エラーについてチェックされます。式の結果の型は、D代入演算子(=
)に使用されるルールに従って、inline
で定義される型と互換性を持つ型である必要があります。インライン式でinline
識別子自体を参照することはできません。再帰的定義は許可されていません。
DTraceソフトウェア・パッケージでは、Dプログラム内で使用可能なインライン・ディレクティブが含まれるシステム・ディレクトリ/usr/lib64/dtrace/installed-version
に多数のDソース・ファイルがインストールされます。
たとえば、signal.d
ライブラリには、次の形式のディレクティブが含まれます。
inline int SIGHUP = 1; inline int SIGINT = 2; inline int SIGQUIT = 3; ...
これらのインライン定義を使用すると、sigaction(2)
のマニュアル・ページに記載されている現在のOracle Linuxのシグナル名セットにアクセスできます。同様に、errno.d
ライブラリには、errno(3)
のマニュアル・ページに記載されているCのerrno
定数のインライン・ディレクティブが含まれます。
デフォルトでは、Dコンパイラには、提供されているすべてのDライブラリ・ファイルが自動的に含まれるため、これらの定義はすべてのDプログラムで使用できます。
型の名前空間
ANSI Cなどの従来の言語では、型の可視性は、型が関数や他の宣言でネストしているかどうかによって決まります。Cプログラムの外部スコープで宣言された型には、単一のグローバル名前空間が関連付けられるため、これらの型は、プログラム全体にわたって表示されます。通常、この外部スコープには、Cヘッダー・ファイルに定義された型が含まれます。Dでは、これらの言語とは異なり、複数の外部スコープの型にアクセスできます。
Dは、オペレーティング・システム・カーネル、これに関連付けられているロード可能なカーネル・モジュールのセット、システム上で実行されるユーザー・プロセスなど、ソフトウェア・スタックの複数のレイヤーにわたる動的な監視を簡略化する言語です。単一のDプログラムにより、プローブをインスタンス化し、複数のカーネル・モジュールや、独立したバイナリ・オブジェクトにコンパイルされる他のソフトウェア・エンティティからデータを収集できます。したがって、DTraceやDコンパイラで使用可能な型として、同じ名前の複数のデータ型(場合によっては定義が異なる)が存在する可能性があります。このような状況を管理するために、各型には、それを含むプログラム・オブジェクトによって識別される名前空間が関連付けられます。特定のプログラム・オブジェクトの型にアクセスするには、型名に、オブジェクト名と逆引用符(`
)によるスコープ演算子を指定します。
たとえば、foo
という名前のカーネル・モジュールに次のC型宣言が含まれるとします。
typedef struct bar { int x; } bar_t;
Dからstruct bar
やbar_t
型にアクセスするには、次のような型名を使用します。
struct foo`bar foo`bar_t
逆引用符の演算子は、型名が適切であれば、どのような状況でも使用できます。たとえば、D変数宣言の型や、Dプローブ節のキャスト式を指定する場合などです。
Dコンパイラには、それぞれ名前CとDを使用する2つの特殊な組込み型の名前空間も用意されています。Cの型の名前空間には、最初にint
などのANSI C固有の標準型が移入されます。また、Cプリプロセッサ(cpp)を使用して得られた型定義がdtrace -Cコマンドを実行して処理され、Cスコープに追加されます。この結果、コンパイル・エラーを発生させずに、別の型の名前空間ですでに参照可能な型宣言が含まれるCヘッダー・ファイルを組み込むことができます。
Dの型の名前空間には、最初に、int
やstring
などのD固有の型と、uint64_t
などのDの組込み型の別名が移入されます。Dプログラム・ソース内で使用される新しい型宣言はすべて、Dの型の名前空間に自動的に追加されます。Dプログラム内で、他の名前空間のメンバーの型から構成される複合型(struct
など)を作成すると、これらのメンバーの型は宣言によってDの名前空間にコピーされます。
逆引用符の演算子を使用して名前空間が明示的に指定されていない型宣言が検出されると、指定された型名を使用して一致する型名を検出するために、アクティブな型の名前空間が検索されます。常にCの名前空間が最初に検索され、その後でDの名前空間が検索されます。CとDのどちらの名前空間でも型名が見つからない場合、アクティブなカーネル・モジュールの型の名前空間がロード・アドレスの順に検索されます。ただし、ロード可能なモジュール間の順序付けプロパティは保証されません。ロード可能なカーネル・モジュールで定義された型にアクセスする場合は、他のカーネル・モジュールとの型名の競合を避けるために、スコープ演算子を使用する必要があります。
Dコンパイラでは、オペレーティング・システムのソース・コードに関連付けられた型に自動的にアクセスするために、Linuxのコア・カーネル・モジュールに用意されている圧縮形式のANSI Cのデバッグ情報が使用されます。これにより、対応するCのインクルード・ファイルにアクセスする必要がなくなります。このシンボリックなデバッグ情報は、システム上のすべてのカーネル・モジュールで使用可能なわけではありません。DTraceで使用することを目的とした圧縮形式のCのデバッグ情報が含まれないモジュールの名前空間内の型にアクセスしようとすると、エラーがレポートされます。