ミニドライバー、ミニポート ドライバー、ドライバー ペア

ミニドライバーまたはミニポート ドライバーは、ドライバー ペアの片方として機能します。 (ミニポート、ポート) のようなドライバー ペアを使うと、ドライバー開発が容易になります。 ドライバー ペアでは、一方のドライバーがデバイスの集まり全体に共通する一般的なタスクを処理し、もう一方のドライバーが個々のデバイスに固有のタスクを担当します。 デバイス固有のタスクを処理するドライバーは、ミニポート ドライバー、ミニクラス ドライバー、ミニドライバーなど、さまざまな名称で呼ばれています。

Microsoft は、汎用的なドライバーを提供し、通常、個々のハードウェア ベンダーが固有のドライバーを提供します。 このトピックを読む前にまず、「デバイス ノードとデバイス スタック」と「I/O 要求パケット」で説明する概念について理解しておく必要があります。

個々のカーネル モード ドライバーでは、DriverEntry という名前の関数が実装されている必要があります。この関数は、ドライバーが読み込まれた直後に呼び出されます。 DriverEntry 関数により、DRIVER_OBJECT 構造体に含まれる特定のメンバーには、当該のドライバーがこれ以外に実装するいくつかの関数へのポインターが提供されます。 たとえば DriverEntry 関数は、次の図に示すように、DRIVER_OBJECT 構造体の Unload メンバーに、ドライバーの Unload 関数へのポインターを提供します。

DRIVER_OBJECT 構造体と Unload メンバーを示す図。

次の図のように、DRIVER_OBJECT 構造体の MajorFunction メンバーは、I/O 要求パケット (IRP) を処理する関数に対するポインターの配列になっています。 通常、ドライバーは MajorFunction 配列に含まれるいくつかのメンバーに対し、各種の IRP を処理する関数 (ドライバーで実装される) へのポインターを提供します。

DRIVER_OBJECT 構造体と MajorFunction メンバーを示す図。

IRP は、IRP_MJ_READIRP_MJ_WRITEIRP_MJ_PNP のような定数で識別されるメジャー関数コードに応じて分類することができます。 メジャー関数コードを識別する定数は、MajorFunction 配列のインデックスとしての役割を果たします。 たとえば、IRP_MJ_WRITE というメジャー関数コードを含む IRP を処理するディスパッチ関数を実装するドライバーを考えてみます。 この場合、ドライバーは、配列の MajorFunction[IRP_MJ_WRITE] 要素にディスパッチ関数へのポインターを提供する必要があります。

通常、ドライバーは MajorFunction 配列に含まれるいくつかの要素にポインターを提供し、残りの要素については、I/O マネージャーから指定される既定値を設定します。 次の例は、!drvobj デバッガー拡張機能を使って、parport ドライバーが提供する関数ポインターを調べる方法を示したものです。

0: kd> !drvobj parport 2
Driver object (fffffa80048d9e70) is for:
 \Driver\Parport
DriverEntry:   fffff880065ea070 parport!GsDriverEntry
DriverStartIo: 00000000 
DriverUnload:  fffff880065e131c parport!PptUnload
AddDevice:     fffff880065d2008 parport!P5AddDevice

Dispatch routines:
[00] IRP_MJ_CREATE                      fffff880065d49d0    parport!PptDispatchCreateOpen
[01] IRP_MJ_CREATE_NAMED_PIPE           fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       fffff880065d4a78    parport!PptDispatchClose
[03] IRP_MJ_READ                        fffff880065d4bac    parport!PptDispatchRead
[04] IRP_MJ_WRITE                       fffff880065d4bac    parport!PptDispatchRead
[05] IRP_MJ_QUERY_INFORMATION           fffff880065d4c40    parport!PptDispatchQueryInformation
[06] IRP_MJ_SET_INFORMATION             fffff880065d4ce4    parport!PptDispatchSetInformation
[07] IRP_MJ_QUERY_EA                    fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION      fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL              fffff880065d4be8    parport!PptDispatchDeviceControl
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     fffff880065d4c24    parport!PptDispatchInternalDeviceControl
[10] IRP_MJ_SHUTDOWN                    fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL                fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP                     fffff880065d4af4    parport!PptDispatchCleanup
[13] IRP_MJ_CREATE_MAILSLOT             fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY                fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER                       fffff880065d491c    parport!PptDispatchPower
[17] IRP_MJ_SYSTEM_CONTROL              fffff880065d4d4c    parport!PptDispatchSystemControl
[18] IRP_MJ_DEVICE_CHANGE               fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA                   fffff80001b6ecd4    nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP                         fffff880065d4840    parport!PptDispatchPnp

