Compartir a través de


Uso de un bloqueo de número de Driver-Supplied

Los controladores que administran sus propias colas de IRP pueden usar un bloqueo de giro proporcionado por el controlador, en lugar del bloqueo de número de cancelación del sistema, para sincronizar el acceso a las colas. Puede mejorar el rendimiento evitando el uso del bloqueo de número de cancelación, excepto cuando sea absolutamente necesario. Dado que el sistema solo tiene un bloqueo de giro de cancelación, es posible que un controlador tenga que esperar a que ese bloqueo de giro esté disponible. El uso de un bloqueo de giro proporcionado por el controlador elimina este retraso potencial y hace que el bloqueo de giro de cancelación esté disponible para el administrador de E/S y otros controladores. Aunque el sistema todavía adquiere el bloqueo de giro de cancelación cuando llama a la rutina Cancel del controlador, un controlador puede usar su propio bloqueo de giro para proteger su cola de IRP.

Incluso si un controlador no pone en cola los IRP pendientes, pero conserva la propiedad de alguna otra manera, ese controlador debe establecer una rutina Cancel para irP y debe usar un bloqueo de número para proteger el puntero IRP. Por ejemplo, supongamos que un controlador marca un IRP pendiente y, a continuación, pasa el puntero IRP como contexto a una rutina de IoTimer . El controlador debe establecer una rutina Cancel que cancele el temporizador y debe usar el mismo bloqueo de giro en la rutina Cancel y la devolución de llamada del temporizador al acceder al IRP.

Cualquier controlador que ponga en cola sus propios IRP y use su propio bloqueo de número debe hacer lo siguiente:

  • Cree un bloqueo de número para proteger la cola.

  • Establezca y borre la rutina Cancel solo mientras mantiene este bloqueo de giro.

  • Si la rutina Cancel comienza a ejecutarse mientras el controlador pone en cola un IRP, permita que la rutina Cancel complete el IRP.

  • Adquiera el bloqueo que protege la cola en la rutina Cancelar .

Para crear el bloqueo de número, el controlador llama a KeInitializeSpinLock. En el ejemplo siguiente, el controlador guarda el bloqueo de número en una estructura de DEVICE_CONTEXT junto con la cola que ha creado:

typedef struct {
    LIST_ENTRYirpQueue;
    KSPIN_LOCK irpQueueSpinLock;
    ...
} DEVICE_CONTEXT;

VOID InitDeviceContext(DEVICE_CONTEXT *deviceContext)
{
    InitializeListHead(&deviceContext->irpQueue);
    KeInitializeSpinLock(&deviceContext->irpQueueSpinLock);
}

Para poner en cola un IRP, el controlador adquiere el bloqueo de número, llama a InsertTailList y, a continuación, marca el IRP pendiente, como en el ejemplo siguiente:

NTSTATUS QueueIrp(DEVICE_CONTEXT *deviceContext, PIRP Irp)
{
   PDRIVER_CANCEL  oldCancelRoutine;
   KIRQL  oldIrql;
   NTSTATUS  status;

   KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);

   // Queue the IRP and call IoMarkIrpPending to indicate
   // that the IRP may complete on a different thread.
   // N.B. It is okay to call these inside the spin lock
   // because they are macros, not functions.
   IoMarkIrpPending(Irp);
   InsertTailList(&deviceContext->irpQueue, &Irp->Tail.Overlay.ListEntry);

   // Must set a Cancel routine before checking the Cancel flag.
   oldCancelRoutine = IoSetCancelRoutine(Irp, IrpCancelRoutine);
   ASSERT(oldCancelRoutine == NULL);

   if (Irp->Cancel) {
      // The IRP was canceled. Check whether our cancel routine was called.
      oldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
      if (oldCancelRoutine) {
         // The cancel routine was NOT called.  
         // So dequeue the IRP now and complete it after releasing the spin lock.
         RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
         // Drop the lock before completing the request.
         KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
         Irp->IoStatus.Status = STATUS_CANCELLED; 
         Irp->IoStatus.Information = 0;
         IoCompleteRequest(Irp, IO_NO_INCREMENT);
         return STATUS_PENDING;

      } else {
         // The Cancel routine WAS called.  
         // As soon as we drop our spin lock, it will dequeue and complete the IRP.
         // So leave the IRP in the queue and otherwise do not touch it.
         // Return pending since we are not completing the IRP here.
         
      }
   }

   KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);

   // Because the driver called IoMarkIrpPending while it held the IRP,
   // it must return STATUS_PENDING from its dispatch routine.
   return STATUS_PENDING;
}

Como se muestra en el ejemplo, el controlador mantiene su bloqueo de número mientras establece y borra la rutina Cancel . La rutina de puesta en cola de ejemplo contiene dos llamadas a IoSetCancelRoutine.

