IRP 是 I/O request packet 的缩写,即 I/O 请求包。驱动与驱动之间通过 IRP 进行通信。而使用驱动的应用层调用的 CreatFile,ReadFile,WriteFile,DeviceIoControl 等函数,说到底也是使用 IRP 和驱动进行通信。
一个 IRP 由两部分组成。首先是头部或者叫包的固定部分,是一个 IRP 结构。紧跟在这个头部之后的是 I/O栈位置,这是一个 IO_STACK_LOCATION 结构的数组,这个数组中元素的个数是根据情况而定的,由 IoAllocateIrp( IN CCHAR StackSize , IN BOOLEAN ChargeQuota ) 时的参数 StackSize 决定。而 StackSize 通常由 IRP 发往的目标 DEVICE_OBJECT 的StackSize 决定。而这个 StackSize 是由设备对象连入所在的设备栈时,根据在设备栈中位置决定的。我们先看看 IRP 结构和 IO_STACK_LOCATION 结构的定义。实现代码如下:
#001 typedef struct _IRP {
标志IRP类型。
#002 CSHORT Type;
本IRP的长度和IRP栈的长度。
#003 USHORT Size;
指向内存描述符列表。
#004 struct _MDL *MdlAddress;
IRP包特征标志,比如直接I/O,还是缓存I/O等等。
#005 ULONG Flags;
保存驱动程序相关的数据结构。其中,与WDM驱动程序相关的指针是AssociatedIrp.SystemBuffer。 SystemBuffer指针指向一个数据缓冲区,该缓冲区位于内核模式的非分页内存中。对于IRP_MJ_READ和IRP_MJ_WRITE操作,如果顶级设备指定DO_BUFFERED_IO标志,则I/O管理器就创建这个数据缓冲区。对于IRP_MJ_DEVICE_CONTROL操作,如果I/O控制功能代码指出需要缓冲区(见第九章),则I/O管理器就创建这个数据缓冲区。I/O管理器把用户模式程序发送给驱动程序的数据复制到这个缓冲区,这也是创建IRP过程的一部分。这些数据可以是与WriteFile调用有关的数据,或者是DeviceIoControl调用中所谓的输入数据。对于读请求,设备驱动程序把读出的数据填到这个缓冲区,然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。对于指定了METHOD_BUFFERED的I/O控制操作,驱动程序把所谓的输出数据放到这个缓冲区,然后I/O管理器再把数据复制到用户模式的输出缓冲区。
#006 union {
#007 struct _IRP *MasterIrp;
#008 volatile LONG IrpCount;
#009 PVOID SystemBuffer;
#010 } AssociatedIrp;
当前线程入口。
#011 LIST_ENTRY ThreadListEntry;
一个仅包含两个域的结构,驱动程序在最终完成请求时设置这个结构。
#012 IO_STATUS_BLOCK IoStatus;
等于一个枚举常量UserMode或KernelMode,指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数。
#013 KPROCESSOR_MODE RequestorMode;
IRP是否被阻塞。
#014 BOOLEAN PendingReturned;
IRP栈的大小。
#015 CHAR StackCount;
当前栈的位置。
#016 CHAR CurrentLocation;
IRP是否被取消操作。
#017 BOOLEAN Cancel;
当IoAcquireCancelSpinLock函数调用,指明它是那一个IRQ级别。
#018 KIRQL CancelIrql;
APC调用的环境索引。
#019 CCHAR ApcEnvironment;
内存分配方式,比如定额地增加,还是固定大小等等。
#020 UCHAR AllocationFlags;
保存用户I/O状态。
#021 PIO_STATUS_BLOCK UserIosb;
保存用户的事件。
#022 PKEVENT UserEvent;
APC、或分配的内存大小。
#023 union {
#024 struct {
#025 PIO_APC_ROUTINE UserApcRoutine;
#026 PVOID UserApcContext;
#027 } AsynchronousParameters;
#028 LARGE_INTEGER AllocationSize;
#029 } Overlay;
驱动程序取消例程的地址
#030 volatile PDRIVER_CANCEL CancelRoutine;
指向用户缓冲区。
#031 PVOID UserBuffer;
#032 union {
设备队列入口,或者设备上下环境指针。
#033 struct {
#034 _ANONYMOUS_UNION union {
#035 KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
#036 _ANONYMOUS_STRUCT struct {
#037 PVOID DriverContext[4];
#038 } DUMMYSTRUCTNAME;
#039 } DUMMYUNIONNAME;
批向内核线程。
#040 PETHREAD Thread;
辅助缓冲区。
#041 PCHAR AuxiliaryBuffer;
I/O栈位置
#042 _ANONYMOUS_STRUCT struct {
#043 LIST_ENTRY ListEntry;
#044 _ANONYMOUS_UNION union {
#045 struct _IO_STACK_LOCATION *CurrentStackLocation;
#046 ULONG PacketType;
#047 } DUMMYUNIONNAME;
#048 } DUMMYSTRUCTNAME;
原来文件对象。
#049 struct _FILE_OBJECT *OriginalFileObject;
#050 } Overlay;
APC队列。
#051 KAPC Apc;
I/O完成设置用户关键数据。
#052 PVOID CompletionKey;
#053 } Tail;
#054 } IRP;
#055 typedef struct _IRP *PIRP;
上面学习了IRP的结构,知道了IRP保存的基本内容,也就是说知道了有什么相关东西,这就相当有了原材料,那么怎么样加工和处理这些原材料呢?那就得去分析IRP相关的操作函数,也就是IRP的相关算法。下面就从IRP分配开始,实现代码如下:
#001 PIRP
#002 NTAPI
#003 IoAllocateIrp(IN CCHAR StackSize,
#004 IN BOOLEAN ChargeQuota)
#005 {
#006 PIRP Irp = NULL;
计算IRP占用的大小,包括IRP的头部和IRP栈空间。
#007 USHORT Size = IoSizeOfIrp(StackSize);
#008 PKPRCB Prcb;
#009 UCHAR Flags = 0;
#010 PNPAGED_LOOKASIDE_LIST List = NULL;
#011 PP_NPAGED_LOOKASIDE_NUMBER ListType = LookasideSmallIrpList;
#012
如果设置为定额分配方式,就添加这个标志位。
#013 /* Set Charge Quota Flag */
#014 if (ChargeQuota) Flags |= IRP_QUOTA_CHARGED;
#015
#016 /* FIXME: Implement Lookaside Floats */
#017
#018 /* Figure out which Lookaside List to use */
#019 if ((StackSize <= 8) && (ChargeQuota == FALSE))
#020 {
设置为固定分配大小空间。
#021 /* Set Fixed Size Flag */
#022 Flags = IRP_ALLOCATED_FIXED_SIZE;
#023
需要使用一个大列表方式。
#024 /* See if we should use big list */
#025 if (StackSize != 1)
#026 {
#027 Size = IoSizeOfIrp(8);
#028 ListType = LookasideLargeIrpList;
#029 }
#030
获取当前处理器控制块。
#031 /* Get the PRCB */
#032 Prcb = KeGetCurrentPrcb();
#033
获取后备列表。
#034 /* Get the P List First */
#035 List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].P;
#036
从后备列表里分配一个IRP包。
#037 /* Attempt allocation */
#038 List->L.TotalAllocates++;
#039 Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead);
#040
#041 /* Check if the P List failed */
#042 if (!Irp)
#043 {
#044 /* Let the balancer know */
#045 List->L.AllocateMisses++;
#046
#047 /* Try the L List */
#048 List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].L;
#049 List->L.TotalAllocates++;
#050 Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead);
#051 }
#052 }
#053
如果没有从后备列表里分配到IRP,就需要从内存里分配。
#054 /* Check if we have to use the pool */
#055 if (!Irp)
#056 {
从后备列表里分配失败。
#057 /* Did we try lookaside and fail? */
#058 if (Flags & IRP_ALLOCATED_FIXED_SIZE) List->L.AllocateMisses++;
#059
定额增加分配的方式。
#060 /* Check if we should charge quota */
#061 if (ChargeQuota)
#062 {
#063 /* Irp = ExAllocatePoolWithQuotaTag(NonPagedPool, Size, TAG_IRP); */
#064 /* FIXME */
#065 Irp = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_IRP);
#066 }
#067 else
#068 {
非定额增加分配的方式。
#069 /* Allocate the IRP With no Quota charge */
#070 Irp = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_IRP);
#071 }
#072
#073 /* Make sure it was sucessful */
#074 if (!Irp) return(NULL);
#075 }
#076 else
#077 {
#078 /* In this case there is no charge quota */
#079 Flags &= ~IRP_QUOTA_CHARGED;
#080 }
#081
现在初始化IRP一些属性。
#082 /* Now Initialize it */
#083 IoInitializeIrp(Irp, Size, StackSize);
#084
设置IRP分配的标志。
#085 /* Set the Allocation Flags */
#086 Irp->AllocationFlags = Flags;
#087
返回分配成功的IRP包。
#088 /* Return it */
#089 IOTRACE(IO_IRP_DEBUG,
#090 "%s - Allocated IRP %p with allocation flags %lx/n",
#091 __FUNCTION__,
#092 Irp,
#093 Flags);
#094 return Irp;
#095 }