IRP完成与IO_STACK_LOCATION
IRP与IO_STACK_LOCATION的关系
IPR完成时,有时为了获取数据,我们经常要这样干
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(
Irp,
CompleteRoutine,
context,
TRUE,
TRUE,
TRUE);
但这是一步到底是什么意思呢?
这就得介绍一下IRP与IO_STACK_LOCATION的关系。
IRP是I/O Request Package的综写,其代表了一个调用。但由于这个调用需要穿过多个设备,而为了实现各个设备栈独立,所以就为各个设备都开辟了一个设备栈调用空间。
而一个设备的PDO在创建时,会设置他的调用栈深度,这个值记录在DeviceObject中的StackSize成员中。
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
...
CCHAR StackSize;
...
} DEVICE_OBJECT;
这个我们也可以通过函数IoAllocateIrp来验证
PIRP
IoAllocateIrp(
_In_ CCHAR StackSize,
_In_ BOOLEAN ChargeQuota
);
IO_STACK_LOCATION的巧妙设计
从上面知道,每个设备都有一个独立的IO_STACK_LOCATION,所以越是底层的设备其栈指针越小,越是上层的设备,其栈设栈越大。这个栈指针计录在IRP成员的CurrentLocation中。
所以像IoSkipCurrentIrpStackLocation这样的函数,都是栈指针或者栈指针各设备栈的操作。
代码如下:
IoSkipCurrentIrpStackLocation
VOID
IoSkipCurrentIrpStackLocation (
_Inout_ PIRP Irp
)
{
NT_ASSERT(Irp->CurrentLocation <= Irp->StackCount);
Irp->CurrentLocation++;
Irp->Tail.Overlay.CurrentStackLocation++;
}
IoGetCurrentIrpStackLocation
PIO_STACK_LOCATION
IoGetCurrentIrpStackLocation(
_In_ PIRP Irp
)
{
NT_ASSERT(Irp->CurrentLocation <= Irp->StackCount + 1);
return Irp->Tail.Overlay.CurrentStackLocation;
}
IoGetNextIrpStackLocation
PIO_STACK_LOCATION
IoGetNextIrpStackLocation(
_In_ PIRP Irp
)
{
NT_ASSERT(Irp->CurrentLocation > 0);
return ((Irp)->Tail.Overlay.CurrentStackLocation - 1 );
}
IoCopyCurrentIrpStackLocationToNext
IoCopyCurrentIrpStackLocationToNext(
_Inout_ PIRP Irp
)
{
PIO_STACK_LOCATION irpSp;
PIO_STACK_LOCATION nextIrpSp;
irpSp = IoGetCurrentIrpStackLocation(Irp);
nextIrpSp = IoGetNextIrpStackLocation(Irp);
RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
nextIrpSp->Control = 0;
}
VOID
IoSetCompletionRoutine(
_In_ PIRP Irp,
_In_opt_ PIO_COMPLETION_ROUTINE CompletionRoutine,
_In_opt_ __drv_aliasesMem PVOID Context,
_In_ BOOLEAN InvokeOnSuccess,
_In_ BOOLEAN InvokeOnError,
_In_ BOOLEAN InvokeOnCancel
)
{
PIO_STACK_LOCATION irpSp;
NT_ASSERT( (InvokeOnSuccess || InvokeOnError || InvokeOnCancel) ? (CompletionRoutine != NULL) : TRUE );
irpSp = IoGetNextIrpStackLocation(Irp);
irpSp->CompletionRoutine = CompletionRoutine;
irpSp->Context = Context;
irpSp->Control = 0;
if (InvokeOnSuccess) {
irpSp->Control = SL_INVOKE_ON_SUCCESS;
}
if (InvokeOnError) {
irpSp->Control |= SL_INVOKE_ON_ERROR;
}
if (InvokeOnCancel) {
irpSp->Control |= SL_INVOKE_ON_CANCEL;
}
}
IoCompleteRequest与完成例程
当IRP完成时,需要调用IoCompleteRequest,这个宏其实调用的是IofCompleteRequest函数。
windows并没有提供源代码,不过我们可以通过REACTOS来看。
for (StackPtr = IoGetCurrentIrpStackLocation(Irp),
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++;
Irp->CurrentLocation <= (Irp->StackCount + 1);
StackPtr++,
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++)
{
...
/* Call the completion routine */
Status = StackPtr->CompletionRoutine(DeviceObject,
Irp,
StackPtr->Context);
/* Don't touch the Packet in this case, since it might be gone! */
if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;
}
代码比较长,这里我们只留关键的代码。
这里我们可以看到,当IRP完成时,会从当前的栈投针向上回溯调用各个栈的完成回调函数,当某个回调函数返回STATUS_MORE_PROCESSING_REQUIRED时,直接退出。也就是说明那个设备重新获取了IRP,自己内部需要再次调用IoCompleteRequest实现IRP的完成。