IRP的取消机制IoCancelIrp
2021-07-14
444
3
在驱动层,可以使用取消例程实现对IRP的取消。
IRP的取消主要涉及几个函数,分别是:
- IoSetCancelRoutine 设置取消IRP例程
- IoCancelIrp
函数看起来比较简单,但过程却是并不简单。
REACTOS提供的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;
}
从代码来看,这里首先获取一个自旋锁 OldIrql,然后设置当前IRP的Cancel状态为TRUE.
然后获取当前IRP是否包含有取消例程,如果没有,就释放自旋锁,并返回FALSE.这说明IoCancelIrp函数调用了一个寂寞,显然这是不我们希望的。
如果含有取消例程,那么会将当前自锁旋保存在当前IRP的Irp->CancelIrql成员中,然后调用该IRP的取消例程。
注意,这里我们看到,在调用取消例程之前,获取了一个自锁旋,所以在取消例程中是需要释放自旋锁的,因为IoCancelIrp并没有释放自旋锁的。
在取消例程中,一般需要遵信以下几个规则,微软的官方文档给出了要求,详见:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/kernel/implementing-a-cancel-routine
简而言之,就是要实现:
- 调用 IoReleaseCancelSpinLock 以释放系统的取消旋转锁。
- 将IRP的status设置为STATUS_CANCELLED, Information设为0
- 使用函数IoCompleteRequest完成IRP.
- 持有自旋锁时,切勿使用 IRP 调用 IoCompleteRequest 。 尝试在持有自旋锁时完成 IRP 会导致死锁。
这里提供一个简单的取消例程:
VOID IrpCancelRoutine(PDEVICE_OBJECT devObj, PIRP Irp)
{
UNREFERENCED_PARAMETER(devObj);
IoReleaseCancelSpinLock(Irp->CancelIrql);
DbgPrint("IrpCancelRoutine.......\n");
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
参考资料:http://www.usbzh.com/article/detail-555.html