デバイスドライバをはじめ、デバイスにかかわるお困りごとの際はお気軽にお問い合わせください。
【 デバドラ講座30 : 遅延プロシージャコール(DPC) 】
DPCは割り込み処理を完了させるために、割り込みサービスルーチンから起動されます。DPCはDISPATCH_LEVELで処理されます。ISRルーチンはDIRQLで処理されるのでDPCはISRルーチンよりは優先度は低くなります。他のディスパッチルーチンはPASSIVE_LEVELで処理されるので、DPCは他のディスパッチルーチンよりも優先度は高くなります。従って、ドライバ内部処理の同期を行うのにとても重要です。
DPCには大きく分けてDpcForIsrとカスタムDPCの2つに分けられます。DpcForIsrはISRの処理を完了させるためにI/Oマネージャによって定義されます。カスタムDPCはドライバ開発者が任意で作成します。1つのISRに対して、DpcForIsrは1つしか定義できませんが、カスタムDPCは複数定義することができます。
DPCはオブジェクトとしてI/Oマネージャに管理されています。DPCオブジェクトは非ページプールメモリに生成する必要があります。DPCオブジェクトの構造は次のようになっています。
typedf struct _KDPC{
UCHAR Type;
UCHAR Number;
LIST_ENTRY Importance;
PKDEFERRED_ROUTINE DeferredRoutine;
PVIOD DeferredContext;
PVOID SystemArgument1;
PVOID SystemArgument2;
PULONG_PTR Lock;
} KDPC, *PKDPC, *RESTRICTED_POINTERPRKDPC;
DPCオブジェクトは一部のフィールドのみドキュメント化されています。その中でもNumberとImportanceがDPCの処理に影響を与えます。
カスタムDPC
カスタムDPCは開発者がドライバの状況に応じて設けます。カスタムDPCはドライバによって非ページプールメモリに確保されます。カスタムDPCの中でも使用方法によりタイマーDPCと一般カスタムDPCに分けられます。カスタムDPCの個数は制限されていません。カスタムDPCはDPCオブジェクトのImportanceとNumberフィールドの値を変更することができます。DprForIsrではこれらの値を変更することはできません。
DPCの重要度(Importance)の値はWINDDK.Hで列挙されています。DPCの重要度は、DPCオブジェクトをキューに入れる際に、キュー内のどこに配置するかを決定します。DPCの重要度には次の3つの値を設定できます。
- HighImportance:
- キューの先頭に配置します。
- MediumImportance:
- キューの末尾に配置します。
- LowImportance:
- キューの末尾に配置します。
HighImportanceとMediumImportanceは現在処理を行っているキューにDPCオブジェクトを配置し、処理を行えます。LowImportanceは、マルチプロセッサの環境において現在処理を行っているキューにDPCオブジェクトを配置することができません。通常、ドライバはImportanceフィールドの設定を行いません。重要度の指定がない場合、ImportanceフィールドはMediumImportanceに設定されます。DPCの重要度を設定するために、次のKeSetImportantanceDpc関数を使用します。
VOID
KeSetImportanceDpc(
IN PKDPC Dpc, //対象となるDPCオブジェクトのポインタ
IN KDPC_IMPORTANCE Importance); //DPCオブジェクトの重要度の値
Numberフィールドは、マルチプロセッサにおいて、DPCを実行するCPUの番号を指定します。DPCは指定したCPUによってのみDPCの実行が可能です。通常、ドライバはNumberフィールドの設定を行いません。CPUの指定がない場合、Numberフィールドは現在処理を行っているCPUに設定されます。Numberフィールドを設定するために次のKeSetTargetProcessorDpc関数を使用します。
VOID
KeSetTargetProcessorDpc(
IN PRKDPC Dpc, //処理対象のDPCオブジェクトへのポインタ
IN CCHAR Number); //DPCを実行するCPUの番号
カスタムDPCは、DPCオブジェクトが割り当てられた後、次の関数を使って初期化を行います。
VOID
KeInitializeDpc(
IN PRKDPC Dpc, //初期化するDPCオブジェクトのポインタ
IN PKDEFERRED_ROUTINE DeferredRoutine, //DPCルーチンへのポインタ
IN PVOID DeferredContext); //DPCルーチンへ渡すパラメタ
カスタムDPCのDPCオブジェクトをキューに配置するために次の関数を使います。
BOOLEAN
KeInsertQueueDpc(
IN PRKDPC Dpc, //キューに配置されるDPCオブジェクトへのポインタ
IN PVOID SystemArgument1, //DPCルーチンに渡す引数1
IN PVOID SystemArgument2); //DPCルーチンに渡す引数2
カスタムDPCのDPCオブジェクトを初期化する際、KeInitializeDpc関数においてDPCルーチンのポインタを引数DeferredRoutineとして与えます。DeferredRoutineは次のように定義されています。
VOID
(*PKDEFERRED_ROUTINE)(
IN struct _KDPC *Dpc, //DPCオブジェクトへのポインタ
IN PVOID DeferredContext, //DPCオブジェクト初期化字に指定した引数
IN PVOID SystemArgument1, //KeInsertQueueDpc関数でしてした引数1
IN PVOID SystemArgument2); //KeInsertQueueDpc関数でしてした引数2
DpcForIsrルーチン
DpcForIsrはI/Oマネージャが提供する割り込み処理機構です。1つのデバイスオブジェクトに対し、DpcForIsrを1つのみ設けることができます。DpcForIsrが存在しない場合もあります。DpcForIsrはISRの処理を完了するためにI/Oマネージャによって定義されます。DpcForIsrのDPCオブジェクトは、ドライバがデバイスオブジェクトを作成する際にデバイスオブジェクトの中に含まれた形で自動的に作成されます。
デバイスドライバと割り込みを接続後、DPCで割り込みを処理可能にするために、DPCオブジェクトを初期化し、DpcForIsrルーチンを登録する必要があります。DpcForIsrルーチンの登録は、次のIoInitializeDpcRequest関数を使います。
VOID
IoInitializeDpcRequest(
DeviceObject, //デバイスオブジェクトへのポインタ
DpcRoutine) //ドライバのDpcForIsrへのポインタ
実際、NTDDK.Hでは次のように定義されています。
#define IoInitializeDpcRequest(DeviceObject, DpcRoutine)(
KeInitializeDpc(&(DeviceObject)->Dpc,
(PKDEFERRED_ROUTINE)(DpcRoutine), (DeviceObject)))
DpcForIsrのDPCオブジェクトをキューに配置するために次の関数を使用します。
VOID
IoRequestDpc(
IN PDEVICE_OBJECT DeviceObject, //デバイスオブジェクトのポインタ
IN PIRP Irp, //処理未完了のIRPへのポインタ
IN PVOID Context) //DpcForIsrルーチンに渡す引数
実際、NTDDK.Hでは次のように定義されています。
#define IoRequestDpc( DeviceObject, Irp, Context)
(\KeInsertQueueDpc(&(DeviceObject)->Dpc, (Irp), (Context)))
DpcForIsrルーチンのプロトコルは次のようになっています。
VOID
(*PIO_DPC_ROUTINE)(
IN PKDPC Dpc, //DpcForIsrのためのDPCオブジェクト
IN PDEVICE_OBJECT DeviceObject, //デバイスオブジェクトのポインタ
IN PRIP Irp, //処理未完了のIRPへのポインタ
IN PVOID Context) //DpcForIsrルーチンに渡す引数
参考資料
- 「NTドライバプログラミング」
- Peter G. Viscarola、W. Anthony Mason著 久保雅俊、三浦秀朗訳 サイエンスパーク株式会社 監修
- 「WDMデバイスドライバプログラミング完全ガイド」上
- Edword N. Dekker, Jpseph M. Newcomer著 株式会社クイック訳