WDM(Windows 드라이버 모델) 드라이버는 일반적으로 다른 드라이버에 IAP(입력/출력 요청 패킷)를 보냅니다. 드라이버는 자체 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은 대부분의 프로세서 아키텍처에서 효율적으로 로드되므로).
다음 코드를 읽으실 때, STATUS_CONTINUE_COMPLETION이 WDK 내에서 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를 반환할 수 있습니다. 다른 스레드에서 IRP를 다시 사용하고 나중에 완료하려는 경우에만 STATUS_MORE_PROCESSING_REQUIRED를 반환합니다.
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를 해제해야 합니다. I/O 관리자는 드라이버에서 만든 비동기 IRP( IoBuildAsynchronousFsdRequest 및 IoAllocateIrp를 사용하여 생성됨)의 완료 후 정리를 수행할 수 없기 때문입니다.
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: 비동기 요청을 보내고 다른 스레드에서 취소
이 시나리오에서는 요청이 완료 될 때까지 기다리지 않고 낮은 드라이버에 한 번에 하나의 요청을 보낼 수있는 방법을 보여 하며, 다른 스레드에서 언제든지 요청을 취소 할 수도 있습니다.
아래와 같이 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 ;
}
참고문헌
- 월터 오니 Windows 드라이버 모델 프로그래밍, 두 번째 버전, 5장.