ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
![]() |
デバイスドライバの記述 Oracle Solaris 11.1 Information Library (日本語) |
パート I Oracle Solaris プラットフォーム用デバイスドライバの設計
2. Oracle Solaris カーネルとデバイスツリー
22. ドライバのコンパイル、ロード、パッケージ化、およびテスト
23. デバイスドライバのデバッグ、テスト、およびチューニング
システムはしばしば、ユーザーアクションやシステム要求など、状態の変化に応答する必要があります。たとえばデバイスは、あるコンポーネントが過熱し始めたときに警告を発したり、DVDがドライブに挿入されたときにムービープレーヤーを起動したりする可能性があります。デバイスドライバは、イベントと呼ばれる特殊なメッセージを使用することで、状態の変化が発生したことをシステムに通知します。
イベントとは、状態の変化が発生したことを示すためにデバイスドライバから対象のエンティティーに送信されるメッセージのことです。Oracle Solaris OS ではイベントはユーザー定義の名前-値ペア構造体として実装されており、この構造体は nvlist* 関数を使用して管理されます。nvlist_alloc(9F) のマニュアルページを参照してください。イベントはベンダー、クラス、およびサブクラスに基づいて編成されます。たとえば、環境の状態を監視するためのクラスを定義します。環境用のクラスには、温度、ファンのステータス、および電力の変化を示すためのサブクラスが存在する場合があります。
状態の変化が発生すると、デバイスはドライバに通知します。次に、ドライバは ddi_log_sysevent(9F) 関数を使用して、sysevent という名前のキュー内にこのイベントをロギングします。sysevent キューは、syseventd デーモンまたは syseventconfd のいずれかのデーモン経由でユーザーレベルにイベントを処理対象として渡します。これらのデーモンは、指定されたイベントの通知を受け取るように登録しているすべてのアプリケーションに、通知を送信します。
ユーザーレベルアプリケーションの設計者にとって、イベントを処理するには次の 2 つの方法があります。
アプリケーションは、libsysevent(3LIB) 内のルーチンを使用することで、特定のイベントが発生したときの通知のために syseventd デーモンに登録できます。
開発者は、イベントに応答するための独立したユーザーレベルアプリケーションを記述できます。このタイプのアプリケーションは、syseventadm(1M) に登録する必要があります。指定されたイベントを syseventconfd が検出すると、アプリケーションが実行され、そのイベントを適切に処理します。
このプロセスを示したのが次の図です。
図 5-1 イベントの plumb
デバイスドライバは ddi_log_sysevent(9F) インタフェースを使用することで、イベントの生成とそのイベントのシステムへのロギングを行います。
ddi_log_sysevent() で使用される構文は、次のとおりです。
int ddi_log_sysevent(dev_info_t *dip, char *vendor, char *class, char *subclass, nvlist_t *attr-list, sysevent_id_t *eidp, int sleep-flag);
各表記の意味は次のとおりです。
このドライバの dev_info ノードへのポインタ。
ドライバのベンダーを定義する文字列へのポインタ。他社製ドライバでは、その会社の銘柄記号や、銘柄記号と同様の永続性のある識別子を使用します。Oracle が提供するドライバでは DDI_VENDOR_SUNW が使用されます。
イベントのクラスを定義する文字列へのポインタ。class はドライバに固有の値です。クラスの例には、あるデバイスに影響を与える一連の環境状態を表す文字列があります。この値は、イベントのコンシューマが認識できるものである必要があります。
class 引数のサブセットを表すドライバ固有の文字列。たとえば、環境の状態を表すクラス内に、デバイスの温度を表すイベントサブクラスを追加できます。この値は、イベントのコンシューマが理解できるものである必要があります。
このイベントに関連する名前-値属性のリストを含む nvlist_t 構造体へのポインタ。名前-値属性はドライバごとに定義される値であり、デバイスの特定の属性や状態を表すことができます。
たとえば、CD-ROM と DVD の両方を読み取るデバイスを考えます。このデバイスには、名前が disc_type で値が cd_rom、dvd のいずれかに等しいような属性を割り当てることができます。
イベントのコンシューマは class や subclass の場合と同様に、名前-値ペアを解釈できる必要があります。
名前-値ペアや nvlist_t 構造体の詳細については、「イベント属性の定義」を参照するほか、nvlist_alloc(9F) のマニュアルページも参照してください。
属性を持たないイベントでは、この引数を NULL に設定するべきです。
sysevent_id_t 構造体のアドレス。sysevent_id_t 構造体を使用すると、イベントを一意に識別できます。ddi_log_sysevent(9F) から返されるこの構造体には、システムによって提供されるイベントシーケンス番号とタイムスタンプが含まれます。sysevent_id_t 構造体の詳細については、ddi_log_sysevent(9F) のマニュアルページを参照してください。
リソースが使用可能でない場合に呼び出し元がどのように対処するのかを示すフラグ。sleep-flag が DDI_SLEEP に設定されると、リソースが使用可能になるまでドライバがブロックされます。DDI_NOSLEEP の状態では割り当てがスリープしないため、処理の成功が保証されません。DDI_ENOMEM が返された場合、ドライバは処理をあとで再試行する必要があります。
DDI_SLEEP の状態でも、システムビジーや syseventd デーモンの不応答、割り込みコンテキストでのイベントのロギング試行など、その他のエラーがこのインタフェースから返される可能性があります。
デバイスドライバは、イベントのロギングを行うために次のタスクを実行します。
nvlist_alloc(9F) を使用して属性リスト用のメモリーを割り当てる
名前-値ペアを属性リストに追加する
ddi_log_sysevent(9F) 関数を使用して sysevent キューにイベントをロギングする
属性リストが不要になった時点で nvlist_free(9F) を呼び出す
次の例は、ddi_log_sysevent() の使用方法を示しています。
例 5-1 ddi_log_sysevent() の呼び出し
char *vendor_name = "DDI_VENDOR_JGJG" char *my_class = "JGJG_event"; char *my_subclass = "JGJG_alert"; nvlist_t *nvl; /* ... */ nvlist_alloc(&nvl, nvflag, kmflag); /* ... */ (void) nvlist_add_byte_array(nvl, propname, (uchar_t *)propval, proplen + 1); /* ... */ if (ddi_log_sysevent(dip, vendor_name, my_class, my_subclass, nvl, NULL, DDI_SLEEP)!= DDI_SUCCESS) cmn_err(CE_WARN, "error logging system event"); nvlist_free(nvl);
イベント属性は名前-値ペアのリストとして定義します。Oracle Solaris DDI には、情報を名前-値ペアとして格納するためのルーチンや構造体が用意されています。名前-値ペアは、ドライバからは不透明な nvlist_t 構造体に保持されます。名前-値ペアの値は、ブール、int、バイト、文字列、nvlist のいずれか、またはこれらのデータ型の配列になります。int は、16 ビット、32 ビット、64 ビットのいずれかとして定義できるほか、符号付き、符号なしのいずれかにすることができます。
名前-値ペアのリストを作成する手順は、次のとおりです。
nvlist_alloc(9F) を使用して nvlist_t 構造体を作成します。
nvlist_alloc () インタフェースが取る引数は次の 3 つです。
nvlp – nvlist_t 構造体へのポインタへのポインタ
nvflag – ペアの名前の一意性を示すフラグ。このフラグを NV_UNIQUE_NAME_TYPE に設定すると、新しいペアの名前と型に一致する既存のペアがすべて、リストから削除されます。このフラグを NV_UNIQUE_NAME に設定すると、型が何であっても、同じ名前を持つ既存のペアがすべて削除されます。NV_UNIQUE_NAME_TYPE を指定すると、型が異なるかぎり、名前が同じペアを複数個リストに含めることが可能となります。一方、NV_UNIQUE_NAME の場合、リストに含めることのできるペア名のインスタンスは 1 つのみになります。このフラグを設定しなかった場合、一意性のチェックは行われず、リストのコンシューマが重複への対処を担当することになります。
kmflag – カーネルメモリーの割り当てポリシーを示すフラグ。この引数を KM_SLEEP に設定すると、要求したメモリーが割り当て可能になるまでドライバがブロックされます。KM_SLEEP 割り当ての場合、スリープが発生する可能性がありますが、処理が成功することは保証されます。KM_NOSLEEP 割り当ての場合、スリープが発生しないことが保証されますが、使用可能なメモリーが現在存在しない場合には NULL が返される可能性があります。
nvlist に名前-値ペアを設定します。たとえば、文字列を追加するには、nvlist_add_string(9F) を使用します。32 ビット整数の配列を追加するには、nvlist_add_int32_array(9F) を使用します。nvlist_add_boolean(9F) のマニュアルページに、ペアを追加するためのインタフェースの完全な一覧が含まれています。
リストの割り当てを解除するには、nvlist_free(9F) を使用します。
次のサンプルコードは、名前-値リストの作成方法を示したものです。
例 5-2 名前-値ペアリストの作成およびデータ設定
nvlist_t* create_nvlist() { int err; char *str = "child"; int32_t ints[] = {0, 1, 2}; nvlist_t *nvl; err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); /* allocate list */ if (err) return (NULL); if ((nvlist_add_string(nvl, "name", str) != 0) || (nvlist_add_int32_array(nvl, "prop", ints, 3) != 0)) { nvlist_free(nvl); return (NULL); } return (nvl); }
ドライバから nvlist の要素を取得するには、nvlist_lookup_int32_array(9F) など、その型の検索用関数を使用します。検索用関数は、検索対象となるペアの名前を引数として取ります。
注 - これらのインタフェースが動作するのは、nvlist_alloc(9F) の呼び出し時に NV_UNIQUE_NAME、 NV_UNIQUE_NAME_TYPE のいずれかが指定された場合のみです。それ以外の場合は ENOTSUP が返されます。同じ名前の複数のペアをリストに含めることができないためです。
名前-値リストペアのリストは、連続するメモリー内に配置できます。このアプローチは、通知を受け取るように登録したエンティティーにリストを渡す場合に便利です。最初のステップは、リストに必要なメモリーブロックのサイズを、nvlist_size(9F) を使用して取得することです。次のステップは、nvlist_pack(9F) でリストをバッファーにパックすることです。バッファーの内容を受け取るコンシューマは、nvlist_unpack(9F) でバッファーを展開できます。
名前-値ペアを操作するための関数は、ユーザーレベル、カーネルレベルのどちらの開発者も使用できます。『man pages section 3: Library Interfaces and Headers』と『man pages section 9: DDI and DKI Kernel Functions 』の両方に、これらの関数に対する同一のマニュアルページが含まれています。名前-値ペアを処理対象とする関数の一覧については、次の表を参照してください。
表 5-1 名前-値ペアを使用するための関数
|