当前位置 博文首页 > KOOKNUT的博客:对IRP知识总结(壹)(Windows内核学习笔记)

    KOOKNUT的博客:对IRP知识总结(壹)(Windows内核学习笔记)

    作者:[db:作者] 时间:2021-07-02 21:31

    用户模式下对所有驱动程序的I/O请求都会,全部由操作系统转化为一个叫做IRP(I/O Request Package)的数据结构,不同的IRP数据结构会被派遣到不同的派遣函数中进行请求的处理。
    在一个驱动程序中对应的DriverObject中,有个函数指针数组MajorFunction,里面存放的是派遣函数的地址,通过这个数组,可以将IRP请求与对应的派遣函数关联起来。
    接下来首先介绍一个重要的数据结构I/O堆栈,IO_STACK_LOCATION,这个数据结构和IRP紧密相连。每一个IRP都会被操作系统发送到设备栈的顶层,如果顶层设备对象的派遣函数结束了IRP请求,则这次I/O请求结束。如果没有将IRP的请求结束,那么操作系统将IRP转发到设备栈的下一层去设备处理,一直到IRP请求被设备对象处理,则停止向下转发。
    因此一个IRP就可能被转发很多次,那么我们怎么记录这些操作和需要操作的数据呢?IRP中有一个IO_STACK_LOCATION数组,数组的元素应该大于IRP穿越过的设备数,每个IO_STACK_LOCATION元素记录着对应设备中做的操作。对于本层设备对应的IO_STACK_LOCATION,可以通过IoGetCurrentIrpStackLocation来得到。

    #define IoGetCurrentIrpStackLocation( Irp ) ( (Irp)->Tail.Overlay.CurrentStackLocation )
    

    IRP转发的具体实现是怎样的?
    当上层设备对象不使用IoCompleteRequest处理IRP请求的时候,会选择向下转发,让底层的设备对象处理IRP请求,每一个设备对象对应自己的I/O堆栈,而当我们向底层转发IRP的时候,会调用IoCallDriver函数,会使IRP的当前I/O堆栈指针下移,指向下一个IO_STACK_LOCATION指针

    NTSTATUS
    NTAPI
    IoCallDriver(IN PDEVICE_OBJECT DeviceObject,
                 IN PIRP Irp)
    {
        /* Call fastcall */
        return IofCallDriver(DeviceObject, Irp);
    }
    NTSTATUS
    FASTCALL
    IofCallDriver(IN PDEVICE_OBJECT DeviceObject,
                  IN PIRP Irp)
    {
        PDRIVER_OBJECT DriverObject;
        PIO_STACK_LOCATION StackPtr;
    
        /* Make sure this is a valid IRP */
        ASSERT(Irp->Type == IO_TYPE_IRP);
    
        /* Get the Driver Object */
        DriverObject = DeviceObject->DriverObject;
    
        /* Decrease the current location and check if */
        Irp->CurrentLocation--;
        if (Irp->CurrentLocation <= 0)
        {
            /* This IRP ran out of stack, bugcheck */
            KeBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR)Irp, 0, 0, 0);
        }
    
        /*将当前I/O堆栈的指针下移*/
        StackPtr = IoGetNextIrpStackLocation(Irp);
        Irp->Tail.Overlay.CurrentStackLocation = StackPtr;
    
        /* Get the Device Object */
        StackPtr->DeviceObject = DeviceObject;
    
        /* Call it */
        return DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject,
                                                                    Irp);
    }
    

    但有的时候,当前设备堆栈不对IRP做任何处理,而IoCallDriver会将当前I/O堆栈向下移动一个单位,此时可以调用IoSkipCurrentIrpStackLocation对设备堆栈的移动会实现平衡,它的作用就是将I/O堆栈往回移动一个单位。这时IoCallDriver调用的低一层驱动所用到的I/O堆栈和上一层用到的是同一个。
    为什么要用上层设备的IO堆栈数据,因为上层IO堆栈有我们想要的数据,如果低一层驱动想要用上层的IO数据,也可以用IoCopyCurrentIrpStackLocationToNext。
    如图,当DeviceB不对IRP做任何处理时候,向下层设备(DeviceA)转发IRP时,调用IoCallDriver内核函数会将IRP的当前指针下移,指向下一个IO_STACK_LOCATION指针,所以在调用IoCallDriver之前应该调用IoSkipCurrentIrpStackLocation,使IO堆栈上移,从而使IO_STACK_LOCATION(B)中的数据能够正确被DeviceA所使用
    在这里插入图片描述
    另一种情况,当IRP被本层驱动(DeviceB)设备使用,但需要向下(DeviceA)转发IRP时,调用IoCopyCurrentIrpStackLocationToNext来复制当前I/O堆栈(IO_STACK_LOCATION(B))参数到下一层(IO_STACK_LOCATION(A)),然后调用IoCallDriver。
    几个重要的函数实现

    #define IoSkipCurrentIrpStackLocation( Irp ) \
        (Irp)->CurrentLocation++; \
        (Irp)->Tail.Overlay.CurrentStackLocation++;
    
    #define IoGetNextIrpStackLocation( Irp ) (\
        (Irp)->Tail.Overlay.CurrentStackLocation - 1 )
    
    FORCEINLINE
    VOID
    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;
    }
    

    “Do not let your emotions override your judgement.”
    参考资料:
    《Windows驱动开发技术详解》
    ReactOS

    cs
    下一篇:没有了