IRP的操作
通常状态下派遣函数调用IoCompleteRequest 结束当前IRP请求
但在一些情况下可能要挂起irp 用内核函数IoMarkIrpPending 挂起IRP并返回 STATUS_PENDING ,对于ring3级的请求会返回False 这时用GetLastError会发现错误码是ERROR_IO_PENDING 表明当前操作被挂起。为了最终要结束所有的IRP请求,我们需要保存被挂起的IRP,并在最后进行处理(IRP_MJ_CLEANUP)
还有一种情况是取消IRP的请求IoSetCancelRoutine(PIRP pIrp, PDRIVER_CANCEL CancelRoutine);参数1 需要取消的IRP;参数2 :回调函数地址,需要同步执行的。
流程:在某个派遣函数A中挂起IRP 并且设置返回为STATUS_PENDING,同时需要设置取消例程,也就是回调函数。此回调函数需要同步执行,其可以在ring3级显示调用(CancelIo(hDevice) hDcvice为操作的句柄内核函数可以调用IoCancelIrp(IN pIrp) ),也可以不需要调用,程序关闭设备时会自动调用CancelIo;系统调用IoCancelIrp时自动会获得自旋锁,以进行同步,所以在回调函数完成时记得释放自旋锁(IoReleaseCancelSpinLock),否则会导致系统崩溃,另外此cancel自旋锁是全局自旋锁,所有驱动都会使用,所以不宜长时间占用。
代码见上两篇
Irp的串行处理:
Irp的串行处理:IRP有很多时候也是需要同步处理的, 这个有个专门的名词, 就是串行化.串行化能够保证各个并行的IRP能够按照顺序执行, 典型情况下, 对某个设备的操作的, 假如有很多个线程同时去操作这个设备, 那么必须将这些操作排队, 然后一一进行处理, 如果不做串行化操作就会变得混乱.
当一个新的Irp请求来临的时候首先检查设备是否处于"忙"状态, 如果处于忙状态, 那么将Irp插入一个队列中, 等待设备变为闲的时候从队列中取出一个IRP进行处理, 这个就是串行化了, 也就是StartIo. Windows内核中有专门支持这种操作的一系列结构和例程, 但是Windows提供的只有一个队列, 如果需要多个队列, 比如读一个队列写一个队列, 那么就要自己实现队列了.
在驱动入口处指定startIo的派遣函数
pDriverObj->DriverStartIo = &StartIoRoutine;
在相应的派遣函数中将irp插入到系统队列中去
//将IRP设置为挂起
IoMarkIrpPending(pIrp);//将IRP插入系统的队列
IoStartPacket(pDevObj,pIrp,0,OnCancelIRP);//pDevObj 派遣函数参数 设备对象,cancelIRP 为IRP取消派遣例程startIo与其他的派遣函数一样,只不过返回值为void的。
StartIo派遣函数开始时,获取cancel的自旋锁,判断 当前irp是否正在处理或者属于需要取消处理,如果是则释放自旋锁并立即返回。如果不是的话则不允许该请求调用取消例程(设置该IRP取消回调函数为NULL即可),释放自旋锁。然后完成Irp请求,最后读取队列中下一个Irp并进行下一轮StartIo,调用
函数IoStartNextPacket.。
Cancel派遣例程开始时先判断当前irp是否在startIo中处理,如果是话则释放自旋锁并继续读取下个irp,调用IoStartNextPacket。否则从设备队列中将该Irp抽取出来(KeRemoveEntryDeviceQueue),调用此函数会自动获取自旋锁,所以调用完后记得释放自旋锁,最后进行cancel处理Irp请求。
自定义StartIO
系统的StartIO只能使用一个队列,所有的IRP处理都在这个队列中。然而在一些情况下可能需要各个类型的IRP分开处理,这就需要自定义startIo例程。
自定义StartIo需要自己建立管理队列,DDK中的KDVICE_QUEUE数据结构存储队列如下typedef struct _KDEVICE_QUEUE {
CSHORT Type; CSHORT Size; LIST_ENTRY DeviceListHead; KSPIN_LOCK Lock;BOOLEAN Busy;
} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *PRKDEVICE_QUEUE;
队列中的每个元素用KDEVICE_QUEUE_ENTRY 数据结构表示
typedef struct _KDEVICE_QUEUE_ENTRY {
LIST_ENTRY DeviceListEntry; ULONG SortKey; BOOLEAN Inserted;} KDEVICE_QUEUE_ENTRY, *PKDEVICE_QUEUE_ENTRY, *PRKDEVICE_QUEUE_ENTRY;IRP.Tail.Overlay.DeviceQueueEntry 就是我们需要保存的数据结构
其实我们可以建立Irp列表将整个IRP保存起来,在自定义StartIo中进行处理。
使用队列时需要初始化队列,因为是将该数据保存在设备扩展数据里了,所以在生成设备的时候将其初始化:
RtlZeroBytes(&pDevExt->device_queue,sizeof(pDevExt->device_queue));
KeInitializeDeviceQueue(&pDevExt->device_queue);
在相应的派遣函数中将其插入到队列中去 进行处理
if (!KeInsertDeviceQueue(&pDevExt->device_queue, &pIrp->Tail.Overlay.DeviceQueueEntry))
MyStartIo(pDevObj,pIrp);注意:进入队列之前要先将IRQL提升至DISPATCH_LEVEL级别