デバッガー出力を見ると、parport.sys により、ドライバーに対するエントリ ポイントとして GsDriverEntry が実装されているのがわかります。 GsDriverEntry は、ドライバーのビルド時に自動的に生成されます。この関数は、一部の初期化を行い、ドライバーの開発者によって実装された DriverEntry を呼び出します。

また、parport ドライバー (その DriverEntry 関数中) により、以下のメジャー関数コードに対し、ディスパッチ関数へのポインターが提供されていることがわかります。

  • IRP_MJ_CREATE
  • IRP_MJ_CLOSE
  • IRP_MJ_READ
  • IRP_MJ_WRITE
  • IRP_MJ_QUERY_INFORMATION
  • IRP_MJ_SET_INFORMATION
  • IRP_MJ_DEVICE_CONTROL
  • IRP_MJ_INTERNAL_DEVICE_CONTROL
  • IRP_MJ_CLEANUP
  • IRP_MJ_POWER
  • IRP_MJ_SYSTEM_CONTROL
  • IRP_MJ_PNP

MajorFunction 配列の残りの要素には、既定のディスパッチ関数 nt!IopInvalidDeviceRequest に対するポインターが保持されます。

デバッガー出力を見ると、parport ドライバーでは UnloadAddDevice に関数のポインターが提供されていますが、StartIo には関数のポインターが提供されていないことがわかります。 AddDevice は、その関数ポインターが DRIVER_OBJECT 構造体に格納されていないという意味で、特殊な関数です。 関数ポインターは、DRIVER_OBJECT 構造体の拡張機能に含まれる AddDevice メンバーに格納されています。 次の図は、DriverEntry 関数の中で parport ドライバーが提供する各関数ポインターを示したものです。 parport が提供する関数ポインターには影が付けてあります。

DRIVER_OBJECT 構造体の中の関数ポインターの図。

ドライバー ペアによる作業の効率化

Microsoft の社の内外を問わず、ドライバー開発者が Windows Driver Model (WDM) にかかわる経験を重ねるに従い、ディスパッチ関数について次のような特徴が明らかになってきました。

  • ディスパッチ関数の相当部分がスケルトン コードで構成されています。 たとえば、関数コード IRP_MJ_PNP のディスパッチ関数の大部分がすべてのドライバーに共通しています。 ハードウェアの個々の部分を制御する個別のドライバーに限定されるのは、プラグ アンド プレイ (PnP) コードの一部だけです。
  • ディスパッチ関数は複雑で、正確に理解することが困難です。 スレッド同期、IRP キュー、IRP の取り消しなどの機能を実装するには、オペレーティング システムの動作について詳しい知識が必要になります。

Microsoft では、ドライバー開発者がこうした作業を容易に行うことができるように、テクノロジ固有のドライバー モデルをいくつか考案しました。 各テクノロジ固有のモデルは一見、それぞれ異なっているように思われますが、詳しく見てみると、その多くが次の共通のパラダイムに基づいていることがわかります。

  • ドライバーは、汎用的な処理を行う部分と特定のデバイスに固有の処理を行う部分の 2 つに分けられます。
  • 汎用的な部分は Microsoft が作ります。
  • 固有の部分は、Microsoft または個々のハードウェア ベンダーが作ります。

たとえば、Proseware と Contoso の両社が WDM ドライバーを使うおもちゃのロボットを製造していると仮定します。 Microsoft では、GeneralRobot.sys という汎用ロボット ドライバーを提供しているとします。 Proseware と Contoso はいずれも、独自のロボットの要件に対応した小規模のドライバーを作ることができます。 たとえば、Proseware では ProsewareRobot.sys というドライバーを作れば、ドライバー ペア (ProsewareRobot.sys と GeneralRobot.sys) として組み合わせることにより、単一の WDM ドライバーを形成することができます。 同様に、ContosoRobot.sys と GeneralRobot.sys のドライバー ペアにより、単一の WDM ドライバーを構成できます。 最も基本的な形式として、specific.sys と general.sys (固有ドライバーと汎用ドライバー) というペアを使うことにより、ドライバーを作ることができるということです。

ドライバー ペア中の関数ポインター

specific.sys と general.sys のペアでは、Windows は specific.sys を読み込み、DriverEntry 関数を呼び出します。 specific.sys の DriverEntry 関数は、DRIVER_OBJECT 構造体に対するポインターを受け取ります。 通常、DriverEntry からは MajorFunction 配列に含まれるいくつかの要素に対して、ディスパッチ関数へのポインターが提供されることを期待します。 また、DriverEntry についても DRIVER_OBJECT 構造体に含まれる Unload メンバー (および StartIo メンバー) とドライバー オブジェクトの拡張機能に含まれる AddDevice メンバーにポインターが提供されることを期待します。 ところがドライバー ペア モデルの場合、DriverEntry は必ずしもこのようには動作しません。 つまり specific.sys の DriverEntry 関数は、general.sys で実装された初期化関数に対し、DRIVER_OBJECT 構造体を受け渡します。 次のコード例は、初期化関数を ProsewareRobot.sys と GeneralRobot.sys のペア中でどのように呼び出すことができるかを示したものです。

