WDF ドライバー (KMDF または UMDF) でのデータ バッファーへのアクセス
Windows Driver Frameworks (WDF) ドライバーが読み取り、書き込み、またはデバイス I/O 制御要求を受信すると、要求オブジェクトには入力バッファー、出力バッファー、またはその両方が含まれます。
入力バッファーには、ドライバーに必要な情報が含まれています。 書き込み要求の場合、通常この情報は、関数ドライバーがデバイスに送信する必要があるデータです。 デバイス I/O 制御要求の場合、入力バッファーには、ドライバーが実行する必要がある操作の種類を示す情報が含まれている場合があります。
出力バッファーは、ドライバーから情報を受け取ります。 読み取り要求の場合、通常この情報は、関数ドライバーがデバイスから受信するデータです。 デバイス I/O 制御要求の場合、出力バッファーは、要求の I/O 制御コードで指定された状態またはその他の情報を受け取る場合があります。
ドライバーが要求のデータ バッファーにアクセスするために使用する手法は、ドライバーがデバイスのデータ バッファーにアクセスする方法によって異なります。 次の 3 つのアクセス方法があります。
- バッファー I/O。 I/O マネージャーは、ドライバーと共有する中間バッファーを作成します。
- ダイレクト I/O。 I/O マネージャーは、バッファー領域を物理メモリにロックし、バッファー領域への直接アクセスをドライバーに提供します。
- バッファー I/O でもダイレクト I/Oでもないもの。 I/O マネージャーは、要求のバッファー領域の仮想アドレスをドライバーに提供します。 I/O マネージャーは要求のバッファー領域を検証しないため、ドライバーはバッファー領域がアクセス可能であることを確認し、バッファー領域を物理メモリにロックする必要があります。
カーネルモード ドライバー フレームワーク (KMDF) ドライバーは、3 つのアクセス方法のいずれかを使用できます。 ユーザーモード ドライバー フレームワーク (UMDF) ドライバーは、読み取り、書き込み、および IOCTL 要求にバッファーまたはダイレクト I/O を使用でき、METHOD_NEITHER メソッドを指定する要求を変換できます。
バッファー アクセス方法の指定
読み取り要求と書き込み要求の場合、ドライバー スタック内のすべてのドライバーは、デバイスのバッファーにアクセスするために同じメソッドを使用する必要があります。ただし、最上位レベルのドライバーは、下位ドライバーで使用されるメソッドに関係なく、「どちらでもない」方法を使用できます。
バージョン 1.13 以降、KMDF ドライバーは、各デバイスの WdfDeviceInitSetIoTypeEx を呼び出すことによって、デバイスのすべての読み取りおよび書き込み要求のアクセス方法を指定します。 たとえば、ドライバーがあるデバイスに対してバッファー I/O メソッドを指定する場合、I/O マネージャーは、そのデバイスのドライバーに読み取りおよび書き込み要求を配信するときに、バッファー I/O メソッドを使用します。
デバイス I/O 制御要求の場合、I/O 制御コード (IOCTL) には、バッファー アクセス メソッドを指定するビットが含まれています。 その結果、KMDF ドライバーは、IOCTL のバッファリング メソッドを選択するアクションを実行する必要はありません。 IOCTL の詳細については、「I/O 制御コードの定義」を参照してください。 読み取り要求と書き込み要求とは異なり、デバイスのすべての IOCTL で同じアクセス方法を指定する必要はありません。
UMDF ドライバーは、読み取り要求と書き込み要求、およびデバイス I/O 制御要求にフレームワークが使用するアクセス メソッドの優先設定を指定します。 UMDF ドライバーが提供する値は単なる優先設定であり、フレームワークで使用される保証はありません。 詳細については、「UMDF ドライバー でのバッファー アクセス メソッドの管理」を参照してください。
UMDF ドライバーは、各デバイスの WdfDeviceInitSetIoTypeEx を呼び出すことによって、デバイスのすべての読み取り、書き込み、および IOCTL 要求のアクセス メソッドを指定します。 たとえば、ドライバーがあるデバイスに対してバッファー I/O メソッドを指定する場合、フレームワークは、そのデバイスのドライバーに読み取り、書き込み、および IOCTL 要求を配信するときに、バッファー I/O メソッドを使用します。
KMDF と UMDF の IOCTL のバッファー アクセス メソッドの違いに注意してください。 KMDF ドライバーは IOCTL のバッファー アクセス メソッドを指定しませんが、UMDF ドライバーでは IOCTL のバッファー アクセス メソッドを指定します。
I/O ターゲットが使用する I/O メソッドに対して正しくないメソッドを使用して、WDF ドライバーが I/O 要求のバッファーを記述する場合、フレームワークはバッファーの記述を修正します。 たとえばドライバーが、WdfIoTargetSendReadSynchronous に渡されるバッファーを記述するのに MDL を使用し、I/O ターゲットがバッファー I/O (MDL ではなく仮想アドレスを使用してバッファーを指定する必要がある) を使用する場合、フレームワークはバッファー記述を MDL から仮想アドレスと長さに変換します。 ただし、ドライバーが正しい形式でバッファーを指定する方が効率的です。
フレームワーク メモリ オブジェクト、ルックアサイド リスト、MDL、およびローカル バッファーについては、「メモリ バッファーの使用」を参照してください。
メモリ バッファーが削除されるタイミングについては、「メモリ バッファーのライフ サイクル」を参照してください。
バッファー I/O のデータ バッファーへのアクセス
ドライバーがバッファー I/O を使用している場合、その動作は、データ要求の種類と、KMDF または UMDF のどちらを使用しているかによって変わります。
KMDF ドライバーがバッファー I/O を使用する場合、I/O マネージャーは、ドライバーが要求のすべての種類でアクセスできる 1 つの中間バッファーを作成します。 これは次のように動作します。
- 書き込み要求。 I/O マネージャーは、ドライバー スタックを呼び出す前に、呼び出し元アプリの入力バッファーから入力情報を転送します。 次に、KMDF ドライバーは中間バッファーから入力情報を読み取り、デバイスに書き込みます。
- 読み取り要求。 KMDF ドライバーは、デバイスから情報を読み取り、中間バッファーに格納します。 次に、I/O マネージャーは中間バッファーからアプリの出力バッファーに出力データをコピーします。
- デバイス I/O の制御要求。 KMDF ドライバーは、中間バッファーとの間でその要求のデータを読み取りまたは書き込みます。
UMDF ドライバーがバッファー I/O を使用する場合、ドライバー ホスト プロセスは、要求の種類に応じて、1 つまたは 2 つの中間バッファーを作成します。 これは次のように動作します。
- 書き込み要求。 フレームワークは、1 つのバッファーを作成し、呼び出し元のアプリの入力バッファーから入力情報を転送してから、ドライバー スタックを呼び出します。 KMDF ドライバーは中間バッファーから入力情報を読み取り、デバイスに書き込みます。
- 読み取り要求。 UMDF ドライバーは、デバイスから情報を読み取り、フレームワークが作成したバッファーに格納します。 ドライバー ホスト プロセスは、中間バッファーからアプリの出力バッファーに出力データをコピーします。
- デバイス I/O の制御要求。 フレームワークは、ドライバーがアクセスできる IOCTL の入力バッファーと出力バッファーに対応する 2 つのバッファーを作成します。 フレームワークは、IOCTL から新しい中間バッファーに入力情報をコピーし、ドライバーが使用できるようにします。 フレームワークは出力バッファーの内容をコピーしないため、ドライバーはそこから読み取ろうとしないでください (そうしないと、ガベージ データが読み取られます)。 ドライバーが出力バッファーに書き込むデータは元の IOCTL バッファーにコピーされ、I/O 要求が正常に完了するとアプリに返されます。 ドライバーが入力バッファーに書き込むデータは破棄され、呼び出し元のアプリには返されないことに注意してください。
バッファーを表すフレームワーク メモリ オブジェクトへのハンドルを取得するには、KMDF ドライバーと UMDF ドライバーの両方が、読み取り要求か書き込み要求かに応じて WdfRequestRetrieveInputMemory または WdfRequestRetrieveOutputMemory を呼び出します。 次にドライバーは、WdfMemoryGetBuffer を呼び出すことによってバッファーへのポインターを取得できます。 バッファーの読み取りと書き込みを行うために、ドライバーは WdfMemoryCopyFromBuffer または WdfMemoryCopyToBuffer を呼び出します。
バッファーの仮想アドレスと長さを取得するために、ドライバーは WdfRequestRetrieveInputBuffer または WdfRequestRetrieveOutputBuffer を呼び出します。
バッファーのメモリ記述子リスト (MDL) を割り当ててビルドするために、KMDF ドライバーは WdfRequestRetrieveInputWdmMdl または WdfRequestRetrieveOutputWdmMdl を呼び出します。
ダイレクト I/O のデータ バッファーへのアクセス
ドライバーがダイレクト I/O を使用している場合、I/O マネージャーは、I/O 要求の発信元 (通常はユーザーモード アプリケーション) が指定したバッファー領域のアクセシビリティを検証し、バッファー領域を物理メモリにロックし、バッファー領域への直接アクセスをドライバーに提供します。
ドライバーがダイレクト I/O の優先設定を指定していて、ダイレクト I/O のすべての UMDF 要件が満たされている場合 (「UMDF ドライバーでのバッファー アクセス メソッドの管理」を参照)、フレームワークは I/O マネージャーから受信したメモリ バッファーを、ドライバーのホスト プロセス アドレス空間に直接マップし、ドライバーにバッファー領域への直接アクセスを提供します。
バッファー領域を表すフレームワーク メモリ オブジェクトへのハンドルを取得するには、ドライバーは WdfRequestRetrieveInputMemory または WdfRequestRetrieveOutputMemory を呼び出します。 次にドライバーは、WdfMemoryGetBuffer を呼び出すことによってバッファーへのポインターを取得できます。 バッファーの読み取りと書き込みを行うために、ドライバーは WdfMemoryCopyFromBuffer または WdfMemoryCopyToBuffer を呼び出します。
バッファー領域の仮想アドレスと長さを取得するために、ドライバーは WdfRequestRetrieveInputBuffer または WdfRequestRetrieveOutputBuffer を呼び出します。
デバイスのドライバーがダイレクト I/O を使用している場合、I/O マネージャーは MDL を使用してバッファーを記述します。 バッファーの MDL へのポインターを取得するには、KMDF ドライバーは WdfRequestRetrieveInputWdmdl または WdfRequestRetrieveOutputWdmmdl を呼び出します。 UMDF ドライバーは MDL にアクセスできません。
バッファー I/O でもダイレクト I/O でもないデータ バッファーへのアクセス
ドライバーがバッファー I/O でもダイレクト I/O でもないメソッド (「どちらもない」メソッド) と呼ばれる バッファー アクセス メソッド を使用している場合 、I/O マネージャーは、要求のバッファー領域に対して指定された I/O 要求の発信元の仮想アドレスをドライバーに提供するだけです。 I/O マネージャーは I/O 要求のバッファー領域を検証しないため、ドライバーはバッファー領域がアクセス可能であることを確認し、バッファー領域を物理メモリにロックする必要があります。
I/O マネージャーが提供する仮想アドレスには、I/O 要求の発信元のプロセス コンテキストでのみアクセスできます。 ドライバー スタック内の最上位レベルのドライバーのみが、発信元のプロセス コンテキストで実行することが保証されます。
I/O 要求のバッファー領域へのアクセスを取得するには、最上位レベルのドライバーが EvtIoInCallerContext コールバック関数を提供する必要があります。 フレームワークは、ドライバーの I/O 要求を受信するたびに、このコールバック関数を呼び出します。
要求のバッファー アクセスメソッドが 「どちらでもない」の場合、KMDF ドライバーはバッファーごとに次の操作を行う必要があります。
WdfRequestRetrieveUnsafeUserInputBuffer または WdfRequestRetrieveUnsafeUserOutputBuffer を呼び出して、バッファーの仮想アドレスを取得します。
WdfRequestProbeAndLockUserBufferForRead または WdfRequestProbeAndLockUserBufferForWrite を呼び出して、バッファーをプローブおよびロックし、バッファーのフレームワーク メモリ オブジェクトへのハンドルを取得します。
メモリ オブジェクト ハンドルを要求のコンテキスト空間に保存します。
WdfDeviceEnqueueRequest を呼び出します。これはフレームワークに要求を返します。
フレームワークはその後、ドライバーの I/O キューのいずれかに要求を追加します。 ドライバーが要求ハンドラーを提供している場合、フレームワークは最終的に適切な要求ハンドラーを呼び出します。
要求ハンドラーは、要求のコンテキスト領域から要求のメモリ オブジェクト ハンドルを取得できます。 ドライバーは、WdfMemoryGetBuffer にハンドルを渡して、バッファーのアドレスを取得することができます。
場合によっては、最上位レベルのドライバーは、ドライバーが「どちらでもない」アクセス メソッドを使用していない場合でも、ユーザーモード バッファーにアクセスするために前の手順を使用する必要があります。 たとえば、ドライバーがバッファー I/O を使用するとします。 バッファー アクセス メソッドを使用する I/O 制御コードは、ユーザーモード バッファーへの埋め込みポインターを含む構造体を渡す場合があります。 このような場合、ドライバーは、構造体からポインターを抽出し、前の手順 2 から 4 を使用する EvtIoInCallerContext コールバック関数を提供する必要があります。
UMDF では、バッファーでもダイレクト I/O 型でもないバッファーはサポートされていないため、UMDF ドライバーは、この種類のバッファーを直接処理する必要はありません。
ただし、フレームワークが I/O マネージャーからの読み取りまたは書き込みのためにこのようなバッファーを受け取る場合、ドライバーによって選択されたアクセス メソッドに応じて、バッファー I/O またはダイレクト I/O として UMDF ドライバーで使用できるようになります。 フレームワークが「どちらでもない」バッファー メソッドを指定する IOCTL を受け取った場合、必要に応じて、INF ディレクティブの存在に基づいて、IOCTL 要求のバッファー アクセス メソッドをバッファー I/O またはダイレクト I/O に変換できます。 詳細については、「UMDF ドライバー でのバッファー アクセス メソッドの管理」を参照してください。