Skip to content

デバドラ講座

開発27 遅延プロシージャコール(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オブジェクトの構造は次のようになっています。

typedef struct _KDPC {
    UCHAR Type;
    UCHAR Number;
    LIST_ENTRY Importance;
    PKDEFERRED_ROUTINE DeferredRoutine;
    PVOID DeferredContext;
    PVOID SystemArgument1;
    PVOID SystemArgument2;
    PULONG_PTR Lock;
} KDPC, *PKDPC, *RESTRICTED_POINTER PRKDPC;

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(
    PDEVICE_OBJECT DeviceObject,       // デバイスオブジェクトへのポインタ
    PKDEFERRED_ROUTINE 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 PIRP Irp, // 処理未完了のIRPへのポインタ
    IN PVOID Context // DpcForIsrルーチンに渡す引数
);


参考資料

「NTドライバプログラミング」
Peter G. Viscarola、W. Anthony Mason著 久保雅俊、三浦秀朗訳 サイエンスパーク株式会社 監修
「Windows NTデバイスドライバプログラミング」
アート・ベーカー著 WinProgDDK ML和訳プロジェクト訳
「WDMデバイスドライバプログラミング完全ガイド」上
Edward N. Dekker, Joseph M. Newcomer著 株式会社クイック訳