PVOID g_ProsewareRobottCallbacks[3] = {DeviceControlCallback, PnpCallback, PowerCallback};

// DriverEntry function in ProsewareRobot.sys
NTSTATUS DriverEntry (DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
   // Call the initialization function implemented by GeneralRobot.sys.
   return GeneralRobotInit(DriverObject, RegistryPath, g_ProsewareRobottCallbacks);
}

GeneralRobot.sys の初期化関数により、DRIVER_OBJECT 構造体 (およびその拡張機能) に含まれる該当メンバーと、MajorFunction 配列で該当する要素に対して関数ポインターが書き込まれます。 つまり I/O マネージャーからドライバー ペアに IRP が送られると、IRP はまず GeneralRobot.sys で実装されたディスパッチ関数に送られるということです。 GeneralRobot.sys が自力で IRP を処理できる場合は、固有ドライバー、ProsewareRobot.sys を使う必要はありません。 また GeneralRobot.sys が IRP の全部ではなく一部だけ処理できる場合は、ProsewareRobot.sys で実装されたいずれかのコールバック関数からの支援を利用します。 GeneralRobot.sys は、GeneralRobotInit の呼び出しの中で ProsewareRobot コールバックへのポインターを受け取ります。

DriverEntry に戻ってからある時点で、Proseware Robot デバイス ノードに対するデバイス スタックが作られます。 デバイス スタックは次のようになります。

Proseware Robot デバイス ノードの図 (デバイス スタックに AfterThought.sys (Filter DO)、ProsewareRobot.sys と GeneralRobot.sys (FDO)、および Pci.sys (PDO) という 3 つのデバイス オブジェクトがある状態)。

上の図のように、Proseware Robot のデバイス スタックには 3 つのデバイス オブジェクトがあります。 一番上のデバイス オブジェクトはフィルター デバイス オブジェクト (Filter DO) で、フィルター ドライバー AfterThought.sys に関連付けられています。 中央のデバイス オブジェクトはファンクショナル デバイス オブジェクト (FDO) で、ドライバー ペア (ProsewareRobot.sys、GeneralRobot.sys) に関連付けられています。 ドライバー ペアは、デバイス スタックのファンクション ドライバーとして機能します。 一番下のデバイス オブジェクトは物理デバイス オブジェクト (PDO) で、Pci.sys に関連付けられています。

ドライバー ペアがデバイス スタックで占有するのは 1 レベルだけで、関連付けられるデバイス オブジェクトも FDO だけであることがわかります。 GeneralRobot.sys が IRP を処理するときに ProsewareRobot.sys に支援を要求することもありますが、これは、デバイス スタックに要求を渡すこととは異なります。 ドライバー ペアは単一の WDM ドライバーを形成して、これがデバイス スタック中で 1 レベルを占有します。 ドライバー ペアは、IRP を自力で処理するか、デバイス スタック上で Pci.sys に関連付けられた PDO に IRP を受け渡します。

ドライバー ペアの例

たとえば、ワイヤレス ネットワーク カードを搭載したノート PC で、デバイス マネージャーにより、ネットワーク カードのドライバーとして netwlv64.sys が特定されたと仮定します。 !drvobj デバッガー拡張機能を使って、netwlv64.sys の関数ポインターを調べることができます。

1: kd> !drvobj netwlv64 2
Driver object (fffffa8002e5f420) is for:
 \Driver\netwlv64
