SCIENCE PARK

デバドラ講座

【 デバドラ講座21 : ドライバエントリの処理 】

DriverEntryルーチンはドライバのロード後必ず呼び出されるルーチンです。デバイスの種類によって多少の違いはありますが、次のような処理が行われます。

1. ディスパッチルーチンなどの準備

ドライバに使うディスパッチルーチンや他のルーチンのポインタが準備されます。

DriverObject->DriverUnload =「アンロードルーチン」(必須)
DriverObject->MajorFunction[IRP_MJ_XXX] =「ディスパッチルーチン」 (必要に応じて複数存在)
DriverObject->DriverExtension->AddDevice  = 「AddDeviceルーチン」(必須)
DriverObject->MajorFunction[IRP_MJ_PNP] = 「PnPディスパッチルーチン」(必須)
DriverObject->MajorFunction[IRP_MJ_POWER]  = 「Powerマネジメントディスパッチルーチン」(必須)
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = 「WMIのサポートディスバッチルーチン」(必要に応じて)

2. 物理デバイスの確認

ドライバエントリではまず、ドライバが制御するデバイスの物理デバイスを確認します。PnPマネージャが行います。

3. デバイスオブジェクトの作成

デバイスが見つかると、そのデバイスをソフト的に表現する必要があります。そのためにデバイスオブジェクトがドライバによって非ページプールメモリに作成されます。デバイスオブジェクトはデバイスのすべての状態を保持しています。I/Oマネージャはデバイスにアクセスする際、デバイスオブジェクトを使用します。ドライバは物理デバイスの存在の有無に関わらずデバイスオブジェクトを作成する必要があります。物理デバイスが存在しない場合、デバイスオブジェクトの作成は失敗します。デバイスオブジェクトの構造体を次に示します。

NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT                 DriverObject,
IN ULONG                                     DeviceExtensionSize,
IN PUNICODE_STRING               DeviceName,
IN DEVICE_TYPE                         DeviceType,
IN ULONG                                     DeviceCharacteristics,
IN BOOLEAN                                Exclusive,
OUT PDEVICE_OBJECT *            DeviceObject );
DriverObject:
ドライバオブジェクトへのポインタです。この引数はDriverEntryまたはAddDevice関数の引数をそのまま使用します。
DeviceExtensionSize:
デバイスエクステンションのサイズです。デバイスエクステンションとはデバイスオブジェクトと共に確保されるデバイスに関する構造体であり、非ページプールメモリに作られます。ドライバ開発者はこの引数を指定する必要があります。
DeviceName:
カーネルがデバイスを認識する際に使用するデバイス名です。この名称はUnicode文字列でなければなりません。開発者はこの引数を指定する必要があります。
DeviceType :
ディスク、シリアルポートなどの標準デバイスにはWINDDK.Hに定義したFILE_DEVICE_XXXを使います。カスタムデバイスには32768~65535を使います。この値はデバイスI/Oコントロールコードを定義するCTL_CODEマクロの値と一致する必要があります。例えば、キーボードに関するドライバを作成する場合はWINDDK.Hに定義されたFILE_DEVICE_KEYBOARDを使用します。
DeviceCharacteristics:
デバイス全体の特徴を示します。標準デバイスはWINDDK.Hに定義され、例えばFILE_READ_ONLY_DEVICEといったようにFILE_XXX_XXXの形式で表されます。カスタムデバイスは0にセットします。
Exclusive:
Trueを設定した時、I/Oマネージャによって1度に1つのハンドルしか開くことができません。Falseを設定した時にこの制限がありません。
DeviceObject:

この関数が成功した時、作成されたデバイスオブジェクへのポインタがセットされます。デバイスオブジェクト作成終了後に次のコードを追加し、OSにデバイスオブジェクトの作成完了を通知する必要があります。

deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

バスドライバがパワーマネジメントのディスパッチルーチンをサポートするためには必要に応じて、DO_POWER_PAGABLE、DO_POWER_INRUSH,、DO_POWER_NOOPのフラグを設定する必要があります。ドライバによってデバイス名を指定する必要がない場合があります。デバイスオブジェクトを作成時にデバイス名を指定しない場合、ドライバインタフェースを使用します。ドライバインタフェースはIoRegisterDeviceInterface関数を使って設定します。この機能は比較的複雑なので説明は省略します。

