The IRP cancel routine is changed for the case when the IRP is the current IRP. Listing 16.5 shows that in this case, the IRP is completed and IoStartNextPacket called.

Listing 16.5 Alternative IRP cancel routine

if (Irp==fdo->CurrentIrp) {

 DebugPrintMsg('WdmIoCancelIrp: IRP just dequeued for StartIo');

 // IRP has just been dequeued but StartIo has not had a chance

 // to remove this cancel routine yet. Irp->Cancel flag already set.

 IoReleaseCancelSpinLock(Irp->CancelIrql);

 // Cancel IRP and start next one

 CompleteIrp(Irp, STATUS_CANCELLED);

 IoStartNextPacket(fdo, TRUE);

} else

 …

The start of the StartIo routine must also be changed. The main job is to remove the cancel routine using IoSetCancelRoutine. However, there is a small chance that the IRP has already been cancelled. The code in Listing 16.6 checks the IRP Cancel field first. If it has been cancelled, StartIo simply exits.

In my mind, this technique has a potential race condition. When the Cancel routine completes, the IRP memory may disappear straightaway. However, the StartIo routine may be just about to run on another processor. Accessing the IRP Cancel field might, therefore, cause an access violation (or refer to the next IRP to use this IRP structure). Moving the CompleteIrp and IoStartNextPacket calls into the StartIo routine (if Irp->Cancel is set) would solve this problem, but it would mean that the cancel routine does not complete the IRP.

Listing 16.6 Alternative StartIo initial processing

// Check whether cancelled already KIRQL OldIrql;

IoAcquireCancelSpinLock(&OldIrql);

if (Irp->Cancel) {

 IoReleaseCancelSpinLock(OldIrql);

 // IoStartNextPacket called by cancel routine

 return;

}

// Remove cancel routine

IoSetCancelRoutine(Irp, NULL);

IoReleaseCancelSpinLock(OldIrql);

Cleanup IRP Handling

The Cleanup IRP is issued to cancel any IRPs that are outstanding when a file handle is closed. Even if the IRPs have cancel routines, they are not called.

To handle it correctly, the driver ought to cancel only IRPs that belong to the correct file handle. Only queued IRPs whose FileObject field matches the Cleanup IRP's stack FileObject field should be cancelled.

The WdmIo Cleanup IRP handler, WdmIoDispatchCleanup, shown in Listing 16.7, does not perform this FileObject check. This makes the code simpler.

WdmIoDispatchCleanup must hold the Cancel spin lock wherever it accesses the device queue. However, you must not be holding the Cancel spin lock when an IRP is completed.

The code keeps extracting IRPs using KeRemoveDeviceQueue until the device queue is empty. A pointer to the IRP is found using the usual CONTAINING_RECORD machinations. The IRP is marked for cancelling and its cancel routine is removed. The Cancel spin lock is released before UnlockDevice is called and the IRP is completed with status STATUS_CANCELLED.

The Cleanup IRP handler then goes on to try to cancel the IRP currently being processed by WdmIoStartIo or its follow-on code. CancelCurrentIrpSynch is called as a Critical Section routine. This returns TRUE if a transfer is in progress (i.e., if the device extension Timeout field is greater than or equal to zero). If a transfer is in progress, WdmIoDpcForIsr is called to complete the IRP with status STATUS_CANCELLED. The Timeout field and WdmIoDpcForIsr are described in the next chapter.

To handle the FileObject check correctly, you must still remove each IRP from the queue in turn. If the IRP's FileObject does not match the Cleanup FileObject, the IRP must be put in a temporary holding queue. At the end, all these IRPs must be reinserted in the main device queue.

Listing 16.7 WdmIo Cleanup IRP handling

NTSTATUS WdmIoDispatchCleanup(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PWDMIO_DEVICE_EXTENSION dx = (PWDMIO_DEVICE_EXTENSION)fdo->DeviceExtension;

 DebugPrintMsg('WdmIoDispatchCleanup');

 KIRQL OldIrql;

 IoAcquireCancelSpinLock(&OldIrql);

 // Cancel all IRPs in the I/O Manager maintained queue in device object

 PKDEVICE_QUEUE_ENTRY QueueEntry;

 while((QueueEntry=KeRemoveDeviceQueue(&fdo->DeviceQueue)) != NULL) {

  PIRP CancelIrp = CONTAINING_RECORD(QueueEntry, IRP, Tail.Overlay.DeviceQueueEntry);

  CancelIrp->Cancel = TRUE;

  CancelIrp->CancelIrql = OldIrql;

  CancelIrp->CancelRoutine = NULL;

  IoReleaseCancelSpinLock(OldIrql);

  DebugPrint('WdmIoDispatchCleanup: Cancelling %I', CancelIrp);

  UnlockDevice(dx);

  CompleteIrp(CancelIrp, STATUS_CANCELLED);

  IoAcquireCancelSpinLock(&OldIrql);

 }

 IoReleaseCancelSpinLock(OldIrql);

 // Forceably cancel any in-progress IRP

 if (dx->Timeout!=-1) {

  if (KeSynchronizeExecution(dx->InterruptObject, (PKSYNCHRONIZE_ROUTINE)CancelCurrentIrpSynch, dx)) {

   if (fdo->CurrentIrp!=NULL) {

    DebugPrint('WdmIoDispatchCleanup: Cancelled in-progress IRP %I', fdo->CurrentIrp);

    WdmIoDpcForIsr(NULL, fdo, fdo->CurrentIrp, dx);

   }

  }

 }

 return CompleteIrp(Irp, STATUS_SUCCESS);

}

static BOOLEAN CancelCurrentIrpSynch(IN PWDMIO_DEVICE_EXTENSION dx) {

 if (dx->Timeout==-l) return FALSE;

 dx->Timeout = –1;

 dx->TxStatus = STATUS_CANCELLED;

 return TRUE;

}

Testing, Cancelling, and Cleanup

I amended WdmIoTest so that I could test the WdmIo driver's IRP cancelling and Cleanup IRP handling.

The WdmIoCancel application is substantially the same as that of WdmIoTest and is contained in the book software WdmIoCancel directory. You will have to uncomment some of the code and recompile to undertake some of the tests. The PHDIoCancel application does similar tests for the PHDIo driver.

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату
×