DriverEntry:   fffff8800482f064 netwlv64!GsDriverEntry
DriverStartIo: 00000000 
DriverUnload:  fffff8800195c5f4 ndis!ndisMUnloadEx
AddDevice:     fffff88001940d30 ndis!ndisPnPAddDevice
Dispatch routines:
[00] IRP_MJ_CREATE                      fffff880018b5530 ndis!ndisCreateIrpHandler
[01] IRP_MJ_CREATE_NAMED_PIPE           fffff88001936f00 ndis!ndisDummyIrpHandler
[02] IRP_MJ_CLOSE                       fffff880018b5870 ndis!ndisCloseIrpHandler
[03] IRP_MJ_READ                        fffff88001936f00 ndis!ndisDummyIrpHandler
[04] IRP_MJ_WRITE                       fffff88001936f00 ndis!ndisDummyIrpHandler
[05] IRP_MJ_QUERY_INFORMATION           fffff88001936f00 ndis!ndisDummyIrpHandler
[06] IRP_MJ_SET_INFORMATION             fffff88001936f00 ndis!ndisDummyIrpHandler
[07] IRP_MJ_QUERY_EA                    fffff88001936f00 ndis!ndisDummyIrpHandler
[08] IRP_MJ_SET_EA                      fffff88001936f00 ndis!ndisDummyIrpHandler
[09] IRP_MJ_FLUSH_BUFFERS               fffff88001936f00 ndis!ndisDummyIrpHandler
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    fffff88001936f00 ndis!ndisDummyIrpHandler
[0b] IRP_MJ_SET_VOLUME_INFORMATION      fffff88001936f00 ndis!ndisDummyIrpHandler
[0c] IRP_MJ_DIRECTORY_CONTROL           fffff88001936f00 ndis!ndisDummyIrpHandler
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         fffff88001936f00 ndis!ndisDummyIrpHandler
[0e] IRP_MJ_DEVICE_CONTROL              fffff8800193696c ndis!ndisDeviceControlIrpHandler
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     fffff880018f9114 ndis!ndisDeviceInternalIrpDispatch
[10] IRP_MJ_SHUTDOWN                    fffff88001936f00 ndis!ndisDummyIrpHandler
[11] IRP_MJ_LOCK_CONTROL                fffff88001936f00 ndis!ndisDummyIrpHandler
[12] IRP_MJ_CLEANUP                     fffff88001936f00 ndis!ndisDummyIrpHandler
[13] IRP_MJ_CREATE_MAILSLOT             fffff88001936f00 ndis!ndisDummyIrpHandler
[14] IRP_MJ_QUERY_SECURITY              fffff88001936f00 ndis!ndisDummyIrpHandler
[15] IRP_MJ_SET_SECURITY                fffff88001936f00 ndis!ndisDummyIrpHandler
[16] IRP_MJ_POWER                       fffff880018c35e8 ndis!ndisPowerDispatch
[17] IRP_MJ_SYSTEM_CONTROL              fffff880019392c8 ndis!ndisWMIDispatch
[18] IRP_MJ_DEVICE_CHANGE               fffff88001936f00 ndis!ndisDummyIrpHandler
[19] IRP_MJ_QUERY_QUOTA                 fffff88001936f00 ndis!ndisDummyIrpHandler
[1a] IRP_MJ_SET_QUOTA                   fffff88001936f00 ndis!ndisDummyIrpHandler
[1b] IRP_MJ_PNP                         fffff8800193e518 ndis!ndisPnPDispatch

デバッガー出力を見ると、netwlv64.sys により、ドライバーに対するエントリ ポイントとして、GsDriverEntry が実装されているのがわかります。 GsDriverEntry は、ドライバーのビルド時に自動的に生成されます。この関数は、一部の初期化を行い、ドライバーの開発者によって作られた DriverEntry を呼び出します。

この例では、DriverEntry は netwlv64.sys により実装されますが、AddDeviceUnload、およびいくつかのディスパッチ関数は ndis.sys により実装されます。 netwlv64.sys は NDIS ミニポート ドライバー、ndis.sys は NDIS ライブラリと呼ばれます。 2 つのモジュールを組み合わせて、1 つのペア (NDIS ミニポート、NDIS ライブラリ) が形成されます。

下の図は、ワイヤレス ネットワーク カードのデバイス スタックを示します。 ドライバー ペア (netwlv64.sys と ndis.sys) がデバイス スタックで占有するのは 1 レベルだけで、関連付けられるデバイス オブジェクトも FDO だけであることがわかります。

ワイヤレス ネットワーク カードのデバイス スタックの図 (FDO に関連付けられたドライバー ペアとして netwlv64.sys と ndis.sys、および PDO に関連付けられた Pci.sys)。

利用可能なドライバー ペア

各種のテクノロジ固有のドライバー モデルで、ドライバー ペアの固有部分と汎用部分に対してさまざまな名前が使われます。 多くの場合、ペアの固有部分には "ミニ" というプレフィックスが付きます。以下に、利用可能なペア (固有ドライバー、汎用ドライバー) の一部を示します。

  • (ディスプレイ ミニポート ドライバー、ディスプレイ ポート ドライバー)
  • (オーディオ ミニポート ドライバー、オーディオ ポート ドライバー)
  • (ストレージ ミニポート ドライバー、ストレージ ポート ドライバー)
  • (バッテリ ミニクラス ドライバー、バッテリ クラス ドライバー)
  • (HID ミニドライバー、HID クラス ドライバー)
  • (チェンジャー ミニクラス ドライバー、チェンジャー ポート ドライバー)
  • (NDIS ミニポート ドライバー、NDIS ライブラリ)

一覧からわかるように、モデルの一部ではドライバー ペアの汎用部分について "クラス ドライバー" という用語が使われています。 この種のクラス ドライバーは、スタンドアロンのクラス ドライバーやクラス フィルター ドライバーとは異なります。

すべてのドライバー開発者のための概念

デバイス ノードとデバイス スタック

ドライバー スタック