当前位置 博文首页 > KOOKNUT的博客:对IRP知识总结(壹)(Windows内核学习笔记)
用户模式下对所有驱动程序的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