私のデバイスは削除されましたが、なぜ IRP を受信し続けるのでしょうか?

最終更新日: 2005年9月4日

WDM ドライバは、デバイス削除の IRP を処理してドライバが割り当てたメモリを解放した後、追加の IRP を受信する場合があります。追加の IRP の処理中に、解放されたメモリの参照を試みると、システムがクラッシュする場合があります。

ドライバは、下記のいずれかの理由により、削除されたデバイスに対する IRP を受信する場合があります。

デバイスが削除された後、別のコンポーネントが I/O を送信する場合があります。コンポーネントは、IRP を送信するため、ターゲット デバイス (またはファイル) オブジェクトへのポインタを取得し、そのデバイス オブジェクト上の参照を取得します。または、コンポーネントの代わりに I/O マネージャが参照を取得します。参照により、ターゲット デバイスまたはファイル オブジェクトが存在し続け、ターゲット ドライバがデバイス オブジェクトやデバイス拡張にアクセスできるようになります。ただし、コンポーネントは、ターゲット デバイス上のプラグ アンド プレイ通知の対象として登録されていない限り、デバイスがまだ存在しているかどうかを判断できません。

デバイス削除要求の前に送信された I/O 要求は、ターゲット ドライバがデバイス削除要求を処理した後に到着する場合があります。このようなことが起きるかどうかは、I/O を送信するコンポーネント、デバイス スタック内でのターゲット ドライバの位置、およびそのデバイスに対する保留中のその他の I/O 要求に依存します。

削除ロックは、ターゲット ドライバに対する IRP を安全に処理するために重要です。ドライバは、通常、削除ロックをデバイス拡張に割り当てて、AddDevice ルーチンで初期化します。その後、ドライバは、I/O 処理を開始するたびにディスパッチ ルーチンから IoAcquireRemoveLock を呼び出して削除ロックを取得し、I/O 処理を完了するたびに IoReleaseRemoveLock を呼び出して削除ロックを解放します。削除ロックは、参照カウントを保持し、繰り返し取得できます。

ドライバがいつ IoReleaseRemoveLock を呼び出すべきかは、IRP の処理方法に依存します。処理方法としては、完了ルーチンを設定せずに次の下位ドライバに渡す、次の下位ドライバに渡さずに IRP を完了する、IRP を下位に渡して完了ルーチンを設定する、のいずれかが考えられます。

ドライバは、IRP を次の下位ドライバに渡して IoCompletion ルーチンを設定しない場合、IoCallDriver を呼び出して IRP を下位に渡した後、IoReleaseRemoveLock を呼び出す必要があります。例 :

      NTSTATUS
      MyDispatchRoutine (
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp
      )
      {
      PDEVICE_EXTENSION   devExt;
      NTSTATUS    status;
    
      devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
      
      status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp);
      if (!NT_SUCCESS (status)) { // maybe device is being removed.
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
        }

      // Do request-specific processing
      . . . 

      // Pass down the IRP and release the lock.
      IoSkipCurrentIrpStackLocation (Irp);
      status = IoCallDriver (devExt->NextLowerDriver, Irp);
      IoReleaseRemoveLock (&devExt->RemoveLock, Irp);       
      return status;
      }

ドライバは、IRP を完了してそれを次の下位ドライバに渡さない場合、IoCompleteRequest を呼び出した後、IoReleaseRemoveLock を呼び出す必要があります。下記に例を示します。

      NTSTATUS
      MyDispatchRoutine (
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp
      )
      {
      PDEVICE_EXTENSION   devExt;
      NTSTATUS    status;
    
      devExt = 
         (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
      
      status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp);
      if (!NT_SUCCESS (status)) { // maybe device is being removed.
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
        }

      // Do request-specific processing
      . . . 

      // Request-specific processing is done. Complete the IRP
      // and release the lock.
      Irp->IoStatus.Status = status;
      IoCompleteRequest (Irp, IO_NO_INCREMENT );
      IoReleaseRemoveLock (&devExt->RemoveLock, Irp);       
      return status;
      }

ドライバは、IRP を次の下位ドライバに渡して IoCompletion ルーチンを設定する場合、IoCompletion ルーチンから IoReleaseRemoveLock を呼び出す必要があります。下記に例を示します。

      NTSTATUS
      MyCompletionRoutine (
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp,
        IN PVOID Context
        )
      {
        PDEVICE_EXTENSION   data;
        UNREFERENCED_PARAMETER (DeviceObject);

        data = (PDEVICE_EXTENSION) Context;

        IoReleaseRemoveLock (&data->RemoveLock, Irp);
        return STATUS_SUCCESS;
      }

