Compartir a través de


Uso de bloqueos de número: un ejemplo

Minimizar el tiempo que un controlador mantiene los bloqueos de giro puede mejorar significativamente tanto el rendimiento del controlador como del sistema en general. Por ejemplo, considere la ilustración siguiente, que muestra cómo un bloqueo de número de interrupción protege los datos específicos del dispositivo que se deben compartir entre un ISR y las rutinas StartIo y DpcForIsr en un equipo SMP.

diagrama que ilustra el uso de un bloqueo de número de interrupción.

  1. Mientras el ISR del controlador se ejecuta en DIRQL en un procesador, su rutina StartIo se ejecuta en DISPATCH_LEVEL en un segundo procesador. El controlador de interrupción del kernel contiene interruptSpinLock para el ISR del controlador, que accede a datos protegidos específicos del dispositivo, como el estado o los punteros a los registros de dispositivos (SynchronizeContext), en la extensión del dispositivo del controlador. La rutina StartIo , que está lista para acceder a SynchronizeContext, llama a KeSynchronizeExecution, pasando un puntero a los objetos de interrupción asociados, SynchronizeContext compartido y la rutina SynchCritSection del controlador (AccessDevice en la figura anterior).

    Hasta que el ISR vuelva, liberando así el objeto InterruptSpinLock del controlador, KeSynchronizeExecutiongira en el segundo procesador, lo que impide que AccessDevice toque SynchronizeContext. Sin embargo, KeSynchronizeExecution también genera IRQL en el segundo procesador al SynchronizeIrql de los objetos de interrupción, lo que impide que se produzca otra interrupción del dispositivo en ese procesador para que AccessDevice se pueda ejecutar en DIRQL tan pronto como el ISR devuelva. Sin embargo, pueden producirse interrupciones de DIRQL más altas para otros dispositivos, interrupciones de reloj e interrupciones de error de energía en cualquiera de los procesadores.

  2. Cuando el ISR pone en cola el DpcForIsr del controlador y devuelve, AccessDevice se ejecuta en el segundo procesador en synchronizeIrql de los objetos de interrupción asociados y accede a SynchronizeContext. Mientras tanto, DpcForIsr se ejecuta en otro procesador en DISPATCH_LEVEL IRQL. El DpcForIsr también está listo para acceder a SynchronizeContext, por lo que llama a KeSynchronizeExecution con los mismos parámetros que la rutina StartIo hizo en el paso 1.

    Cuando KeSynchronizeExecution adquiere el bloqueo de número y ejecuta AccessDevice en nombre de la rutina StartIo , la rutina de sincronización proporcionada por el controlador AccessDevice tiene acceso exclusivo a SynchronizeContext. Dado que AccessDevice se ejecuta en SynchronizeIrql, el ISR del controlador no puede adquirir el bloqueo de giro y acceder al mismo área hasta que se libere el bloqueo de giro, incluso si el dispositivo interrumpe en otro procesador mientras accessDevice se está ejecutando.

  3. AccessDevice devuelve y libera el bloqueo de número. La rutina StartIo se reanuda en ejecución en DISPATCH_LEVEL en el segundo procesador. KeSynchronizeExecution ahora ejecuta AccessDevice en el tercer procesador, por lo que puede acceder a SynchronizeContext en nombre de DpcForIsr. Sin embargo, si se hubiera producido una interrupción del dispositivo antes de que el DpcForIsr llamara KeSynchronizeExecution en el paso 2, el ISR podría ejecutarse en otro procesador antes de que KeSynchronizeExecution pudiera adquirir el bloqueo de número y ejecutar AccessDevice en el tercer procesador.

Como se muestra en la ilustración anterior, mientras que una rutina que se ejecuta en un procesador contiene un bloqueo de número, todas las demás rutinas que intentan adquirir ese bloqueo de giro no realizan ningún trabajo. Cada rutina que intenta adquirir un bloqueo de giro ya mantenido simplemente gira en su procesador actual hasta que el soporte libera el bloqueo de giro. Cuando se libera un bloqueo de giro, una y solo una rutina puede adquirirla; todas las demás rutinas que están intentando adquirir el mismo bloqueo de giro seguirán girando.

