Este capítulo explica como maneja las interrupciones el núcleo de Linux Debido a que el núcleo tiene mecanismos genéricos e interfaces para manejar las interrupciones, la mayor parte de los detalles del manejo de interrupciones son específicos para cada arquitectura.
Linux usa distintos componentes físicos para llevar a cabo trabajos muy diferentes. El dispositivo de vídeo maneja el monitor, el dispositivo IDE maneja los discos, y así sucesivamente. Puede manejar estos dispositivos de forma sincronizada, es decir, puede enviar una petición de alguna operación (como escribir un bloque de memoria a disco) y luego esperar que la operación se complete. Este método, aunque funciona, es muy poco eficiente y el sistema operativo pasaría mucho tiempo «ocupado sin hacer nada» mientras espera que cada operación finalice. Una manera mejor y más eficiente sería realizar la petición y luego realizar otra, un trabajo más útil que luego se interrumpirúa cuando el dispositivo haya finalizado la petición. Con este esquema puede haber muchas peticiones pendientes hacia los dispositivos en el sistema, todas al mismo tiempo.
Tiene que haber algún apoyo físico para los dispositivos de forma que se pueda interrumpir lo que esté haciendo la CPU. La mayoría de los procesadores de propósito general, si no todos, como el Alpha AXP usan un método similar. Algunas de las patillas físicas de la CPU están cableadas de forma que al cambiar el voltaje (por ejemplo cambiandolo de +5v a -5v) se provoca que la CPU pare lo que esté haciendo y empiece a ejecutar un código especial para manejar la interrupción; el código de manejo de interrupción. Una de estas patillas puede estar conectada a un reloj de tiempo real y recibir una interrupción cada milésima de segundo, otras pueden estar conectados a otros dispositivos del sistema, como el controlador SCSI.
Los sistemas usan a menudo un controlador de interrupciones para agrupar las interrupciones de los dispositivos antes de pasar la señal a una patilla de interrupción de la CPU. Esto ahorra patillas de interrupción en la CPU y tambien da flexibilidad a la hora de diseñar sistemas. El controlador de interrupciones tiene registros de máscara y de estado que controlan las interrupciones. Marcando los bits en el registro de máscara activa y desactiva las interrupciones y registro de estado devuelve las interrupciones actualmente activas en el sistema.
Algunas de las interrupciones del sistema pueden estar cableados físicamente, por ejemplo, el reloj de tiempo real puede estar permanentemente conectado a la tercera patilla del controlador de interrupciones. Sin embargo, el donde están conectadas algunas de las patillas puede estar determinado por dónde esté conectada la tarjeta, en una ranura PCI o ISA en particular. Por ejemplo, la cuarta patilla del controlador de interrupciones puede estar conectada a la ranura PCI número 0 que un dia puede tener una tarjeta ethernet, y al dia siguiente tener una controladora SCSI. En resumen, cada sistema tiene sus propios mecanismos de rutado de interrupciones y el sistema operativo debe de ser lo suficientemente flexible para adaptarse a él.
La mayoría de los microprocesadores modernos de propósito general manejan las interrupciones de la misma forma. Cuando se genera una interrupción de hardware la CPU para de ejecutar las instrucciones que estaba ejecutando y salta a la posición de memoria que contiene o el código de manejo de interrupciones o una instrucción bifurcando hacia el código de manejo de interrupciones- Este código suele operar de una forma especial para la CPU, en modo interrupción y , normalmente, no se puede producir ninguna interrupción en este modo. A pesar de todo hay excepciones; algunas CPUs establecen una jerarquía de prioridad de interrupciones y entonces pueden producirse interrupciones de alto nivel. Esto significa que el primer nivel del código de manejo de interrupciones debe de ser muy cuidadoso y a menudo tiene su propia pila, que se usa para guardar el estado de ejecución de la CPU (tanto los registros normales de la CPU como contexto) antes de que la interrupción tome el control. Algunas CPUs tienen un juego especial de registros que solo existen en modo interrupción, y el código de las interrupciones puede usar estos registros para realizar todo el salvado de contexto que necesite.
Cuando la interrupción ha acabado, el estado de la CPU se reestablece y la interrupción se da por concluida. La CPU entonces continua haciendo lo que estuviera haciendo antes de ser interrumpida. Es importante que el código de proceso de la interrupción sea lo más eficiente posible y que el sistema operativo no bloquee interrupciones demasiado a menudo o durante demasiado tiempo.
La figura 7.1 muestra que hay dos controladores de 8 bits encadenados juntos; cada uno tiene una máscara y un registro de estado, PIC1 y PIC2 (de Programmable Interrupt Controller). Los registros de máscara están en las direcciones 0x21 y 0xA1 y los registros de estado están en 0x20 y 0xA0 Escribiendo un uno en un bit particular del registro de máscara activa una interrupción, escribiendo un cero la desactiva. Entonces, escribiendo un uno al bit 3 activaría la interrupción 3, y escribiendo un cero la desactivaría. Desafortunadamente (e irritantemente), los registros de máscara de interrupción son de solo escritura, no se puede leer el valor que se escribió. Esto conlleva que Linux debe guardar una copia local de lo que ha modificado en el registro de máscara. Para ello modifica estas máscaras guardadas en las rutinas de activación y desactivación de interrupciones y escribe las máscaras completas en el registro cada vez.
Cuando se manda una señal a una interrupción, el código de manejo de interrupciones lee los dos registros de estado de interrupciones (ISRs, de Interrupt Status Registers). Al ISR en 0x20 se le trata como los ocho bits más bajos de un registro de interrupciones de dieciseis bits y al ISR en 0xA0 como los ocho bits más altos. Entonces, una interrupción en el bit 1 del ISR de 0xA0 se tratará como la interrupción de sistema 9. El bit 2 de PIC1 no está disponible ya que se usa para encadenar las interrupciones de PIC2, cualquier interrupción de PIC2 provoca que el bit 2 de PIC1 esté activado.
Algunas interrupciones son fijas por convenio de la arquitectura PC y entonces el controlador simplemente pide esa interrupción cuando se haya inicializado. Esto es lo que hace el controlador de la disquetera; siempre pide la IRQ 6 (IRQ = Interrupt ReQuest, petición de Interrupción). Puede haber ocasiones en las que un controlador no sepa qué interrupción va a usar el dispositivo. Esto no es un problema para los controladores de dispositivos PCI ya que siempre saben cual es su número de interrupción. Desafortunadamente no hay ninguna manera fácil de que los controladores de dispositivos ISA encuentren su número de interrupción. Linux resuelve este problema permitiendo a los controladores sondear en búsqueda de su interrupción.
Lo primero de todo el controlador hace algo al dispositivo que le provoque una interrupción. Luego todas las interrupciones del sistema sin asignar se habilitan, Esto significa que la interrupción de la que depende el dispositivo será repartida a través del controlador de interrupciones programable. Linux lee el registro de estado de las interrupciones y devuelve sus contenidos al controlador del dispositivo. Un resultado distinto a 0 significa que se han producido una o más interrupciones durante el sondeo. El controlador desactiva ahora el sondeo y las interrupciones sin asignar se desactivan. Si el controlador ISA ha lógrado encontrar su número de IRQ entonces puede pedir el control de forma normal.
Los sistemas basados en PCI son mucho más dinámicos que los basados en ISA. La patilla de interrupción que suele usar un dispositivo ISA es a menudo seleccionado usando jumpers en la tarjéta y fijado en el controlador del dispositivo. Por otra parte, los dispositivos PCI tienen sus interrupciones localizadas por la BIOS PCI o el subsitema PCI ya que PCI se configura cuando el sistema arranca. Cada dispositivo PCI puede usar uno de cuatro patillas de interrupción, A, B, C o D. Esto se fija cuando el dispositivo se construye y la mayoría de dispositivos toman por defecto la interrupción en la patilla A. Entonces, la patilla A de la ranura PCI 4 debe ser rutada hacia la patilla 6 del controlador de interrupciones, la patilla B de la ranura PCI 4 a la patilla 7 del controlador de interrupciones y así.
La manera de rutar las interrupciones PCI es completamente específica del sistema y debe haber algún código de inicialización que entienda esa topología PCI de rutado de interrupciones. En PCs basados en Intel es el código de la BIOS del sistema que corre durante el arranque, pero para sistemas sin BIOS (por ejemplo los sistemas basados en Alpha AXP ) el núcleo de Linux hace esta configuración. El código de configuración de PCI escribe el número de patilla del controlador de interrupciones en la cabecera de configuración de PCI para cada dispositivo. Esto determina el número de la patilla de interrupción ( o IRQ) usando su conocimiento de la topología PCI de rutado de interrupciones junto a los números de ranura de los dispositivos PCI y la patilla PCI de interrupción que esté usando. La patilla de interrupción que usa un dispositivo es fija y se guarda en un campo en la cabecera de configuración de PCI para este dispositivo. Entonces se escribe esta información en el campo de línea de interrupción que está reservado para este propósito. Cuando el controlador del dispositivo se carga, lee esta información y la usa para pedir el control de la interrupción al núcleo de Linux.
Puede haber muchas fuentes de interrupciones PCI en el sistema, por ejemplo cuando se usan puentes PCI-PCI. El número de fuentes de interrupciones puede exceder el número de patillas de los controladores de interrupciones programables del sistema. En tal caso, los dispositivos PCI pueden compartir interrupciones, una patilla del controlador de interrupciones recogiendo interrupciones de más de un dispositivo PCI. Linux soporta esto permitiendo declarar a la primera petición de una fuente de interrupciones si ésta puede ser compartida. Compartir interrupciones conlleva que se apunten varias estucturas de datos irqaction por unta entrada en el vector irq_action. Cuando se ejecuta una interrupción compartida, Linux llamará a todos los controladores de esa fuente. Cualquier controlador de dispositivos que pueda compartir interrupciones (que deberían ser todos los controladores de dispositivos PCI) debe estar preparado para que se llame al su controlador de interrupciones cuando no hay interrupción a la que atender.
Una de las tareas principales del subsistema de manejo de interrupciones de Linux es rutar las interrupciones a las pártes correspontdientes del código de manejo de interrupciones. Este código debe entender la topología de interrupciones del sistema. Si, por ejemplo, el controlador de la disquetera ejecuta la interrupción en la patilla 6 1 del controlador de interrupciones, se debe reconocer la el origen de la interrupción como la disquetera y entonces rutarlo hacia el código de manejo de interrupciones del controlador del dispositivo. Linux usa un juego de punteros a estructuras de datos que contienen las direcciones de las rutinas que manejan las interrupciones del sistema. Estas rutinas pertenecen a los controladores de los dispositivos del sistema y es responsabilidad de cada controlador el pedir la interrupcion que quiera cuando se inicializa el controlador. La figura 7.2 muestra que irq_action es un vector de punteros a la estructura de datos irqaction. Cada estructura de datos irqaction contiene información sobre el controlador de esa interrupción, incluyendo la dirección de la rutina de manejo de la interrupción. Como tanto el número de interrupciones como la manera de la que se manejan varía entre arquitecturas y, a veces, entre sistemas, el código de manejo de interrupciones de Linux es específico para cada arquitectura. Esto significa que el tamaño del vector irq_action varía dependiendo del número de fuentes de interrupciones que haya.
Cuando se produce la interrupción, Linux debe determinar primero su fuente leyendo el registro de estado de interrupciones de los controladores de interrupciones programables del sistema. Luego convierte esa fuente en un desplazamiento dentro del vector irq_action vector. Entonces, por ejemplo, una interrupción en la patilla 6 del controlador de interrupciones desde el controlador de la disquetera se traduciría ak séptimo puntero del vector de controladores de interrupciones. Si no hay un controlador de interrupciones para la interrupción que se produjo entonces el núcleo de Linux indicaría un error, en caso contrario, llamaría a las rutinas de manejo de interrupciones de todas las estructuras de datos irqaction para esta fuente de interrupciones.
Cuando el núcleo de Linux llama a la rutina de manejo de interrupciones del controlador de dispositivos esta debe averiguar eficientemente por qué fue interrumpida y responder. Para encontrar la causa de la interrupción el controlador tendrá que leer el registro de estado del dispositivo que causó la interrupción. El dispositivo puede estar comunicando un error o que una operacion pedida ha sido completada. Por ejemplo el controlador de la disquetera puede estar informando que ha completado la posición de la cabecera de lectura de la disquetera sobre el sector correcto del disquete. Una vez que la razón de la interrupción se ha determinado, el controlador puede necesitar trabajar más. Si es así, el núcleo de Linux tiene mecanismos que le permiten posponer ese trabajo para luego. Esto evita que la CPU pase mucho tiempo en modo interrupción. Véase el cap´itulo sobre los Controladores deDispositivos (Chapter dd-chapter) para más detalles.
NOTA DE REVISIÓN: Interrupciones rápidas y lentes, ¿son esto cosa de Intel?
1 Actualmente, el controlador de la disquetera es una de las interrupciones fijas en un sistema PC, ya que, por convenio, la controladora de la disquetera está siempre conectada a la interrupción 6.