标准IRP的处理过程如同上图所示,整个处理由IO管理器创建的IRP开始。当然这个IO管理器在大多数情况下是系统,然而在驱动程序当中也可以创建IRP。下面四个函数可以用于创建IRP:
IoBuildAsynchronousFsdRequest用于创建异步IRP,发送IRP的IO管理器不需要因为等待IRP的处理。
IoBuildSynchronousFsdRequest用于创建同步IRP,如果IRP的处理没有完成则IO管理器会一直等待。
IoBuildDeviceIoControlRequest用于创建同步的IRP_MJ_DEVICE_CONTROL或者IRP_INTERNAL_DEVICE_CONTROL请求。
IoAllocateIrp创建上面三种不支持的IRP,实际上前面三种都调用了IoAllocateIrp函数分配IRP。
不过调用IoAllocateIrp函数之后需要自己进行IOSTACK_STATION结构体的初始化。利用函数IoGetNextIrpStackLocation得到第一个IOSTACK_STATION,由于整个IOSTACK_STATION的第一个指针指向-1,所以这里是得到-1的下一个。在得到IOSTACK_STATION之后还需要对输入缓存和输出缓存进行一定的设置。然后就可以调用IoCallDriver了。IoCallDriver函数有两个参数,一个是DeviceObject的指针,另一个是IRP的指针。所以这个函数的实现比较核心的部分是利用IRP找到隐藏于IOSTACK_STATION当中的IRP_MJ_XX,并且将IOSTACK_STATION的位置下降一位,然后利用设备对象找到相应的驱动对象,就可以利用驱动对象的函数数组进行相应的函数调用了。
而通过IRP_MJ_XX得到的函数数组当中的函数,就是上面图片当中的派遣例程。这个函数实际上是一个存储于驱动对象当中的结构体指针。这个函数的处理分为两种情况,第一种是直接返回,函数的处理如下:
Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCESS;
另外一种是将请求挂载到队列当中,在以后的异步调用当中才真正的返回,函数的处理如下:
Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoMarkIrpPending(Irp); IoStartPacket(DeviceObject, Irp, NULL, BeepCancel); return STATUS_PENDING;
一般在调用IoStartPacket函数之后,我们就无权处理IRP了,因为整个IRP有可能已经开始处理了。然后在IoStartPacket函数当中会调用驱动对象的StartIo例程。然后StartIo就会从设备队列当中获取相应的IRP进行操作,在StartIo的函数结束之前需要调用IoStartNextPacket以便于对整个队列进行遍历,从而将所有的排队的IRP都处理完毕,最后当然不能忘记IoCompleteRequest函数的调用,这个才是IRP处理的最终终结。
不过上面所讨论的也是StartIo当中最简单的一种,还有一种情况是在StartIo当中调用相应的DPC服务,将控制转到DPC例程当中进行。而这个对DPC的调用是由终端引起的,最简单的是可以通过一次时钟终端引起对DPC例程的调用。当然你也可以自己实现一个中断处理,首先通过一个IoConnectInterrupt获取一个中断对象,然后通过这个中断对象的调用例程来调用相应的DPC例程函数。不过就算在DPC例程函数里面也不要忘记调用IoStartNextPacket和IoCompleteRequest函数。
假设需要对整个IRP的排队队列进行特殊的处理,比如说输入和输出被看成是不同的队列,就需要在DEVICE_EXTENSION当中定义两个队列,然后自己重新实现一个类似的IoStartPacket函数以及相应的StartIo函数就可以了。
整个IoStartPacket的操作可以分为三个部分,第一部分,对IRQL的操作,第二部分,IRP入队列,第三部分,调用StartIo。
其中第一部分分为两个阶段,初始阶段需要将整个IRQL提高到DIRQL,因为整个StartIo的调用需要在DIRQL级别上,然后在第二部分和第三部分处理完之后需要将IRQL还原。至于IRP的入队则是由KeInsertDeviceQueue函数完成。第三部分和上面对StartIo的调用很相似。
在调用IoComleteRequest函数之后就可以结束本层的操作,但是假设我们需要在下一层设备的驱动程序对IRP的操作结束之后得到相应的处理,那么就需要额外的处理流程。首先,在将控制传导下一层设备对象的驱动对象之前,调用IoSetCompletionRoutine函数设置一个完成调用例程,这个完成调用例程在IRP完成的时候根据返回值选择性的进行调用。由于IoSetCompletionRoutine将自身的参数以及相应的信息安装到IRP的下一个STACK_STATION当中,所以最底层的设备对象不应该安装完成例程。所以实际上只是在调用IoCompleteRequest函数之前多了一层额外的调用,然后在IoCompleteRequest函数会根据当前的堆栈单元来找到安装的完成例程。
经过上面的讨论之后发现,又多了一种新情况,也就是说,当存在设备堆栈的时候,怎么实现往下层驱动传递控制呢?这里又需要DEVICE_EXTENSION的支持了。我们在DEVICE_EXTENSION当中保存一个下层设备对象的指针,然后创建一个与下层设备相关的STACK_STATION,然后调用IoCallDriver就可以了。由于设备堆栈的利用IoAttachDeviceToDeviceStack将上下层设备进行关联,并且返回下层设备对象指针,可以将返回值保存在DEVICE_EXTTENSION结构体的成员变量当中。然后调用IoCopyCurrentIrpStackLocation函数创建一个相应的STACK_STATION结构体之后就可以调用函数IoCallDriver将控制权下移了。当然你也可以利用IoSkipCurrentIrpStackLocation直接跳过当前的STACK_STATION的处理。
最后一个部分有关于取消操作,取消操作分为两中情况。第一种取消的是当前正在执行的IRP。正在执行的IRP是不允许取消的,所以直接调用IoStartNextPacket函数就可以了。否则直接将相应的IRP从队列当中移除。