4. デバイスのシンボリックリンク

アプリケーションからデバイスにアクセスするには、そのデバイスのシンボリックリンク名が必要となります。デバイスオブジェクトで定義されたデバイス名はUnicode型であり、アプリケーションから認識できません。ユーザーモードから認識されるには、ASCII型でなければなりません。ユーザーモードからアプリケーションが識別できる名前にリンクする必要があり、これをデバイス名のシンボリックリンクと言います。シンボリックを作成するためには次の二つの関数を使用します。

1.RtlInitUnicodeString

RtlInitUnicodeString関数はDestinationStringの示す領域を初期化し、SourceStringの内容を指すように設定されています。
 
VOID RtlInitUnicodeString(
    IN OUT PUNICODE_STRING      DestinationString,
    PCWSTR                                        SourceString
);
DestinationString:
Win32のデバイス名のポインタです。
SourceString:
ネイティブNTのデバイス名のポインタです。

2.IoCreateSymbolicLink

IoCreateSymbolicLink関数はユーザーから参照可能な名前のシンボリックリンクを作成します。

NTSTATUS IoCreateSymbolicLink(
    IN PUNICODE_STRING SymbolicLinkName,
    IN PUNICODE_STRING DeviceName
);
SymblicLinkName:
作成するシンボリックリンク名を表すUnicode文字列へのポインタです。
DeviceName:
作成されるシンボリックリンクに対応するネイティブNTデバイス名へのポインタです。

実際にこれらの二つの関数がどのようにシンボリックリンクを作成するか次に示します。

RtlInitUnicodeString(&linkName, L"\\??\\MyDevice");
code = IoCreateSymbolicLink(&linkName, &devName);
Handle = CreateFile ("\\\\.\\MyDevice",
                            GENERIC_READ,
                            0,
                            0,
                            OPEN_EXISTING,
                            0,
                            0);

CreateFile関数を呼ぶ際Win32のデバイス名は上のような形で使われています。デバイス名が指定されていないドライバはシンボリックを作成する必要はありません。シンボリックリンクを作成しない場合は特殊ですので、ここでは説明を省略します。シンボリック名とデバイスのネイティブNT名に関連性は必要ありません。また、1つのネイティブNTデバイスに複数のシンボリックリンクを張ることも可能です。

5. デバイスリソースの割り当て

ドライバのハードウェアリソースが識別されると、次にドライバはI/Oマネージャからそのリソースを割り当てようとします。この作業はPnPディスパッチルーチンのマイナー関数[IRP_MN_START_DEVICE]で処理されます。デバイスリソースの割り当てには次の3つの関数を使用します。

  • IoAssignResources関数
  • IoReportResourceUsage関数
  • HalAssignSlotResources関数

現在ほとんどのドライバはIoAssignResources関数を用いてリソースの割り当てを行っています。HalAssignSlotResources関数はPCIバス専用の関数です。新しくドライバを書く場合はIoAssignResources関数を使うべきです。

6. アダプタオブジェクトの取得

初期化終了前にDMAドライバは、デバイスが必要とするマップレジスタの最大数を示さなければなりません。DMA転送可能なデバイスのドライバはここでアダプタオブジェクトを取得し、処理します。詳細の説明は省略します。この処理はディスパッチルーチンのマイナー関数[IRP_MN_START_DEVICE]で処理します。

7. デバイスの初期化処理

デバイスの初期化はPnPディスパッチルーチンのマイナー関数[IRP_MN_START_DEVICE]で処理します。初期化に2秒以上かかるデバイスはここで再初期化ルーチンを登録します。

8. DriverEntryルーチンのまとめ

DriverEntryルーチンで実行される処理を図1にまとめます。処理が行われる順番は上の①~⑦ではなく任意です。

図1.Driver処理図
図1.Driver処理図

参考資料

「NTドライバプログラミング」
Peter G. Viscarola、W. Anthony Mason著 久保雅俊、三浦秀朗訳 サイエンスパーク株式会社 監修
「WDMデバイスドライバプログラミング完全ガイド」下
Edword N. Dekker, Jpseph M. Newcomer著 株式会社クイック訳
「WDMデバイスドライバ」
Chris Cant著 エクストランス株式会社訳 サイエンスパーク株式会社 監修
page up