La primera llamada establece la rutina Cancel para irP. Sin embargo, dado que el IRP podría haberse cancelado mientras se ejecuta la rutina de puesta en cola, el controlador debe comprobar el miembro Cancel del IRP.

  • Si se establece Cancel , se ha solicitado la cancelación y el controlador debe realizar una segunda llamada a IoSetCancelRoutine para ver si se llamó a la rutina Cancel establecida anteriormente.

  • Si el IRP se ha cancelado pero aún no se ha llamado a la rutina Cancel , la rutina actual quita el IRP y la completa con STATUS_CANCELLED.

  • Si se ha cancelado el IRP y ya se ha llamado a la rutina Cancel , la devolución actual marca el IRP pendiente y devuelve STATUS_PENDING. La rutina Cancel completará el IRP.

En el ejemplo siguiente se muestra cómo quitar un IRP de la cola creada anteriormente:

PIRP DequeueIrp(DEVICE_CONTEXT *deviceContext)
{
   KIRQL oldIrql;
   PIRP nextIrp = NULL;

   KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);

   while (!nextIrp && !IsListEmpty(&deviceContext->irpQueue)) {
      PDRIVER_CANCEL oldCancelRoutine;
      PLIST_ENTRY listEntry = RemoveHeadList(&deviceContext->irpQueue);

      // Get the next IRP off the queue.
      nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);

      // Clear the IRP's cancel routine.
      oldCancelRoutine = IoSetCancelRoutine(nextIrp, NULL);

      // IoCancelIrp() could have just been called on this IRP. What interests us
      // is not whether IoCancelIrp() was called (nextIrp->Cancel flag set), but
      // whether IoCancelIrp() called (or is about to call) our Cancel routine.
      // For that, check the result of the test-and-set macro IoSetCancelRoutine.
      if (oldCancelRoutine) {
         // Cancel routine not called for this IRP. Return this IRP.
         ASSERT(oldCancelRoutine == IrpCancelRoutine);
      } else {
         // This IRP was just canceled and the cancel routine was (or will be)
         // called. The Cancel routine will complete this IRP as soon as we
         // drop the spin lock, so do not do anything with the IRP.
         // Also, the Cancel routine will try to dequeue the IRP, so make 
         // the IRP's ListEntry point to itself.
         ASSERT(nextIrp->Cancel);
         InitializeListHead(&nextIrp->Tail.Overlay.ListEntry);
         nextIrp = NULL;
      }
   }

   KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);

   return nextIrp;
}

En el ejemplo, el controlador adquiere el bloqueo de número asociado antes de acceder a la cola. Mientras mantiene presionado el bloqueo de número, comprueba que la cola no está vacía y obtiene el siguiente IRP fuera de la cola. A continuación, llama a IoSetCancelRoutine para restablecer la rutina Cancel del IRP. Dado que el IRP se puede cancelar mientras el controlador quita el IRP y restablece la rutina Cancel , el controlador debe comprobar el valor devuelto por IoSetCancelRoutine. Si IoSetCancelRoutine devuelve NULL, lo que indica que se ha llamado a la rutina Cancel o pronto, la rutina de puesta en cola permite que la rutina Cancel complete el IRP. A continuación, libera el bloqueo que protege la cola y devuelve.

Tenga en cuenta el uso de InitializeListHead en la rutina anterior. El controlador podría volver a poner en cola el IRP, de modo que la rutina Cancel pueda quitarla de la cola, pero es más sencillo llamar a InitializeListHead, que reinicializa el campo ListEntry del IRP para que apunte al propio IRP. El uso del puntero de referencia automática es importante porque la estructura de la lista podría cambiar antes de que la rutina Cancel adquiera el bloqueo de número. Y si la estructura de lista cambia, posiblemente haciendo que el valor original de ListEntry no sea válido, la rutina Cancel podría dañar la lista cuando quita la cola del IRP. Pero si ListEntry apunta al IRP en sí, la rutina Cancel siempre usará el IRP correcto.

La rutina Cancelar , a su vez, simplemente hace lo siguiente:

VOID IrpCancelRoutine(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
   DEVICE_CONTEXT  *deviceContext = DeviceObject->DeviceExtension;
   KIRQL  oldIrql;

   // Release the global cancel spin lock.  
   // Do this while not holding any other spin locks so that we exit at the right IRQL.
   IoReleaseCancelSpinLock(Irp->CancelIrql);

   // Dequeue and complete the IRP.  
   // The enqueue and dequeue functions synchronize properly so that if this cancel routine is called, 
   // the dequeue is safe and only the cancel routine will complete the IRP. Hold the spin lock for the IRP
   // queue while we do this.

   KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);

   RemoveEntryList(&Irp->Tail.Overlay.ListEntry);

   KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);

   // Complete the IRP. This is a call outside the driver, so all spin locks must be released by this point.
   Irp->IoStatus.Status = STATUS_CANCELLED;
   IoCompleteRequest(Irp, IO_NO_INCREMENT);
   return;
}

El administrador de E/S siempre adquiere el bloqueo de número de cancelación global antes de llamar a una rutina Cancel , por lo que la primera tarea de la rutina Cancelar es liberar este bloqueo de giro. A continuación, adquiere el bloqueo de giro que protege la cola del controlador de IRP, quita el IRP actual de la cola, libera su bloqueo de giro, completa el IRP con STATUS_CANCELLED y sin aumento de prioridad, y devuelve.

Para obtener más información sobre cómo cancelar bloqueos de número, consulte las notas del producto Cancelar lógica en controladores de Windows .