IRP无法取消的无限等待问题
有时有这样一种功能,就是当上层设备或应用和我们开发的驱动的时候,而我们这时根本无法完成本次的IRP,所以根据WINDOWS 驱动开发规范,我们这时需要对该IRP进行MarkPending,然后返回Pending。
NTSTATUS
MyDispatch(
PDEVICE_OBJECT DeviceObject,
PIRP Irp)
{
...
IoMarkIrpPending(Irp);
return STATUS_PENDING;
}
以上代码应该没有问题的。
例如我们自己开发自己的驱动程序,然后由我们的应用进行调用,关闭设备或者在调用IRP_MJ_CLOSE时,我们可将我们以上Pending的IRP进行完成。
当然上面也需要将Pending的IRP挂入待完成队列。
可是本人这次开发的是一个触模屏USB过滤驱动,功能是在原来的USB驱动上加了一层自己的FDO,再创建同样的PDO上报给系统,这样就相当于我们自己开发的驱动应成了过滤驱动了。
这时由于上层的调用是由系统的输入设备来读取数据的,在实际卸载驱动时,只有子设备的REMOVE等IRP得到了调用,然后就一直进行进度条无限循环状态,根本无法调用总线驱动相关的停止和移除回调函数。
最后发现,是由于自己对Pending的IRP没有设置Cancel例程,导致该IRP无法完成取消,造成系统无限等待。
相关代码如下:
IoSetCancelRoutine(Irp, CancelReadIRP);
IoMarkIrpPending(Irp);
而取消完成例程如下:
VOID CancelReadIRP(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
DPrint3(("\nEnter CancelReadIRP\n"));
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, 0);
IoReleaseCancelSpinLock(Irp->CancelIrql);//释放取消锁
}
而关于IRP相关的取消例程,本人打算再统读REACTOS源代码,有空再跟踪调试一下,再给大家分享Windows驱动开发调试相关的技术及知识点
取消IRP是通过调用IoCancelIrp函数来完成的。
在这个函数内部首选使用 IoAcquireCancelSpinLock获取取消IRP自旋锁,然后再通过IoSetCancelRoutine获取设置的完成例程,并置新的完成例程为NULL。然后通过IRP的Irp->CancelIrql传递上面获取的取消IRP自旋锁,最后调用取消例程。
Irp->CancelIrql = OldIrql;
CancelRoutine(IoGetCurrentIrpStackLocation(Irp)->DeviceObject, Irp);
这其料也就解释了为什么我们在设置的取消例程中IoCompleteIrp后,再释放自旋锁
IoReleaseCancelSpinLock(Irp->CancelIrql);//释放取消锁
至于IoCancel的调用时机,我们以HidMouse为例,其发生在IRP_MN_REMOVE_DEVICE时。
case IRP_MN_REMOVE_DEVICE:
/* FIXME synchronization */
/* cancel irp */
IoCancelIrp(DeviceExtension->Irp);
而这个DeviceExtension->Irp
其实是HIDMouse在AddDevice时,创建的一个IRP,其一直在Read数据时,复用这个IRP。源码可通过KbdHid_InitiateRead来跟踪,这里我们就不深究了~
附:IoCancelIrp的源代码
BOOLEAN NTAPI IoCancelIrp ( IN PIRP Irp )
{
KIRQL OldIrql;
PDRIVER_CANCEL CancelRoutine;
IOTRACE(IO_IRP_DEBUG,
"%s - Canceling IRP %p\n",
__FUNCTION__,
Irp);
ASSERT(Irp->Type == IO_TYPE_IRP);
/* Acquire the cancel lock and cancel the IRP */
IoAcquireCancelSpinLock(&OldIrql);
Irp->Cancel = TRUE;
/* Clear the cancel routine and get the old one */
CancelRoutine = IoSetCancelRoutine(Irp, NULL);
if (CancelRoutine)
{
/* We had a routine, make sure the IRP isn't completed */
if (Irp->CurrentLocation > (Irp->StackCount + 1))
{
/* It is, bugcheck */
KeBugCheckEx(CANCEL_STATE_IN_COMPLETED_IRP,
(ULONG_PTR)Irp,
(ULONG_PTR)CancelRoutine,
0,
0);
}
/* Set the cancel IRQL And call the routine */
Irp->CancelIrql = OldIrql;
CancelRoutine(IoGetCurrentIrpStackLocation(Irp)->DeviceObject, Irp);
return TRUE;
}
/* Otherwise, release the cancel lock and fail */
IoReleaseCancelSpinLock(OldIrql);
return FALSE;
}
应用层的IrpCancel
在应用层,有一个函数叫做CancelIo,这个函数调用 NtCancelIoFile函数。
而 NtCancelIoFile
这个函数会获取当前线程的&Thread->IrpList;
,这个变量中存放着没有被完成的IRP,然后依次取出调用IoCancelIrp(Irp);
来进行IRP的完成。
CancelIo函数原型比较简单,只需要传入打开的设备或文件句柄即可。
BOOL WINAPI CancelIo(IN HANDLE hFile)