ドライバは、デバイス削除要求 (IRP_MN_REMOVE_DEVICE) を処理するとき、そのドライバの DispatchPnP ルーチンで取得した削除ロックを、IoReleaseRemoveLockAndWait を呼び出して解放します。この呼び出しは、削除ロックに関連する参照カウントがゼロに達するまで、すなわちその他の削除ロックの取得がすべて解放されるまで、戻りません。IoReleaseRemoveLockAndWait が戻った後、ドライバは必要に応じて IRP をそのドライバのデバイス スタックに渡し、IoDetachDevice を呼び出してドライバのデバイス オブジェクトをデバイス スタックから削除します。その後、デバイスが AddDevice ルーチンに割り当てたプール メモリなどのリソースを解放します。最後に、ドライバは IoDeleteDevice を呼び出して、デバイス オブジェクトを削除の対象としてマークします。例 :

    // Wait for all outstanding requests to complete
    IoReleaseRemoveLockAndWait(&devExt->RemoveLock, Irp);

    // Pass the remove IRP to the next lower driver
    Irp->IoStatus.Status = STATUS_SUCCESS;
    IoSkipCurrentIrpStackLocation(Irp);
    status = IoCallDriver(devExt->NextLowerDriver, Irp);

    //Detach from the stack, free resources, and 
    //  delete the device object.
    IoDetachDevice(devExt->NextLowerDriver); 
    RtlFreeUnicodeString (&devExt ->SymbolicLinkName);
    ExFreePool (devExt ->InputData);
    IoDeleteDevice(DeviceObject);

I/O マネージャは、デバイス オブジェクトに対するすべての参照が解放されるまで、そのオブジェクトを実際に削除しません。したがって、有効なデバイス オブジェクトとデバイス拡張が、ドライバのデバイス削除処理の完了後も存在する可能性があります。ただし、ドライバは既にそのドライバのリソースを解放しており、デバイス拡張に格納されたそれらのリソースへのポインタも無効になっています。

ドライバのデバイス削除処理が完了してから I/O マネージャがデバイス オブジェクトを削除するまでの間に、そのドライバが別の I/O 要求を受信すると、問題が生じる場合があります。ドライバは、要求を処理する際にデバイス拡張からの無効なポインタの 1 つの参照解除を試みる可能性があり、それによってシステムがクラッシュします。

この問題を防ぐため、ドライバは、プラグ アンド プレイ要求や電源要求だけでなく、すべての種類の I/O 要求に対する削除ロックを取得する必要があります。ほとんどのドライバは、既に削除ロックをドライバの DispatchPnP ルーチンや DispatchPower ルーチンで取得しており、プラグ アンド プレイ IRP および電源 IRP の処理中に削除されないようにしています。ただし、その他の種類の IRP がデバイスの削除後に送られてくる可能性があるので、ドライバはその他の種類の I/O 要求に対するディスパッチ ルーチンでも削除ロックを取得する必要があります。

最も簡単な方法としては、IRP の送信時に I/O ディスパッチ ルーチンで IoAcquireRemoveLock を呼び出します。IoAcquireRemoveLock は、STATUS_DELETE_PENDING を戻して、デバイスが削除されることを示します。IoAcquireRemoveLock がこの状態を戻すか、STATUS_SUCCESS 以外の状態を戻した場合、ドライバは I/O 要求を失敗させる必要があります。

どうすればよいでしょうか。

IRP の送信時に I/O ディスパッチ ルーチンで IoAcquireRemoveLock を呼び出して、デバイス拡張に格納されているあらゆるポインタの参照が解除される前に削除ロックを取得します。

IoAcquireRemoveLock が STATUS_SUCCESS を戻さない場合、I/O 要求が失敗するようにします。

ドライバが IRP の処理を完了したとき、IoReleaseRemoveLock を呼び出します。

IoDetachDevice および IoDeleteDevice を呼び出す前のデバイス削除処理中に、IoReleaseRemoveLockAndWait を呼び出します。

詳細情報 :
Windows DDK
Using Remove Locks



この情報はお役に立ちましたか?