Aquí hay una vista de alto nivel del procesamiento de bajo nivel. Estoy describiendo una arquitectura típica simple, las arquitecturas reales pueden ser más complejas o diferir en formas que no importan en este nivel de detalle.
Cuando ocurre una interrupción, el procesador busca si las interrupciones están enmascaradas. Si lo son, no pasa nada hasta que se desenmascaran. Cuando las interrupciones se desenmascaran, si hay interrupciones pendientes, el procesador selecciona una.
Luego, el procesador ejecuta la interrupción bifurcándose a una dirección particular en la memoria. El código en esa dirección se llama controlador de interrupción. Cuando el procesador se bifurca allí, enmascara las interrupciones (por lo que el controlador de interrupciones tiene el control exclusivo) y guarda el contenido de algunos registros en algún lugar (normalmente, otros registros).
El manejador de interrupciones hace lo que debe hacer, generalmente comunicándose con el periférico que activó la interrupción para enviar o recibir datos. Si el temporizador generó la interrupción, el controlador podría activar el programador del sistema operativo para cambiar a un subproceso diferente. Cuando el controlador termina de ejecutarse, ejecuta una instrucción especial de retorno de interrupción que restaura los registros guardados y desenmascara las interrupciones.
El controlador de interrupciones debe ejecutarse rápidamente, porque impide que se ejecute cualquier otra interrupción. En el kernel de Linux, el procesamiento de interrupciones se divide en dos partes:
- La "mitad superior" es el controlador de interrupciones. Hace lo mínimo necesario, por lo general se comunica con el hardware y establece un indicador en algún lugar de la memoria del kernel.
- La "mitad inferior" realiza cualquier otro procesamiento necesario, por ejemplo, copiar datos en la memoria del proceso, actualizar las estructuras de datos del núcleo, etc. Puede tomar su tiempo e incluso bloquear la espera de alguna otra parte del sistema, ya que se ejecuta con interrupciones. habilitado.
Como es habitual en este tema, para obtener más información, lea Controladores de dispositivos Linux; el capítulo 10 trata sobre las interrupciones.
Gilles ya describió el caso general de una interrupción, lo siguiente se aplica específicamente a Linux 2.6 en una arquitectura Intel (parte de esto también se basa en las especificaciones de Intel).
Una interrupción es un evento que cambia la secuencia de instrucciones ejecutadas por el procesador.
Hay dos tipos diferentes de interrupciones:
- Interrupción síncrona (Excepción) producido por la CPU mientras procesa las instrucciones
- Interrupción asíncrona (Interrupción) emitido por otros dispositivos de hardware
Las excepciones son causadas por errores de programación (por ejemplo, Error de división , Error de página , Desbordamiento ) que debe ser manejado por el kernel. Envía una señal al programa e intenta recuperarse del error.
Se clasifican las siguientes dos excepciones:
- Excepción detectada por el procesador generado por la CPU al detectar una condición anómala; dividido en tres grupos:Fallas generalmente se puede corregir, Trampas informar de una ejecución, Cancelar son errores graves.
- Excepción programada solicitado por el programador, manejado como una trampa.
Las interrupciones pueden ser emitidas por dispositivos de E/S (teclado, adaptador de red, ..), temporizadores de intervalo y (en sistemas multiprocesador) otras CPU. Cuando ocurre una interrupción, la CPU debe detener su instrucción actual y ejecutar la interrupción recién llegada. Necesita guardar el antiguo estado del proceso interrumpido para (probablemente) reanudarlo después de que se maneje la interrupción.
El manejo de interrupciones es una tarea delicada:
- Las interrupciones pueden ocurrir en cualquier momento, el núcleo intenta eliminarlas lo antes posible
- Una interrupción puede ser interrumpida por otra interrupción
- Hay regiones en el núcleo que no deben interrumpirse en absoluto
Se definen dos niveles de interrupción diferentes:
- Interrupciones enmascarables emitido por dispositivos de E/S; puede estar en dos estados, enmascarado o desenmascarado. Solo se procesan las interrupciones no enmascaradas.
- Interrupciones no enmascarables; mal funcionamiento crítico (p. ej., falla de hardware); siempre procesado por la CPU.
Cada dispositivo de hardware tiene su propia línea de solicitud de interrupción (IRQ). Las IRQ se numeran a partir de 0. Todas las líneas de IRQ están conectadas a un controlador de interrupción programable (PIC). El PIC escucha las IRQ y las asigna a la CPU. También es posible deshabilitar una línea IRQ específica.
Los sistemas Linux de multiprocesamiento modernos generalmente incluyen el Advanced PIC (APIC) más nuevo, que distribuye las solicitudes de IRQ por igual entre las CPU.
El paso intermedio entre una interrupción o excepción y su manejo es la Tabla de descriptores de interrupción (IDT). Esta tabla asocia cada interrupción o vector de excepción (un número) con un controlador específico (por ejemplo, Error de división es manejado por la función divide_error()
).
A través de la IDT, el núcleo sabe exactamente cómo manejar la interrupción o excepción que se produjo.
Entonces, ¿qué hace el kernel cuando ocurre una interrupción?
- La CPU comprueba después de cada instrucción si hay una IRQ del (A)PIC
- Si es así, consulta el IDT para mapear el vector recibido a una función
- Comprueba si la interrupción fue emitida por una fuente autorizada
- Guarda los registros del proceso interrumpido
- Llama a la función correspondiente para manejar la interrupción
- Cargue los registros guardados recientemente del proceso interrumpido e intente reanudarlo
En primer lugar, los participantes involucrados en el manejo de interrupciones son los dispositivos periféricos de hardware, el controlador de interrupciones, la CPU, el kernel del sistema operativo y los controladores. Los dispositivos de hardware periféricos son responsables de la generación de interrupciones. Afirman líneas de solicitud de interrupción cuando quieren la atención del kernel del sistema operativo. Estas señales son multiplexadas por el controlador de interrupción, que es responsable de la recopilación de señales de interrupción. También es responsable de la determinación del orden en que se pasarán las señales de interrupción a la CPU. El controlador de interrupciones puede deshabilitar temporalmente una línea de solicitud de interrupción particular (IRQL) y volver a habilitarla nuevamente (enmascaramiento de IRQL). El controlador de interrupciones pasa las solicitudes de interrupción recopiladas a la CPU de forma secuencial. La CPU después de completar la ejecución de cada instrucción, la CPU verifica si hay solicitudes de interrupción en espera del controlador de interrupciones. Si la CPU encuentra que hay una solicitud en espera Y el indicador Habilitar interrupción está establecido en el registro de control interno de la CPU, entonces la CPU inicia el manejo de interrupciones. Como puede ver, mediante la manipulación del indicador de interrupción en la CPU y la comunicación con el controlador de interrupción, el kernel de Linux puede controlar la aceptación de la interrupción. Por ejemplo, Linux puede deshabilitar la aceptación de interrupciones desde un dispositivo en particular o deshabilitar la aceptación de interrupciones.
¿Qué sucede cuando el procesador recibe una solicitud de interrupción? En primer lugar, la CPU desactiva automáticamente las interrupciones al restablecer el indicador de interrupción. Se volverán a habilitar una vez que finalice el manejo de interrupciones. Al mismo tiempo, la CPU realiza una cantidad mínima de trabajo necesario para cambiar la CPU del modo de usuario al modo kernel de tal manera que le permita reanudar la ejecución del código interrumpido. La CPU consulta con estructuras de control de CPU especiales rellenadas por el kernel de Linux para encontrar una dirección de código a la que se le pasará el control. Esta dirección es la dirección de la primera instrucción del controlador de interrupciones, que forma parte del kernel de Linux.
Como primer paso del manejo de interrupciones, el kernel identifica el vector de interrupción recibida para identificar qué tipo de evento ha ocurrido en el sistema. El vector de interrupción define qué acciones tomará Linux para manejarlo. Como segundo paso, Linux guarda el resto de los registros de la CPU (que la CPU no guardó automáticamente) y que potencialmente puede usar el programa interrumpido. Esta es una acción muy importante, porque permite que Linux maneje las interrupciones de forma transparente con respecto al programa interrumpido. Como tercer paso, Linux logra cambiar al modo kernel configurando el entorno del kernel y configurando el estado de la CPU requerido para ello. Y finalmente, vector se llama al controlador de interrupción dependiente. (Puede consultar la macro BUILD_INTERRUPT3 en arch\x86\kernel\entry_32.S para obtener los detalles adicionales del ejemplo relacionado con la arquitectura x86) En el caso de los dispositivos periféricos, esta es una rutina do_IRQ(). (Mira en el arch\x86\kernel\irq.c)
El manejador de interrupciones dependiente del vector usualmente envuelto por llamadas a irq_enter() e irq_exit(). El área de código encerrada dentro de un par de estas funciones es atómica con respecto a cualquier otra área y también es atómica con respecto a los pares de cli/sti. Irq_enter() e irq_exit() también capturan algunas estadísticas relacionadas con el manejo de la interrupción. Finalmente, el núcleo busca en la tabla vector_irq para encontrar el número irq asignado al vector de la interrupción recibida y llama a handle_irq() (desde arch\x86\kernel \irq_32.c).
En este punto finaliza la parte común del manejo de interrupciones en Linux, porque el núcleo busca la rutina del controlador de interrupciones dependiente del dispositivo instalada por el controlador del dispositivo como parte del descriptor irq y la invoca. Si el controlador no instaló dicho controlador, el núcleo simplemente reconoce la interrupción en el controlador de interrupciones y sale del controlador de interrupciones general.
Después del final del manejo de interrupciones, el kernel restaura el estado del programa que fue interrumpido previamente y reanuda la ejecución de este programa.