先看一些代码如下:
VOID IoStartPacket(PDEVICE_OBJECT device, PIRP Irp, PULONG key, PDRIVER_CANCEL cancel)
{
KIRQL oldirql;
IoAcquireCancelSpinLock(&oldirql);
IoSetCancelRoutine(Irp, cancel);
device->CurrentIrp = Irp;
IoReleaseCancelSpinLock(oldirql);
device->DriverObject->DriverStartIo(device, Irp);
}
VOID IoStartNextPacket(PDEVICE_OBJECT device, BOOLEAN cancancel)
{
KIRQL oldirql;
if (cancancel)
IoAcquireCancelSpinLock(&oldirql);
PKDEVICE_QUEUE_ENTRY p = KeRemoveDeviceQueue(&device->DeviceQueue));
PIRP Irp = CONTAINING_RECORD(p, IRP, Tail.Overlay.DeviceQueueEntry);
device->CurrentIrp = Irp;
if (cancancel)
IoReleaseCancelSpinLock(oldirql);
device->DriverObject->DriverStartIo(device, Irp);
}
BOOLEAN IoCancelIrp(PIRP Irp)
{
IoAcquireCancelSpinLock(&Irp->CancelIrql);
Irp->Cancel = TRUE;
PDRIVER_CANCEL cancel = IoSetCancelRoutine(Irp, NULL);
if (cancel)
{
(*cancel)(device, Irp);
return TRUE;
}
IoReleaseCancelSpinLock(&Irp->CancelIrql);
return FALSE;
}
以上是windows系统的一些伪源码。
startio例程:
VOID
HelloDDKStartIO(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
KIRQL oldirql;
KdPrint(("Enter HelloDDKStartIO\n"));
//获取cancel自旋锁
IoAcquireCancelSpinLock(&oldirql);
if (Irp!=DeviceObject->CurrentIrp||Irp->Cancel)
{
//如果当前有正在处理的IRP,则简单的入队列,并直接返回
//入队列的工作由系统完成,在StartIO中不用负责
IoReleaseCancelSpinLock(oldirql);
KdPrint(("Leave HelloDDKStartIO\n"));
return;
}else
{
//由于正在处理该IRP,所以不允许调用取消例程
//因此将此IRP的取消例程设置为NULL
IoSetCancelRoutine(Irp,NULL);
IoReleaseCancelSpinLock(oldirql);
}
KEVENT event;
KeInitializeEvent(&event,NotificationEvent,FALSE);
//等3秒
LARGE_INTEGER timeout;
timeout.QuadPart = -3*1000*1000*10;
//定义一个3秒的延时,主要是为了模拟该IRP操作需要大概3秒左右时间
KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeout);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0; // no bytes xfered
IoCompleteRequest(Irp,IO_NO_INCREMENT);
//在队列中读取一个IRP,并进行StartIo
IoStartNextPacket(DeviceObject,TRUE);
KdPrint(("Leave HelloDDKStartIO\n"));
}
irp取消例程:
VOID
OnCancelIRP(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
KdPrint(("Enter CancelReadIRP\n"));
if (Irp==DeviceObject->CurrentIrp)
{
//表明当前正在改由StartIo处理
//但StartIo并没有获取cancel自旋锁之前
//这时候需要
KIRQL oldirql = Irp->CancelIrql;
//释放Cancel自旋锁
IoReleaseCancelSpinLock(Irp->CancelIrql);
IoStartNextPacket(DeviceObject,TRUE);
KeLowerIrql(oldirql);
}else
{
//从设备队列中将该IRP抽取出来
KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,&Irp->Tail.Overlay.DeviceQueueEntry);
//释放Cancel自旋锁
IoReleaseCancelSpinLock(Irp->CancelIrql);
}
//设置完成状态为STATUS_CANCELLED
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( Irp, IO_NO_INCREMENT );
KdPrint(("Leave CancelReadIRP\n"));
}
问题:根据以上代码,当取消例程工作时若取消的是当前irp,即Irp==DeviceObject->CurrentIrp,那么就要调用IoStartNextPacket函数,而IoStartNextPacket函数又要调用StartI例程,两者彼此递归调用,取消例程便无法从IoStartNextPacket中返回,同时所有队列中的Irp也将被全部处理,这令我很为不解。请高手详为解释取消例程的这一工作过程并解释一下我的想法中的问题所在,多谢!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课