实际开发中遇到这样一个问题:
上层程序通过连续调用ReadFile读取设备上的数据,相应的IRP通过IoStartPacket进行串行化处理,每当前一个IRP完成后,调用IoStartNextPacket取出下一个IRP。。。如此周而复始,在硬件动作正常的情况下,这种处理方法不会有问题,但当硬件不可靠时,会发生有的IRP永远无法完成的情况。本例中,IRP的设置完成以及IoStartNextPacket的调用是在DPC函数中完成的,硬件完成数据传输之后,通过发送中断的方式告诉驱动本次ReadFile的IRP已经完成,而一旦中途某个中断丢失后,整个系统将无法继续读取数据,中断丢失的原因有很多种,主要有如下:
1:由于MSI采用沿触发的方式,某种极端条件下会出现中断信号没有采集到的情况
2:中断包传输丢失
3:PCIe设备采用共享中断的方式,其他设备的中断服务例程处理不规范时会导致属于我们设备的中断丢失(如返回值不对,导致I/O manager错误判断了该中断的目标设备)
综上所述,为了应对中断丢失的情况,必须将在IRP处理超时后,将该IRP取消掉,从而使系统能继续运行,具体操作如下:
注册ReadFile操作例程:
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObj, PUNICODE_STRING RegistryPath)
{
…………
DriverObj->MajorFunction[IRP_MJ_READ] = DemoPciReadFile;
…………
}
DemoPciReadFile函数中将IRP设置为挂起,并调用IoStartPacket将IRP压入队列中,此时须设置
该IRP的取消例程DemoPciOnCancel(当上层调用CancelIo时,会执行该函数),另外在StartIo例程中,不能调用IoSetCancelRoutine(Irp,NULL)将该IRP的取消例程设为NULL,否则该IRP还是无法取消,上层取消IRP时会出现驱动僵死的情况。
NTSTATUS DemoPciReadFile ( IN PDEVICE_OBJECT DeviceObject,
IN PIRP pIrp
)
{
DebugPrint("DemoPciReadFile () Start\n");
IoMarkIrpPending(pIrp);
IoStartPacket(DeviceObject,pIrp,NULL,DemoPciOnCancel);
DebugPrint("DemoPciReadFile () End\n\n");
return((NTSTATUS)STATUS_PENDING);
}
中断收到后,DPC例程中将会完成IRP并调用IoStartNextPacket取出下一个IRP进行处理。
Irp->IoStatus.Information = RequestSize;
Irp->IoStatus.Status=(NTSTATUS)STATUS_SUCCESS;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
IoStartNextPacket(DeviceObject,TRUE);
系统调用取消例程前,先调用IoAcquireCancelSpinLock获取自旋锁,所以取消例程中必须调用IoReleaseCancelSpinLock来释放该自旋锁,否则驱动将被锁死。
VOID DemoPciOnCancel (IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
KIRQL oldIrql;
PDEMO_PCI_DEVICE_EXT pDevExt;
PIO_STACK_LOCATION IrpStack;
DebugPrint("DemoPciOnCancel () Start\n");
pDevExt = (PDEMO_PCI_DEVICE_EXT)DeviceObject->DeviceExtension;
IrpStack = IoGetCurrentIrpStackLocation(Irp);
if(DeviceObject->CurrentIrp == Irp) //该IRP已经不再队列中,直接取出下一个IRP执行即可
{
DebugPrint("DemoPciOnCancel: Current Irp\n");
//Save the Irql and release the SpinLock which was hold by I/O manager
oldIrql = Irp->CancelIrql;
IoReleaseCancelSpinLock(Irp->CancelIrql);
IoStartNextPacket(DeviceObject,TRUE);
//Lower the IRQL
KeLowerIrql(oldIrql);
}
else //该IRP还在队列中为执行,调用KeRemoveEntryDeviceQueue将该IRP移除出队列
{
DebugPrint("DemoPciOnCancel: Not Current Irp\n");
KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,&Irp->Tail.Overlay.DeviceQueueEntry);
IoReleaseCancelSpinLock(Irp->CancelIrql);
}
IoSetCancelRoutine(Irp,NULL);
// Set the IRP's Status
Irp->IoStatus.Information=0;
Irp->IoStatus.Status=(NTSTATUS)STATUS_CANCELLED;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
DebugPrint("DemoPciOnCancel () End\n");
}
驱动中如此实现之后,上层软件发现某个IRP完成超时时调用CancelIo取消之后,驱动就能继续响应其余的IRP,从而实现系统自我恢复。