Windows ドライバー モデル (WDM) のドライバーは、通常、他のドライバーに入出力要求パケット (IRP) を送信します。 ドライバーは、独自の IRP を作成して下位のドライバーに送信するか、上位に接続されている別のドライバーから受信した IRP を転送します。
この記事では、ドライバーが下位ドライバーに IRP を送信するさまざまな方法について説明し、注釈付きのサンプル コードを示します。
- シナリオ 1~5 では、ディスパッチ ルーチンから下位ドライバーに IRP を転送する方法について説明します。
- シナリオ 6~12 では、IRP を作成して別のドライバーに送信するさまざまな方法について説明します。
さまざまなシナリオを検討するための前提知識として、IRP 完了ルーチンは STATUS_MORE_PROCESSING_REQUIRED または STATUS_SUCCESS を返すことに注意してください。
I/O マネージャーは、ステータスを調べるときに次のルールを使用します。
- ステータスが STATUS_MORE_PROCESSING_REQUIRED の場合、IRP の完了を停止し、スタック位置を変更せずに戻ります。
- ステータスが STATUS_MORE_PROCESSING_REQUIRED 以外の場合は、IRP の完了を上向きに続けます。
I/O マネージャは、STATUS_MORE_PROCESSING_REQUIRED 以外のどの値が使用されているかを知る必要がないため、STATUS_SUCCESS を使用します (値 0 はほとんどのプロセッサ アーキテクチャで効率的に読み込まれるため)。
次のコードを読み取ると、WDK では STATUS_CONTINUE_COMPLETION が STATUS_SUCCESSにエイリアス化されることに注意してください。
// This value should be returned from completion routines to continue
// completing the IRP upwards. Otherwise, STATUS_MORE_PROCESSING_REQUIRED
// should be returned.
//
#define STATUS_CONTINUE_COMPLETION STATUS_SUCCESS
//
// Completion routines can also use this enumeration instead of status codes.
//
typedef enum _IO_COMPLETION_ROUTINE_RESULT {
ContinueCompletion = STATUS_CONTINUE_COMPLETION,
StopCompletion = STATUS_MORE_PROCESSING_REQUIRED
} IO_COMPLETION_ROUTINE_RESULT, *PIO_COMPLETION_ROUTINE_RESULT;
IRP を別のドライバーに転送
シナリオ 1: 転送のみを行う
ドライバーが IRP を転送し、追加のアクションを実行しない場合は、次のコードを使用します。 この場合、ドライバーは完了ルーチンを設定する必要はありません。 ドライバーが最上位ドライバーの場合は、下位のドライバーによって返される状態に応じて、IRP を同期的または非同期的に完了できます。
NTSTATUS
DispatchRoutine_1(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//
// You are not setting a completion routine, so just skip the stack
// location because it provides better performance.
//
IoSkipCurrentIrpStackLocation (Irp);
return IoCallDriver(TopOfDeviceStack, Irp);
}
シナリオ 2: 転送して待機する
ドライバーが下位ドライバーに IRP を転送し、戻りを待って IRP を処理する場合は、次のコードを使用します。 これは、PNP IRP を処理するときによく行われます。 たとえば、IRP_MN_START_DEVICE IRP を受け取った場合、IRP をバス ドライバーに転送し、その処理が完了するのを待ってデバイスを起動する必要があります。 IoForwardIrpSynchronously を呼び出すと、この操作を簡単に実行できます。
NTSTATUS
DispatchRoutine_2(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
KEVENT event;
NTSTATUS status;
KeInitializeEvent(&event, NotificationEvent, FALSE);
//
// You are setting completion routine, so you must copy
// current stack location to the next. You cannot skip a location
// here.
//
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,
CompletionRoutine_2,
&event,
TRUE,
TRUE,
TRUE
);
status = IoCallDriver(TopOfDeviceStack, Irp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&event,
Executive, // WaitReason
KernelMode, // must be Kernelmode to prevent the stack getting paged out
FALSE,
NULL // indefinite wait
);
status = Irp->IoStatus.Status;
}
// <---- Do your own work here.
//
// Because you stopped the completion of the IRP in the CompletionRoutine
// by returning STATUS_MORE_PROCESSING_REQUIRED, you must call
// IoCompleteRequest here.
//
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return status;
}
NTSTATUS
CompletionRoutine_2(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
if (Irp->PendingReturned == TRUE) {
//
// You will set the event only if the lower driver has returned
// STATUS_PENDING earlier. This optimization removes the need to
// call KeSetEvent unnecessarily and improves performance because the
// system does not have to acquire an internal lock.
//
KeSetEvent ((PKEVENT) Context, IO_NO_INCREMENT, FALSE);
}
// This is the only status you can return.
return STATUS_MORE_PROCESSING_REQUIRED;
}
シナリオ 3: 完了ルーチンを使用して転送する
以下のコードでは、ドライバーが完了ルーチンを設定し、IRP を下位ドライバーに転送した後、下位ドライバーのステータスをそのまま返します。 完了ルーチンを設定するのは、IRP の内容を戻る途中で変更するためです。
NTSTATUS
DispatchRoutine_3(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
NTSTATUS status;
//
// Because you are setting completion routine, you must copy the
// current stack location to the next. You cannot skip a location
// here.
//
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,
CompletionRoutine_31,// or CompletionRoutine_32
NULL,
TRUE,
TRUE,
TRUE
);
return IoCallDriver(TopOfDeviceStack, Irp);
}
ディスパッチ ルーチンから下位ドライバーの状態を返す場合:
- 完了ルーチンで IRP の状態を変更することはできません。 これは、IRP の IoStatus ブロック (Irp->IoStatus.Status) に設定された状態値が、下位ドライバーの戻り状態と同じであることを確認するためです。
- Irp->PendingReturned で示される IRP の保留ステータスを伝達する必要があります。
- IRP の同期性を変更しないでください。
結果として、このシナリオで有効な完了ルーチンのバージョンは 2 つだけです (31 と 32)。
NTSTATUS
CompletionRoutine_31 (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
//
// Because the dispatch routine is returning the status of lower driver
// as is, you must do the following:
//
if (Irp->PendingReturned) {
IoMarkIrpPending( Irp );
}
return STATUS_CONTINUE_COMPLETION ; // Make sure of same synchronicity
}
NTSTATUS
CompletionRoutine_32 (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
//
// Because the dispatch routine is returning the status of lower driver
// as is, you must do the following:
//
if (Irp->PendingReturned) {
IoMarkIrpPending( Irp );
}
//
// To make sure of the same synchronicity, complete the IRP here.
// You cannot complete the IRP later in another thread because the
// the dispatch routine is returning the status returned by the lower
// driver as is.
//
IoCompleteRequest( Irp, IO_NO_INCREMENT);
//
// Although this is an unusual completion routine that you rarely see,
// it is discussed here to address all possible ways to handle IRPs.
//
return STATUS_MORE_PROCESSING_REQUIRED;
}
シナリオ 4: キューに登録して後から処理する、または転送して再利用する
ドライバーが、IRP をキューに入れて後から処理するか、IRP を下位ドライバーに転送し、特定の回数だけ再利用してから IRP を完了する場合は、次のコード スニペットを使用します。 IRP は後から別のスレッドで完了されるため、ディスパッチ ルーチンは IRP を保留中としてマークし、STATUS_PENDING を返します。 このシナリオは前のシナリオとは異なり、完了ルーチンが必要に応じて IRP の状態を変更できます。
NTSTATUS
DispathRoutine_4(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
NTSTATUS status;
//
// You mark the IRP pending if you are intending to queue the IRP
// and process it later. If you are intending to forward the IRP
// directly, use one of the methods discussed earlier in this article.
//
IoMarkIrpPending( Irp );
//
// For demonstration purposes: this IRP is forwarded to the lower driver.
//
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,
CompletionRoutine_41, // or CompletionRoutine_42
NULL,
TRUE,
TRUE,
TRUE
);
IoCallDriver(TopOfDeviceStack, Irp);
//
// Because you marked the IRP pending, you must return pending,
// regardless of the status of returned by IoCallDriver.
//
return STATUS_PENDING ;
}
完了ルーチンは、STATUS_CONTINUE_COMPLETION または STATUS_MORE_PROCESSING_REQUIRED を返すことができます。 STATUS_MORE_PROCESSING_REQUIRED を返すのは、IRP を別のスレッドから再利用し、後で完了する場合にのみです。
NTSTATUS
CompletionRoutine_41(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
//
// By returning STATUS_CONTINUE_COMPLETION , you are relinquishing the
// ownership of the IRP. You cannot touch the IRP after this.
//
return STATUS_CONTINUE_COMPLETION ;
}
NTSTATUS
CompletionRoutine_42 (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
//
// Because you are stopping the completion of the IRP by returning the
// following status, you must complete the IRP later.
//
return STATUS_MORE_PROCESSING_REQUIRED ;
}
シナリオ 5: ディスパッチ ルーチンで IRP を完了する
このシナリオでは、ディスパッチ ルーチンで IRP を完了する方法を示します。
重要: ディスパッチ ルーチンで IRP を完了させる場合、ディスパッチ ルーチンの戻りステータスは、IRP の IoStatus ブロックに設定されている値のステータス (Irp->IoStatus.Status) と一致する必要があります。
NTSTATUS
DispatchRoutine_5(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//
// <-- Process the IRP here.
//
Irp->IoStatus.Status = STATUS_XXX;
Irp->IoStatus.Information = YYY;
IoCompletRequest(Irp, IO_NO_INCREMENT);
return STATUS_XXX;
}
IRP の作成と別のドライバーへの送信
はじめに
さまざまなシナリオを検討するための前提知識として、ドライバーが作成する同期入出力要求パケット (IRP) と非同期要求の違いを理解しておく必要があります。
同期 (スレッド化) IRP | 非同期 (非スレッド化) IRP |
---|---|
IoBuildSynchronousFsdRequest または IoBuildDeviceIoControlRequest を使用して作成されます。 | IoBuildAsynchronousFsdRequest または IoAllocateIrp を使用して作成されます。 これはドライバー間の通信用です。 |
スレッドは、IRP が完了するまで待機する必要があります。 | スレッドは、IRP が完了するまで待機する必要はありません。 |
作成したスレッドに関連付けられているため、別名、スレッド化 IRP と呼ばれます。 したがって、スレッドが終了すると、I/O マネージャーは IRP を取り消します。 | 作成したスレッドに関連付けされていません。 |
任意のスレッド コンテキストでは作成できません。 | スレッドが IRP の完了を待機しないため、任意のスレッド コンテキストで作成できます。 |
I/O マネージャーは、IRP に関連付けられているバッファーを解放する事後完了を行います。 | I/O マネージャーは、クリーンアップを実行できません。 ドライバーは、完了ルーチンを提供し、IRP に関連付けられているバッファーを解放する必要があります。 |
PASSIVE_LEVEL と同じ IRQL レベルで送信する必要があります。 | ターゲット ドライバーのディスパッチ ルーチンがDISPATCH_LEVEL で要求を処理できる場合は、IRQL でDISPATCH_LEVEL以下で送信できます。 |
シナリオ 6: IoBuildDeviceIoControlRequest を使用して同期デバイス制御要求 (IRP_MJ_INTERNAL_DEVICE_CONTROL/IRP_MJ_DEVICE_CONTROL) を送信する
次のコードは、IoBuildDeviceIoControlRequest 要求を呼び出して同期 IOCTL 要求を行う方法を示しています。 詳細については、「IRP_MJ_INTERNAL_DEVICE_CONTROL」と「IRP_MJ_DEVICE_CONTROL」を参照してください。
NTSTATUS
MakeSynchronousIoctl(
IN PDEVICE_OBJECT TopOfDeviceStack,
IN ULONG IoctlControlCode,
PVOID InputBuffer,
ULONG InputBufferLength,
PVOID OutputBuffer,
ULONG OutputBufferLength
)
/*++
Arguments:
TopOfDeviceStack-
IoctlControlCode - Value of the IOCTL request
InputBuffer - Buffer to be sent to the TopOfDeviceStack
InputBufferLength - Size of buffer to be sent to the TopOfDeviceStack
OutputBuffer - Buffer for received data from the TopOfDeviceStack
OutputBufferLength - Size of receive buffer from the TopOfDeviceStack
Return Value:
NT status code
--*/
{
KEVENT event;
PIRP irp;
IO_STATUS_BLOCK ioStatus;
NTSTATUS status;
//
// Creating Device control IRP and send it to the another
// driver without setting a completion routine.
//
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest (
IoctlControlCode,
TopOfDeviceStack,
InputBuffer,
InputBufferLength,
OutputBuffer,
OutputBufferLength,
FALSE, // External
&event,
&ioStatus);
if (NULL == irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
status = IoCallDriver(TopOfDeviceStack, irp);
if (status == STATUS_PENDING) {
//
// You must wait here for the IRP to be completed because:
// 1) The IoBuildDeviceIoControlRequest associates the IRP with the
// thread and if the thread exits for any reason, it would cause the IRP
// to be canceled.
// 2) The Event and IoStatus block memory is from the stack and we
// cannot go out of scope.
// This event will be signaled by the I/O manager when the
// IRP is completed.
//
status = KeWaitForSingleObject(
&event,
Executive, // wait reason
KernelMode, // To prevent stack from being paged out.
FALSE, // You are not alertable
NULL); // No time out !!!!
status = ioStatus.Status;
}
return status;
}
シナリオ 7: 同期デバイス制御 (IOCTL) 要求を送信し、一定時間内に完了しない場合はキャンセルする
このシナリオは前のシナリオと似ていますが、要求が完了するまで無期限に待機するのではなく、ユーザーが指定した時間だけ待機し、待機がタイムアウトした場合に IOCTL 要求を安全に取り消す点が異なります。
typedef enum {
IRPLOCK_CANCELABLE,
IRPLOCK_CANCEL_STARTED,
IRPLOCK_CANCEL_COMPLETE,
IRPLOCK_COMPLETED
} IRPLOCK;
//
// An IRPLOCK allows for safe cancellation. The idea is to protect the IRP
// while the canceller is calling IoCancelIrp. This is done by wrapping the
// call in InterlockedExchange(s). The roles are as follows:
//
// Initiator/completion: Cancelable --> IoCallDriver() --> Completed
// Canceller: CancelStarted --> IoCancelIrp() --> CancelCompleted
//
// No cancellation:
// Cancelable-->Completed
//
// Cancellation, IoCancelIrp returns before completion:
// Cancelable --> CancelStarted --> CancelCompleted --> Completed
//
// Canceled after completion:
// Cancelable --> Completed -> CancelStarted
//
// Cancellation, IRP completed during call to IoCancelIrp():
// Cancelable --> CancelStarted -> Completed --> CancelCompleted
//
// The transition from CancelStarted to Completed tells the completer to block
// postprocessing (IRP ownership is transferred to the canceller). Similarly,
// the canceller learns it owns IRP postprocessing (free, completion, etc)
// during a Completed->CancelCompleted transition.
//
NTSTATUS
MakeSynchronousIoctlWithTimeOut(
IN PDEVICE_OBJECT TopOfDeviceStack,
IN ULONG IoctlControlCode,
PVOID InputBuffer,
ULONG InputBufferLength,
PVOID OutputBuffer,
ULONG OutputBufferLength,
IN ULONG Milliseconds
)
/*++
Arguments:
TopOfDeviceStack -
IoctlControlCode - Value of the IOCTL request.
InputBuffer - Buffer to be sent to the TopOfDeviceStack.
InputBufferLength - Size of buffer to be sent to the TopOfDeviceStack.
OutputBuffer - Buffer for received data from the TopOfDeviceStack.
OutputBufferLength - Size of receive buffer from the TopOfDeviceStack.
Milliseconds - Timeout value in Milliseconds
Return Value:
NT status code
--*/
{
NTSTATUS status;
PIRP irp;
KEVENT event;
IO_STATUS_BLOCK ioStatus;
LARGE_INTEGER dueTime;
IRPLOCK lock;
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest (
IoctlControlCode,
TopOfDeviceStack,
InputBuffer,
InputBufferLength,
OutputBuffer,
OutputBufferLength,
FALSE, // External ioctl
&event,
&ioStatus);
if (irp == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
lock = IRPLOCK_CANCELABLE;
IoSetCompletionRoutine(
irp,
MakeSynchronousIoctlWithTimeOutCompletion,
&lock,
TRUE,
TRUE,
TRUE
);
status = IoCallDriver(TopOfDeviceStack, irp);
if (status == STATUS_PENDING) {
dueTime.QuadPart = -10000 * Milliseconds;
status = KeWaitForSingleObject(
&event,
Executive,
KernelMode,
FALSE,
&dueTime
);
if (status == STATUS_TIMEOUT) {
if (InterlockedExchange((PVOID)&lock, IRPLOCK_CANCEL_STARTED) == IRPLOCK_CANCELABLE) {
//
// You got it to the IRP before it was completed. You can cancel
// the IRP without fear of losing it, because the completion routine
// does not let go of the IRP until you allow it.
//
IoCancelIrp(irp);
//
// Release the completion routine. If it already got there,
// then you need to complete it yourself. Otherwise, you got
// through IoCancelIrp before the IRP completed entirely.
//
if (InterlockedExchange(&lock, IRPLOCK_CANCEL_COMPLETE) == IRPLOCK_COMPLETED) {
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
}
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
ioStatus.Status = status; // Return STATUS_TIMEOUT
} else {
status = ioStatus.Status;
}
}
return status;
}
NTSTATUS
MakeSynchronousIoctlWithTimeOutCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PLONG lock;
lock = (PLONG) Context;
if (InterlockedExchange(lock, IRPLOCK_COMPLETED) == IRPLOCK_CANCEL_STARTED) {
//
// Main line code has got the control of the IRP. It will
// now take the responsibility of completing the IRP.
// Therefore...
return STATUS_MORE_PROCESSING_REQUIRED;
}
return STATUS_CONTINUE_COMPLETION ;
}
シナリオ 8: IoBuildSynchronousFsdRequest を使用して同期非 IOCTL 要求を送信する - 完了ルーチンはSTATUS_CONTINUE_COMPLETION を返す
次のコードは、IoBuildSynchronousFsdRequest を呼び出して同期非 IOCTL 要求を行う方法を示しています。 ここで示す手法は、シナリオ 6 に似ています。
NTSTATUS
MakeSynchronousNonIoctlRequest (
PDEVICE_OBJECT TopOfDeviceStack,
PVOID WriteBuffer,
ULONG NumBytes
)
/*++
Arguments:
TopOfDeviceStack -
WriteBuffer - Buffer to be sent to the TopOfDeviceStack.
NumBytes - Size of buffer to be sent to the TopOfDeviceStack.
Return Value:
NT status code
--*/
{
NTSTATUS status;
PIRP irp;
LARGE_INTEGER startingOffset;
KEVENT event;
IO_STATUS_BLOCK ioStatus;
PVOID context;
startingOffset.QuadPart = (LONGLONG) 0;
//
// Allocate memory for any context information to be passed
// to the completion routine.
//
context = ExAllocatePoolWithTag(NonPagedPool, sizeof(ULONG), 'ITag');
if(!context) {
return STATUS_INSUFFICIENT_RESOURCES;
}
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildSynchronousFsdRequest(
IRP_MJ_WRITE,
TopOfDeviceStack,
WriteBuffer,
NumBytes,
&startingOffset, // Optional
&event,
&ioStatus
);
if (NULL == irp) {
ExFreePool(context);
return STATUS_INSUFFICIENT_RESOURCES;
}
IoSetCompletionRoutine(irp,
MakeSynchronousNonIoctlRequestCompletion,
context,
TRUE,
TRUE,
TRUE);
status = IoCallDriver(TopOfDeviceStack, irp);
if (status == STATUS_PENDING) {
status = KeWaitForSingleObject(
&event,
Executive,
KernelMode,
FALSE, // Not alertable
NULL);
status = ioStatus.Status;
}
return status;
}
NTSTATUS
MakeSynchronousNonIoctlRequestCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
if (Context) {
ExFreePool(Context);
}
return STATUS_CONTINUE_COMPLETION ;
}
シナリオ 9: IoBuildSynchronousFsdRequest を使用して同期非 IOCTL 要求を送信する - 完了ルーチンは STATUS_MORE_PROCESSING_REQUIRED を返す
このシナリオとシナリオ 8 の唯一の違いは、完了ルーチンが STATUS_MORE_PROCESSING_REQUIRED を返す点です。
NTSTATUS MakeSynchronousNonIoctlRequest2(
PDEVICE_OBJECT TopOfDeviceStack,
PVOID WriteBuffer,
ULONG NumBytes
)
/*++ Arguments:
TopOfDeviceStack
WriteBuffer - Buffer to be sent to the TopOfDeviceStack.
NumBytes - Size of buffer to be sent to the TopOfDeviceStack.
Return Value:
NT status code
--*/
{
NTSTATUS status;
PIRP irp;
LARGE_INTEGER startingOffset;
KEVENT event;
IO_STATUS_BLOCK ioStatus;
BOOLEAN isSynchronous = TRUE;
startingOffset.QuadPart = (LONGLONG) 0;
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildSynchronousFsdRequest(
IRP_MJ_WRITE,
TopOfDeviceStack,
WriteBuffer,
NumBytes,
&startingOffset, // Optional
&event,
&ioStatus
);
if (NULL == irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
IoSetCompletionRoutine(irp,
MakeSynchronousNonIoctlRequestCompletion2,
(PVOID)&event,
TRUE,
TRUE,
TRUE);
status = IoCallDriver(TopOfDeviceStack, irp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE, // Not alertable
NULL);
status = irp->IoStatus.Status;
isSynchronous = FALSE;
}
//
// Because you have stopped the completion of the IRP, you must
// complete here and wait for it to be completed by waiting
// on the same event again, which will be signaled by the I/O
// manager.
// NOTE: you cannot queue the IRP for
// reuse by calling IoReuseIrp because it does not break the
// association of this IRP with the current thread.
//
KeClearEvent(&event);
IoCompleteRequest(irp, IO_NO_INCREMENT);
//
// We must wait here to prevent the event from going out of scope.
// I/O manager will signal the event and copy the status to our
// IoStatus block for synchronous IRPs only if the return status is not
// an error. For asynchronous IRPs, the above mentioned copy operation
// takes place regardless of the status value.
//
if (!(NT_ERROR(status) && isSynchronous)) {
KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE, // Not alertable
NULL);
}
return status;
}
NTSTATUS MakeSynchronousNonIoctlRequestCompletion2(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context )
{
if (Irp->PendingReturned) {
KeSetEvent ((PKEVENT) Context, IO_NO_INCREMENT, FALSE);
}
return STATUS_MORE_PROCESSING_REQUIRED;
}
シナリオ 10: IoBuildAsynchronousFsdRequest を使用して非同期要求を送信する
このシナリオでは、IoBuildAsynchronousFsdRequest を呼び出して非同期要求を行う方法を示します。
非同期要求では、要求を行ったスレッドは、IRP の完了を待機する必要はありません。 IRP はスレッドに関連付けられていないため、任意のスレッド コンテキストで作成できます。 IRP を再利用しない場合は、完了ルーチンを指定し、バッファーと IRP を完了ルーチンで解放する必要があります。 これは、ドライバーによって作成された非同期 IRP (IoBuildAsynchronousFsdRequest と IoAllocateIrp を使用して作成したもの) の完了後のクリーンアップを I/O マネージャーが実行できないためです。
NTSTATUS
MakeAsynchronousRequest (
PDEVICE_OBJECT TopOfDeviceStack,
PVOID WriteBuffer,
ULONG NumBytes
)
/*++
Arguments:
TopOfDeviceStack -
WriteBuffer - Buffer to be sent to the TopOfDeviceStack.
NumBytes - Size of buffer to be sent to the TopOfDeviceStack.
--*/
{
NTSTATUS status;
PIRP irp;
LARGE_INTEGER startingOffset;
PIO_STACK_LOCATION nextStack;
PVOID context;
startingOffset.QuadPart = (LONGLONG) 0;
irp = IoBuildAsynchronousFsdRequest(
IRP_MJ_WRITE,
TopOfDeviceStack,
WriteBuffer,
NumBytes,
&startingOffset, // Optional
NULL
);
if (NULL == irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Allocate memory for context structure to be passed to the completion routine.
//
context = ExAllocatePoolWithTag(NonPagedPool, sizeof(ULONG_PTR), 'ITag');
if (NULL == context) {
IoFreeIrp(irp);
return STATUS_INSUFFICIENT_RESOURCES;
}
IoSetCompletionRoutine(irp,
MakeAsynchronousRequestCompletion,
context,
TRUE,
TRUE,
TRUE);
//
// If you want to change any value in the IRP stack, you must
// first obtain the stack location by calling IoGetNextIrpStackLocation.
// This is the location that is initialized by the IoBuildxxx requests and
// is the one that the target device driver is going to view.
//
nextStack = IoGetNextIrpStackLocation(irp);
//
// Change the MajorFunction code to something appropriate.
//
nextStack->MajorFunction = IRP_MJ_SCSI;
(void) IoCallDriver(TopOfDeviceStack, irp);
return STATUS_SUCCESS;
}
NTSTATUS
MakeAsynchronousRequestCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PMDL mdl, nextMdl;
//
// If the target device object is set up to do buffered i/o
// (TopOfDeviceStack->Flags and DO_BUFFERED_IO), then
// IoBuildAsynchronousFsdRequest request allocates a system buffer
// for read and write operation. If you stop the completion of the IRP
// here, you must free that buffer.
//
if(Irp->AssociatedIrp.SystemBuffer && (Irp->Flags & IRP_DEALLOCATE_BUFFER) ) {
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
//
// If the target device object is set up do direct i/o (DO_DIRECT_IO), then
// IoBuildAsynchronousFsdRequest creates an MDL to describe the buffer
// and locks the pages. If you stop the completion of the IRP, you must unlock
// the pages and free the MDL.
//
else if (Irp->MdlAddress != NULL) {
for (mdl = Irp->MdlAddress; mdl != NULL; mdl = nextMdl) {
nextMdl = mdl->Next;
MmUnlockPages( mdl ); IoFreeMdl( mdl ); // This function will also unmap pages.
}
Irp->MdlAddress = NULL;
}
if(Context) {
ExFreePool(Context);
}
//
// If you intend to queue the IRP and reuse it for another request,
// make sure you call IoReuseIrp(Irp, STATUS_SUCCESS) before you reuse.
//
IoFreeIrp(Irp);
//
// NOTE: this is the only status that you can return for driver-created asynchronous IRPs.
//
return STATUS_MORE_PROCESSING_REQUIRED;
}
シナリオ 11: IoAllocateIrp を使用して非同期要求を送信する
このシナリオは、前のシナリオと似ていますが、IoBuildAsynchronousFsdRequest を呼び出す代わりに IoAllocateIrp 関数を使用して IRP を作成する点を除きが異なります。
NTSTATUS
MakeAsynchronousRequest2(
PDEVICE_OBJECT TopOfDeviceStack,
PVOID WriteBuffer,
ULONG NumBytes
)
/*++
Arguments:
TopOfDeviceStack -
WriteBuffer - Buffer to be sent to the TopOfDeviceStack.
NumBytes - Size of buffer to be sent to the TopOfDeviceStack.
--*/
{
NTSTATUS status;
PIRP irp;
LARGE_INTEGER startingOffset;
KEVENT event;
PIO_STACK_LOCATION nextStack;
startingOffset.QuadPart = (LONGLONG) 0;
//
// Start by allocating the IRP for this request. Do not charge quota
// to the current process for this IRP.
//
irp = IoAllocateIrp( TopOfDeviceStack->StackSize, FALSE );
if (NULL == irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Obtain a pointer to the stack location of the first driver that will be
// invoked. This is where the function codes and the parameters are set.
//
nextStack = IoGetNextIrpStackLocation( irp );
nextStack->MajorFunction = IRP_MJ_WRITE;
nextStack->Parameters.Write.Length = NumBytes;
nextStack->Parameters.Write.ByteOffset= startingOffset;
if(TopOfDeviceStack->Flags & DO_BUFFERED_IO) {
irp->AssociatedIrp.SystemBuffer = WriteBuffer;
irp->MdlAddress = NULL;
} else if (TopOfDeviceStack->Flags & DO_DIRECT_IO) {
//
// The target device supports direct I/O operations. Allocate
// an MDL large enough to map the buffer and lock the pages into
// memory.
//
irp->MdlAddress = IoAllocateMdl( WriteBuffer,
NumBytes,
FALSE,
FALSE,
(PIRP) NULL );
if (irp->MdlAddress == NULL) {
IoFreeIrp( irp );
return STATUS_INSUFFICIENT_RESOURCES;
}
try {
MmProbeAndLockPages( irp->MdlAddress,
KernelMode,
(LOCK_OPERATION) (nextStack->MajorFunction == IRP_MJ_WRITE ? IoReadAccess : IoWriteAccess) );
} except(EXCEPTION_EXECUTE_HANDLER) {
if (irp->MdlAddress != NULL) {
IoFreeMdl( irp->MdlAddress );
}
IoFreeIrp( irp );
return GetExceptionCode();
}
}
IoSetCompletionRoutine(irp,
MakeAsynchronousRequestCompletion2,
NULL,
TRUE,
TRUE,
TRUE);
(void) IoCallDriver(TargetDeviceObject, irp);
return STATUS_SUCCESS;
}
NTSTATUS
MakeAsynchronousRequestCompletion2(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PMDL mdl, nextMdl;
//
// Free any associated MDL.
//
if (Irp->MdlAddress != NULL) {
for (mdl = Irp->MdlAddress; mdl != NULL; mdl = nextMdl) {
nextMdl = mdl->Next;
MmUnlockPages( mdl ); IoFreeMdl( mdl ); // This function will also unmap pages.
}
Irp->MdlAddress = NULL;
}
//
// If you intend to queue the IRP and reuse it for another request,
// make sure you call IoReuseIrp(Irp, STATUS_SUCCESS) before you reuse.
//
IoFreeIrp(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
シナリオ 12: 非同期要求を送信し、別のスレッドでキャンセルする
このシナリオでは、要求の完了を待たずに一度に 1 つの要求を下位ドライバーに送信する方法を示します。別のスレッドにある要求を随時キャンセルすることもできます。
この作業を行うための IRP とその他の変数は、以下に示すように、デバイス拡張またはデバイスにグローバルなコンテキスト構造体に記憶させることができます。 IRP の状態は、デバイス拡張の IRPLOCK 変数で追跡されます。 IrpEvent は、次の要求を行う前に使用されて、IRP が完全に完了 (または解放) されていることを確認します。
このイベントはまた、これらの要求を完了する前に IRP_MN_REMOVE_DEVICE 要求と IRP_MN_STOP_DEVICE PNP 要求を処理して、保留中の IRP が存在しないことを確認する場合にも使用できます。 このイベントは、AddDevice またはその他の初期化ルーチンで同期イベントとして初期化する場合に最適です。
typedef struct _DEVICE_EXTENSION{
..
PDEVICE_OBJECT TopOfDeviceStack;
PIRP PendingIrp;
IRPLOCK IrpLock; // You need this to track the state of the IRP.
KEVENT IrpEvent; // You need this to synchronize various threads.
..
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
</pre><pre class="code">
InitializeDeviceExtension( PDEVICE_EXTENSION DeviceExtension)
{
KeInitializeEvent(&DeviceExtension->IrpEvent, SynchronizationEvent, TRUE);
}
NTSTATUS
MakeASynchronousRequest3(
PDEVICE_EXTENSION DeviceExtension,
PVOID WriteBuffer,
ULONG NumBytes
)
/*++
Arguments:
DeviceExtension -
WriteBuffer - Buffer to be sent to the TargetDeviceObject.
NumBytes - Size of buffer to be sent to the TargetDeviceObject.
--*/
{
NTSTATUS status;
PIRP irp;
LARGE_INTEGER startingOffset;
PIO_STACK_LOCATION nextStack;
//
// Wait on the event to make sure that PendingIrp
// field is free to be used for the next request. If you do
// call this function in the context of the user thread,
// make sure to call KeEnterCriticialRegion before the wait to protect
// the thread from getting suspended while holding a lock.
//
KeWaitForSingleObject( &DeviceExtension->IrpEvent,
Executive,
KernelMode,
FALSE,
NULL );
startingOffset.QuadPart = (LONGLONG) 0;
//
// If the IRP is used for the same purpose every time, you can just create the IRP in the
// Initialization routine one time and reuse it by calling IoReuseIrp.
// The only thing that you have to do in the routines in this article
// is remove the lines that call IoFreeIrp and set the PendingIrp
// field to NULL. If you do so, make sure that you free the IRP
// in the PNP remove handler.
//
irp = IoBuildAsynchronousFsdRequest(
IRP_MJ_WRITE,
DeviceExtension->TopOfDeviceStack,
WriteBuffer,
NumBytes,
&startingOffset, // Optional
NULL
);
if (NULL == irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Initialize the fields relevant fields in the DeviceExtension
//
DeviceExtension->PendingIrp = irp;
DeviceExtension->IrpLock = IRPLOCK_CANCELABLE;
IoSetCompletionRoutine(irp,
MakeASynchronousRequestCompletion3,
DeviceExtension,
TRUE,
TRUE,
TRUE);
//
// If you want to change any value in the IRP stack, you must
// first obtain the stack location by calling IoGetNextIrpStackLocation.
// This is the location that is initialized by the IoBuildxxx requests and
// is the one that the target device driver is going to view.
//
nextStack = IoGetNextIrpStackLocation(irp);
//
// You could change the MajorFunction code to something appropriate.
//
nextStack->MajorFunction = IRP_MJ_SCSI;
(void) IoCallDriver(DeviceExtension->TopOfDeviceStack, irp);
return STATUS_SUCCESS;
}
NTSTATUS
MakeASynchronousRequestCompletion3(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PMDL mdl, nextMdl;
PDEVICE_EXTENSION deviceExtension = Context;
//
// If the target device object is set up to do buffered i/o
// (TargetDeviceObject->Flags & DO_BUFFERED_IO), then
// IoBuildAsynchronousFsdRequest request allocates a system buffer
// for read and write operation. If you stop the completion of the IRP
// here, you must free that buffer.
//
if(Irp->AssociatedIrp.SystemBuffer && (Irp->Flags & IRP_DEALLOCATE_BUFFER) ) {
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
//
// If the target device object is set up to do direct i/o (DO_DIRECT_IO), then
// IoBuildAsynchronousFsdRequest creates an MDL to describe the buffer
// and locks the pages. If you stop the completion of the IRP, you must unlock
// the pages and free the MDL.
//
if (Irp->MdlAddress != NULL) {
for (mdl = Irp->MdlAddress; mdl != NULL; mdl = nextMdl) {
nextMdl = mdl->Next;
MmUnlockPages( mdl ); IoFreeMdl( mdl ); // This function will also unmap pages.
}
Irp->MdlAddress = NULL;
}
if (InterlockedExchange((PVOID)&deviceExtension->IrpLock, IRPLOCK_COMPLETED)
== IRPLOCK_CANCEL_STARTED) {
//
// Main line code has got the control of the IRP. It will
// now take the responsibility of freeing the IRP.
// Therefore...
return STATUS_MORE_PROCESSING_REQUIRED;
}
//
// If you intend to queue the IRP and reuse it for another request, make
// sure you call IoReuseIrp(Irp, STATUS_SUCCESS) before you reuse.
//
IoFreeIrp(Irp);
deviceExtension->PendingIrp = NULL; // if freed
//
// Signal the event so that the next thread in the waiting list
// can send the next request.
//
KeSetEvent (&deviceExtension->IrpEvent, IO_NO_INCREMENT, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
VOID
CancelPendingIrp(
PDEVICE_EXTENSION DeviceExtension
)
/*++
This function tries to cancel the PendingIrp if it is not already completed.
Note that the IRP may not be completed and freed when the
function returns. Therefore, if you are calling this from your PNP Remove device handle,
you must wait on the IrpEvent to make sure the IRP is indeed completed
before successfully completing the remove request and allowing the driver to unload.
--*/
{
if (InterlockedExchange((PVOID)&DeviceExtension->IrpLock, IRPLOCK_CANCEL_STARTED) == IRPLOCK_CANCELABLE) {
//
// You got it to the IRP before it was completed. You can cancel
// the IRP without fear of losing it, as the completion routine
// will not let go of the IRP until you say so.
//
IoCancelIrp(DeviceExtension->PendingIrp);
//
// Release the completion routine. If it already got there,
// then you need to free it yourself. Otherwise, you got
// through IoCancelIrp before the IRP completed entirely.
//
if (InterlockedExchange((PVOID)&DeviceExtension->IrpLock, IRPLOCK_CANCEL_COMPLETE) == IRPLOCK_COMPLETED) {
IoFreeIrp(DeviceExtension->PendingIrp);
DeviceExtension->PendingIrp = NULL;
KeSetEvent(&DeviceExtension->IrpEvent, IO_NO_INCREMENT, FALSE);
}
}
return ;
}
リファレンス
- Walter Oney 著 『Programming Windows Driver Model (Second Edition)』、第5章。