El titular de cualquier bloqueo de giro se ejecuta en un IRQL elevado: ya sea en DISPATCH_LEVEL para un bloqueo de giro ejecutivo o en un DIRQL para un bloqueo de giro de interrupción. Los autores de llamadas de KeAcquireSpinLock y KeAcquireInStackQueuedSpinLock se ejecutan en DISPATCH_LEVEL hasta que llaman a KeReleaseSpinLock o KeReleaseInStackQueuedSpinLock para liberar el bloqueo. Los autores de llamadas de KeSynchronizeExecution generan automáticamente IRQL en el procesador actual al SynchronizeIrql de los objetos de interrupción hasta que la rutina SynchCritSection proporcionada por el autor de la llamada sale y KeSynchronizeExecution devuelve el control. Para obtener más información, consulte Llamadas a rutinas de soporte técnico que usan bloqueos de número.

Tenga en cuenta el siguiente hecho sobre el uso de bloqueos de número:

Todo el código que se ejecuta en un IRQL inferior no puede realizar ningún trabajo en el conjunto de procesadores ocupados por un soporte de bloqueo de número y por otras rutinas que intentan adquirir el mismo bloqueo de giro.

Por lo tanto, minimizar el tiempo que un controlador mantiene los bloqueos de giro da como resultado un rendimiento del controlador significativamente mejor y contribuye significativamente a mejorar el rendimiento general del sistema.

Como se muestra en la ilustración anterior, el controlador de interrupción del kernel ejecuta rutinas que se ejecutan en el mismo IRQL en una máquina multiprocesador de primera llegada y se sirve por primera vez. El kernel también hace lo siguiente:

  • Cuando una rutina de controlador llama a KeSynchronizeExecution, el kernel hace que la rutina SynchCritSection del controlador se ejecute en el mismo procesador desde el que se produjo la llamada a KeSynchronizeExecution (consulte los pasos 1 y 3).

  • Cuando el ISR de un controlador pone en cola su DpcForIsr, el kernel hace que el DPC se ejecute en el primer procesador disponible en el que IRQL cae por debajo de DISPATCH_LEVEL. Esto no es necesariamente el mismo procesador desde el que se produjo la llamada a IoRequestDpc (consulte el paso 2).

Es posible que las operaciones de E/S controladas por interrupciones de un controlador tienden a serializarse en una máquina uniprocesador, pero las mismas operaciones pueden ser verdaderamente asincrónicas en una máquina SMP. Como se muestra en la ilustración anterior, el ISR de un controlador podría ejecutarse en CPU4 en una máquina SMP antes de que su DpcForIsr comience a procesar un IRP para el que el ISR ya ha controlado una interrupción del dispositivo en CPU1.

En otras palabras, no debe suponer que un bloqueo de número de interrupción puede proteger los datos específicos de la operación que guarda el ISR cuando se ejecuta en un procesador de la sobrescritura por el ISR cuando se produce una interrupción de dispositivo en otro procesador antes de que se ejecute la rutina DpcForIsr o CustomDpc .

Aunque un controlador podría intentar serializar todas las operaciones de E/S controladas por interrupciones para conservar los datos recopilados por el ISR, ese controlador no se ejecutaría mucho más rápido en una máquina SMP que en una máquina uniprocesador. Para obtener el mejor rendimiento posible del controlador mientras permanece portátil entre plataformas de uniprocesador y multiprocesador, los controladores deben usar alguna otra técnica para guardar datos específicos de la operación obtenidos por el ISR para su posterior procesamiento por parte del DpcForIsr.

Por ejemplo, un ISR puede guardar datos específicos de la operación en el IRP que pasa al DpcForIsr. Un refinamiento de esta técnica es implementar un DpcForIsr que consulta un recuento aumentado por ISR, procesa el número de IRP mediante datos proporcionados por ISR y restablece el recuento a cero antes de devolver. Por supuesto, el recuento debe estar protegido por el bloqueo de giro de interrupción del controlador porque su ISR y synchCritSection mantenerían su valor dinámicamente.