En la programación de sistemas informáticos , un manejador de interrupciones , también conocido como rutina de servicio de interrupciones o ISR , es un bloque especial de código asociado con una condición de interrupción específica . Los manejadores de interrupciones se inician mediante interrupciones de hardware, instrucciones de interrupción de software o excepciones de software , y se utilizan para implementar controladores de dispositivos o transiciones entre modos de operación protegidos, como llamadas al sistema .
La forma tradicional de un manejador de interrupciones es el manejador de interrupciones de hardware. Las interrupciones de hardware surgen de condiciones eléctricas o protocolos de bajo nivel implementados en lógica digital , generalmente se envían a través de una tabla de vectores de interrupción codificada de forma rígida, de manera asincrónica con respecto al flujo de ejecución normal (según lo permitan los niveles de enmascaramiento de interrupciones), a menudo utilizando una pila separada y entrando automáticamente en un contexto de ejecución diferente (nivel de privilegio) durante la ejecución del manejador de interrupciones. En general, las interrupciones de hardware y sus manejadores se utilizan para manejar condiciones de alta prioridad que requieren la interrupción del código actual que está ejecutando el procesador . [1] [2]
Más tarde, se consideró conveniente que el software pudiera activar el mismo mecanismo mediante una interrupción de software (una forma de interrupción sincrónica). En lugar de utilizar una tabla de distribución de interrupciones codificada de forma rígida a nivel de hardware, las interrupciones de software suelen implementarse a nivel del sistema operativo como una forma de función de devolución de llamada .
Los controladores de interrupciones tienen una multitud de funciones, que varían en función de lo que activó la interrupción y la velocidad a la que el controlador de interrupciones completa su tarea. Por ejemplo, al presionar una tecla en un teclado de computadora [ 1] o mover el mouse , se activan interrupciones que llaman a controladores de interrupciones que leen la tecla o la posición del mouse y copian la información asociada en la memoria de la computadora [2] .
Un manejador de interrupciones es una contraparte de bajo nivel de los manejadores de eventos . Sin embargo, los manejadores de interrupciones tienen un contexto de ejecución inusual, muchas restricciones estrictas en tiempo y espacio, y su naturaleza intrínsecamente asincrónica los hace notoriamente difíciles de depurar mediante la práctica estándar (los casos de prueba reproducibles generalmente no existen), lo que exige un conjunto de habilidades especializadas (un subconjunto importante de la programación de sistemas ) de los ingenieros de software que participan en la capa de interrupción de hardware.
A diferencia de otros controladores de eventos, se espera que los controladores de interrupciones establezcan indicadores de interrupción en valores apropiados como parte de su funcionalidad principal.
Incluso en una CPU que admite interrupciones anidadas, a menudo se llega a un controlador con todas las interrupciones enmascaradas globalmente por una operación de hardware de la CPU. En esta arquitectura, un controlador de interrupciones normalmente guardaría la menor cantidad de contexto necesaria y luego restablecería el indicador de desactivación de interrupción global en la primera oportunidad, para permitir que interrupciones de mayor prioridad interrumpan el controlador actual. También es importante que el controlador de interrupciones suprima la fuente de interrupción actual mediante algún método (a menudo alternando un bit de indicador de algún tipo en un registro periférico) para que la interrupción actual no se repita inmediatamente al salir del controlador, lo que da como resultado un bucle infinito.
Salir de un controlador de interrupciones con el sistema de interrupciones en el estado exacto en cada eventualidad puede ser a veces una tarea ardua y exigente, y su manejo incorrecto es la fuente de muchos errores graves, del tipo que detiene el sistema por completo. Estos errores a veces son intermitentes, y el caso extremo mal manejado no ocurre hasta semanas o meses después de un funcionamiento continuo. La validación formal de los controladores de interrupciones es tremendamente difícil, mientras que las pruebas normalmente identifican solo los modos de falla más frecuentes, por lo que los errores sutiles e intermitentes en los controladores de interrupciones a menudo se envían a los clientes finales.
En un sistema operativo moderno, al ingresar, el contexto de ejecución de un controlador de interrupciones de hardware es sutil.
Por razones de rendimiento, el controlador normalmente se iniciará en el contexto de memoria y ejecución del proceso en ejecución, con el que no tiene una conexión especial (la interrupción está usurpando esencialmente el contexto en ejecución; la contabilidad del tiempo del proceso a menudo acumulará tiempo dedicado a gestionar interrupciones en el proceso interrumpido). Sin embargo, a diferencia del proceso interrumpido, la interrupción suele ser elevada por un mecanismo de CPU codificado de forma rígida a un nivel de privilegio lo suficientemente alto como para acceder directamente a los recursos de hardware.
En un microcontrolador de bajo nivel, el chip puede carecer de modos de protección y no tener una unidad de gestión de memoria (MMU). En estos chips, el contexto de ejecución de un controlador de interrupciones será esencialmente el mismo que el del programa interrumpido, que normalmente se ejecuta en una pila pequeña de tamaño fijo (los recursos de memoria han sido tradicionalmente extremadamente escasos en el extremo inferior). A menudo se proporcionan interrupciones anidadas, lo que exacerba el uso de la pila. Una restricción principal del controlador de interrupciones en este esfuerzo de programación es no exceder la pila disponible en la peor condición posible, lo que requiere que el programador razone globalmente sobre el requisito de espacio de pila de cada controlador de interrupciones implementado y tarea de aplicación.
Cuando se excede el espacio de pila asignado (una condición conocida como desbordamiento de pila ), esto normalmente no se detecta en el hardware de los chips de esta clase. Si se excede la pila y se alcanza otra área de memoria escribible, el controlador normalmente funcionará como se espera, pero la aplicación fallará más tarde (a veces mucho más tarde) debido al efecto secundario del controlador de corrupción de memoria. Si se excede la pila y se alcanza un área de memoria no escribible (o protegida), el error generalmente ocurrirá dentro del propio controlador (generalmente el caso más fácil de depurar más tarde).
En el caso de escritura, se puede implementar una protección de pila centinela: un valor fijo justo más allá del final de la pila legal cuyo valor se puede sobrescribir, pero nunca lo será si el sistema funciona correctamente. Es común observar regularmente la corrupción de la protección de pila con algún tipo de mecanismo de vigilancia. Esto detectará la mayoría de las condiciones de desbordamiento de pila en un punto en el tiempo cercano a la operación infractora.
En un sistema multitarea, cada subproceso de ejecución normalmente tendrá su propia pila. Si no se proporciona una pila de sistema especial para interrupciones, las interrupciones consumirán espacio de pila de cualquier subproceso de ejecución que se interrumpa. Estos diseños suelen contener una MMU, y las pilas de usuario suelen estar configuradas de forma que el desbordamiento de pila quede atrapado por la MMU, ya sea como un error del sistema (para depuración) o para reasignar la memoria y ampliar el espacio disponible. Los recursos de memoria en este nivel de microcontrolador suelen estar mucho menos restringidos, de modo que las pilas se pueden asignar con un margen de seguridad generoso.
En sistemas que admiten un gran número de subprocesos, es mejor que el mecanismo de interrupción de hardware cambie la pila a una pila de sistema especial, de modo que ninguna de las pilas de subprocesos tenga que dar cuenta del uso de interrupciones anidadas en el peor de los casos. Las CPU pequeñas, como la Motorola 6809 de 8 bits de 1978, han proporcionado punteros de pila de usuario y de sistema independientes.
Por muchas razones, es muy deseable que el manejador de interrupciones se ejecute lo más brevemente posible, y se desaconseja enfáticamente (o prohíbe) que una interrupción de hardware invoque llamadas del sistema que puedan bloquearlas. En un sistema con múltiples núcleos de ejecución, las consideraciones de reentrada también son primordiales. Si el sistema proporciona DMA de hardware , pueden surgir problemas de concurrencia incluso con un solo núcleo de CPU. (No es raro que un microcontrolador de nivel medio carezca de niveles de protección y una MMU, pero aún así proporcione un motor DMA con muchos canales; en este escenario, muchas interrupciones suelen ser activadas por el propio motor DMA, y se espera que el manejador de interrupciones asociado actúe con cuidado).
Se ha desarrollado una práctica moderna para dividir los controladores de interrupciones de hardware en elementos de la mitad delantera y de la mitad trasera. La mitad delantera (o primer nivel) recibe la interrupción inicial en el contexto del proceso en ejecución, realiza el trabajo mínimo para restaurar el hardware a una condición menos urgente (como vaciar un búfer de recepción lleno) y luego marca la mitad trasera (o segundo nivel) para su ejecución en el futuro cercano con la prioridad de programación adecuada; una vez invocada, la mitad trasera opera en su propio contexto de proceso con menos restricciones y completa la operación lógica del controlador (como transmitir los datos recién recibidos a una cola de datos del sistema operativo).
En varios sistemas operativos ( Linux , Unix , [ cita requerida ] macOS , Microsoft Windows , z/OS , DESQview y algunos otros sistemas operativos utilizados en el pasado), los controladores de interrupciones se dividen en dos partes: el controlador de interrupciones de primer nivel ( FLIH ) y los controladores de interrupciones de segundo nivel ( SLIH ). Los FLIH también se conocen como controladores de interrupciones duras o controladores de interrupciones rápidas , y los SLIH también se conocen como controladores de interrupciones lentas/suaves o llamadas a procedimientos diferidos en Windows.
Un FLIH implementa como mínimo un manejo de interrupciones específico de la plataforma similar a las rutinas de interrupción . En respuesta a una interrupción, hay un cambio de contexto y se carga y ejecuta el código para la interrupción. El trabajo de un FLIH es atender rápidamente la interrupción o registrar información crítica específica de la plataforma que solo está disponible en el momento de la interrupción y programar la ejecución de un SLIH para un manejo de interrupciones de larga duración. [2]
Los FLIH provocan fluctuaciones en la ejecución del proceso. Los FLIH también enmascaran las interrupciones. Reducir las fluctuaciones es muy importante para los sistemas operativos en tiempo real , ya que deben mantener una garantía de que la ejecución de un código específico se completará dentro de un período de tiempo acordado. Para reducir las fluctuaciones y reducir la posibilidad de perder datos debido a interrupciones enmascaradas, los programadores intentan minimizar el tiempo de ejecución de un FLIH, moviéndose lo más posible al SLIH. Con la velocidad de las computadoras modernas, los FLIH pueden implementar todo el manejo dependiente del dispositivo y la plataforma, y usar un SLIH para un manejo de larga duración independiente de la plataforma.
Los FLIH que dan servicio al hardware normalmente enmascaran su interrupción asociada (o la mantienen enmascarada, según sea el caso) hasta que completan su ejecución. Un FLIH (inusual) que desenmascara su interrupción asociada antes de que se complete se denomina manejador de interrupciones reentrantes . Los manejadores de interrupciones reentrantes pueden causar un desbordamiento de pila debido a múltiples preempciones por parte del mismo vector de interrupciones , por lo que generalmente se evitan. En un sistema de interrupciones prioritarias , el FLIH también enmascara (brevemente) otras interrupciones de igual o menor prioridad.
Un SLIH completa tareas de procesamiento de interrupciones largas de manera similar a un proceso. Los SLIH tienen un subproceso de kernel dedicado para cada controlador o son ejecutados por un grupo de subprocesos de trabajo de kernel. Estos subprocesos permanecen en una cola de ejecución en el sistema operativo hasta que haya tiempo de procesador disponible para que realicen el procesamiento de la interrupción. Los SLIH pueden tener un tiempo de ejecución de larga duración y, por lo tanto, generalmente se programan de manera similar a los subprocesos y procesos.
En Linux, las FLIH se denominan mitad superior y las SLIH mitad inferior o mitad inferior . [1] [2] Esto es diferente de la denominación utilizada en otros sistemas similares a Unix, donde ambas son parte de la mitad inferior . [ aclaración necesaria ]