Windows驱动
+ -

IRP无法取消的无限等待问题

2021-07-01 180 0

有时有这样一种功能,就是当上层设备或应用和我们开发的驱动的时候,而我们这时根本无法完成本次的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)

0 篇笔记 写笔记

作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!