Windows驱动
+ -

IRP完成与IO_STACK_LOCATION

2021-07-01 154 0

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;
}

IoSetCompletionRoutine

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的完成。

0 篇笔记 写笔记

作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!