Notas de estudio "Restauración de la verdad del sistema operativo": Capítulo 7 Interrupción

  • Debido a que la CPU sabe que algo sucedió en la computadora, la CPU suspende el programa que se está ejecutando y vuelve a ejecutar el programa que maneja el evento. Cuando se ejecuta este programa, la CPU continúa ejecutando el programa en este momento. Todo el proceso se denomina manejo de interrupciones, también conocido como interrupción.

Las interrupciones se clasifican según el origen del evento, las interrupciones externas a la CPU se denominan interrupciones externas y las interrupciones internas de la CPU se denominan interrupciones internas.

  • Las interrupciones externas se clasifican según provoquen tiempo de inactividad y se pueden dividir en interrupciones enmascarables e interrupciones no enmascarables.
  • Las interrupciones internas se dividen según si la interrupción es normal y se pueden dividir en interrupciones suaves y excepciones.

interrupción externa

Las interrupciones externas se pueden dividir en: interrupciones enmascarables e interrupciones no enmascarables

  • Las interrupciones externas se refieren a interrupciones externas a la CPU, y la fuente de interrupción externa debe ser un determinado hardware, por lo que las interrupciones externas también se denominan interrupciones de hardware. La CPU proporciona dos líneas de señal. Las interrupciones de hardware externas se notifican a la CPU a través de dos líneas de señal. Estas dos líneas de señal son INTR (INTeRrupt) y NMI (Interrupción no enmascarable).
    inserte la descripción de la imagen aquí
  1. Interrupciones enmascarables : las interrupciones enmascarables ingresan a la CPU a través del pin INTR, y las interrupciones de dispositivos externos como discos duros y tarjetas de red son todas interrupciones enmascarables. Enmascarable significa que la CPU puede ignorar las interrupciones emitidas por este dispositivo externo, porque no provocará que el sistema falle, por lo que las interrupciones de todos estos dispositivos externos se pueden enmascarar a través del bit IF del registro eflags. Además, todos estos dispositivos están conectados a un determinado dispositivo proxy de interrupción, y la interrupción de un determinado dispositivo también se puede proteger por separado a través del proxy de interrupción. Para este tipo de interrupción enmascarable, la CPU puede optar por ignorarla, o incluso después de ignorarla, puede dividir la interrupción en la mitad superior y la mitad inferior para un procesamiento separado como Linux.
  2. Interrupción no enmascarable : La interrupción no enmascarable ingresa a la CPU a través del pin NMI, lo que significa que ha ocurrido un error fatal en el sistema, lo que equivale a anunciar que el funcionamiento de la computadora ha terminado. El bit IF en el registro eflags no tiene ningún efecto sobre él

interrupción interna

Las interrupciones internas se pueden dividir en: interrupciones suaves y excepciones.

  1. Interrupción suave : una interrupción suave es una interrupción iniciada por el software. Dado que la interrupción se inicia activamente durante la operación del software, es un error interno subjetivo, no objetivo.

Las siguientes instrucciones pueden iniciar una interrupción:

  • "int inmediato de 8 bits". Esta es una instrucción de uso común en el futuro. Necesitamos usarla para realizar llamadas al sistema. El valor inmediato de 8 bits puede representar 256 tipos de interrupciones, lo que es consistente con la cantidad de interrupciones admitidas por el procesador.

  • "int3". Este no es un espacio int 3, no hay espacio entre ellos. int3 es una instrucción de punto de interrupción de depuración, el número de vector de interrupción activado por ella es 3 y el código de máquina de int3 es 0xcc.

  • en. Esta es una instrucción de desbordamiento de interrupción y el número de vector de interrupción que activa es 4. Sin embargo, si se puede activar la interrupción número 4 depende de si el bit OF en el registro de bandera eflags es 1. Si es 1, se activará una interrupción; de lo contrario, la instrucción silenciosamente no hará nada y será muy discreta.

  • atado. Esta es la instrucción para verificar que el índice de la matriz esté fuera de los límites; puede activar la interrupción número 5 para verificar si el subíndice del índice de la matriz está dentro de los límites superior e inferior. El formato del comando es bound 16/32 位寄存器, 16/32 位内存. El operando de destino se almacena en un registro y su contenido es el valor del subíndice de la matriz a detectar. . El operando fuente es la memoria cuyo contenido son los límites superior e inferior del subíndice de la matriz. Cuando se ejecuta la instrucción vinculada, si el subíndice está fuera del rango del índice de la matriz, se activará la interrupción número 5.

  • ud2. Instrucción indefinida, que desencadena la interrupción número 6. Esta instrucción indica que la instrucción no es válida y la CPU no puede reconocerla. Úselo activamente para iniciar una interrupción, que a menudo se usa en pruebas de software y no tiene ningún uso práctico.

Excepto por el primer tipo de "valor inmediato int de 8 bits", los otros tipos de instrucciones de interrupción suave anteriores pueden denominarse excepciones.


  1. Anormal : La excepción es causada por un error generado dentro de la CPU durante la ejecución de la instrucción. Debido a que es un error de tiempo de ejecución, no se ve afectado por el bit IF en el registro de bandera eflags y no se puede ocultar al usuario (porque el la operación no puede continuar y no se puede detectar el error).
  • No todas las anomalías son mortales, según su grado de gravedad se pueden dividir en los siguientes tres tipos. :
    (1) Falla, también conocida como falla . Este tipo de error es un tipo que se puede reparar, pertenece al tipo de anomalía más leve y le da al software la oportunidad de "reformarse". Cuando ocurre una excepción de este tipo, la CPU restaura el estado de la máquina al estado anterior a la excepción y luego llama al controlador de interrupciones, la CPU devuelve la dirección para seguir apuntando a la instrucción que causó la excepción de falla. Por lo general, este problema se soluciona en el controlador de interrupciones y se puede volver a intentar después de que regresa el controlador de interrupciones. El ejemplo más típico es el fallo de página mencionado en el curso sobre sistema operativo. Se dice que la memoria virtual de Linux se basa en el fallo de página, lo que demuestra claramente que este tipo de excepción es extremadamente fácil de reparar e incluso beneficiosa.
    (2) Trampa, también conocida como trampa . El nombre muestra vívidamente que el software ha caído en una trampa colocada por la CPU, lo que provoca que se detenga. Esta excepción se usa generalmente en la depuración. Por ejemplo, la instrucción int3 causa este tipo de excepción. Para permitir que el controlador de interrupciones continúe ejecutándose después de regresar, la CPU apunta la dirección de retorno del controlador de interrupciones a la dirección del siguiente instrucción que causó la excepción.
    (3) Abortar, también conocido como terminación . Por el nombre, este es el tipo de excepción más grave. Una vez que ocurre, el programa no continuará ejecutándose porque el error no se puede reparar. Para protegerse, el sistema operativo Sólo se puede desactivar este programa del proceso eliminado de la tabla. Los errores que causan esta excepción suelen ser errores de hardware o errores en algunas estructuras de datos del sistema.
número vectorial mnemotécnico ilustrar tipo numero erroneo fuente
0 #DE además de errores Falla ninguno Directiva DIV o IDIV
1 #DB depuración Fallo/Trampa ninguno Cualquier código o referencia de datos, o instrucción INT 1
2 - - interrupción NMI interrumpir ninguno interrupción externa no enmascarable
3 #BP punto de interrupción trampa ninguno Instrucción INT 3
4 #DE Desbordamiento trampa ninguno EN instrucción
5 #BR Se superó el rango límite Falla ninguno comando ENLACE
6 #UD código de operación no válido (código de operación indefinido) Falla ninguno Instrucción UD2 o código de operación reservado (nueva instrucción agregada en Pentium Pro)
7 #NUEVO MÉJICO El dispositivo no existe (no hay coprocesador matemático) Falla ninguno Instrucciones de punto flotante o WAIT/FWAIT
8 #DF doble error terminación anormal Sí (0) Cualquier instrucción que genere una excepción, NMI o INTR
9 - - Anulación de segmento de coprocesador (reservado) Falla ninguno Instrucciones de punto flotante (las CPU posteriores a 386 no generan esta excepción)
10 #TS TSS de segmento de estado de tarea no válido Falla tener Intercambio de tareas o visita TSS
11 #NOTARIO PÚBLICO segmento no existe Falla tener Cargar registros de segmento o acceder a segmentos del sistema
12 #SS error de pila Falla tener Operaciones de pila y carga de registros SS.
13 #GP error de protección general Falla tener Cualquier referencia a la memoria y otras comprobaciones de protección.
14 #PF protección de página Falla tener cualquier referencia de memoria
15 (Reservado por Intel, no utilizar) ninguno
dieciséis #MF Error de punto flotante x87FPU (error matemático) Falla ninguno Instrucciones de punto flotante x87FPU o WAIT/FWAIT
17 #C.A verificación de alineación Falla Sí (0) Una referencia a cualquier dato en la memoria.
18 #MC inspección de la máquina terminación anormal ninguno El código de error (si lo hay) y la fuente están relacionados con el tipo de CPU (introducido por el procesador Pentium)
19 #XM Excepción de punto flotante SIMD Falla ninguno Instrucciones de punto flotante SSE y SSE2 (introducidas por los procesadores PIII)
20~31 - - (Reservado por Intel, no utilizar)
32~255 - - Interrupciones definidas por el usuario (no reservadas) interrumpir Interrupción externa o instrucción INT n



Tabla de descriptores de interrupción

  • La tabla de descriptores de interrupciones (tabla de descriptores de interrupciones, IDT) es una tabla que se utiliza para almacenar la entrada del controlador de interrupciones en modo protegido. Cuando la CPU recibe una interrupción, necesita usar el vector de interrupción para recuperar el descriptor correspondiente en esta tabla. En esta descripción, busque la dirección de inicio del controlador de interrupciones en el símbolo y luego ejecute la rutina de interrupción.
  • No solo hay descriptores de interrupción en la tabla de descriptores de interrupción, sino también descriptores de puerta de tarea y descriptores de puerta de trampa. Dado que todos los descriptores en la tabla registran la dirección inicial de un programa, que es equivalente a la "puerta" de un programa, los descriptores en la tabla de descriptores de interrupción tienen su propio nombre ------- puerta

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

puerta de tareas

  • La puerta de tareas y el segmento de estado de la tarea (TSS) es el mecanismo de conmutación de tareas proporcionado por el procesador Intel a nivel de hardware, por lo que la puerta de tareas debe usarse junto con TSS y el selector de TSS se registra en la tarea. puerta. El cambio no se utiliza. Las puertas de tareas pueden existir en la tabla de descriptores globales GDT, la tabla de descriptores locales LDT y la tabla de descriptores de interrupciones IDT. El valor de tipo de la puerta de tarea en el descriptor es binario 0101. La mayoría de los sistemas operativos (incluido Linux) no utilizan TSS para cambiar de tareas.

puerta de interrupción

  • La puerta de interrupción incluye el selector de segmento del segmento donde se encuentra el controlador de interrupciones y la dirección de desplazamiento dentro del segmento. Después de ingresar la interrupción de esta manera, el bit IF en el registro de bandera eflags se establece automáticamente en 0, es decir, después de ingresar la interrupción, la interrupción se desactiva automáticamente para evitar el anidamiento de interrupciones. Linux es una llamada al sistema implementada mediante el uso la puerta de interrupción, que es el famoso int 0x80. Las puertas de interrupción sólo se permiten en el IDT. El valor de tipo de la puerta de interrupción en el descriptor es binario 1110.

trampilla

  • La puerta trampa es muy similar a la puerta de interrupción, la diferencia es que después de que la puerta trampa ingresa a la interrupción, el bit IF en el registro de bandera eflags no se establecerá automáticamente en 0. Las trampillas solo están permitidas en los IDT. El valor de tipo de la trampilla en el descriptor es binario 1111.

puerta de llamada

  • Una puerta de llamada es una forma para que un proceso de usuario ingrese al nivel de privilegio 0. Su DPL es 3. La dirección de la rutina se registra en la puerta de llamada, que no puede ser llamada mediante la instrucción int, sino solo mediante las instrucciones call y jmp. Las puertas de llamada se pueden instalar en GDT y LDT. El valor de tipo de la puerta de llamada en el descriptor es binario 1100.



inserte la descripción de la imagen aquí

Hay un registro de tabla de descriptores de interrupciones (IDTR) dentro de la CPU, que se divide en dos partes: los bits 0 a 15 son los límites de la tabla, el tamaño de IDT menos 1 y los bits 16 a 47 son la dirección base de IDT. . El límite de la tabla de 16 bits significa que el rango máximo es 0xffff, que es 64 KB. La cantidad de descriptores que se pueden acomodar es 64 KB/8 = 8 K = 8192. Se debe prestar especial atención a que el descriptor de segmento 0 en GDT no está disponible, pero IDT no tiene tal limitación, el descriptor de puerta 0 también está disponible y la interrupción con el vector de interrupción número 0 es un error de división. Pero el procesador solo admite 256 interrupciones, es decir, 0-254, y el resto de descriptores de interrupción no están disponibles. Hay un bit P en el descriptor de puerta, por lo que cuando construyamos el IDT en el futuro, recuerde establecer el bit P en 0, lo que significa que el controlador de interrupciones en el descriptor de puerta no está en la memoria.

Al igual que cargar GDTR, cargar IDTR también tiene una instrucción especial: lidt, y su uso es:lidt 48 位内存数据




Manejo y protección de interrupciones

El proceso de interrupción completo se divide en dos partes fuera y dentro de la CPU:

  • Fuera de la CPU: el chip proxy de interrupción recibe la interrupción del dispositivo externo y el número del vector de interrupción de la interrupción se envía a la CPU después del procesamiento.
  • Dentro de la CPU: la CPU ejecuta el controlador de interrupciones correspondiente al número del vector de interrupción.

Procesos dentro de la CPU:

  1. El procesador localiza el descriptor de la puerta de interrupción según el número del vector de interrupción.
    El número de vector de interrupción es el índice del descriptor de interrupción. Cuando el procesador recibe un número de vector de interrupción externo, utiliza este número de vector para consultar el descriptor de interrupción correspondiente en la tabla de descriptores de interrupción y luego ejecuta el descriptor de interrupción en el descriptor de interrupción. Manejador de interrupciones. Dado que el descriptor de interrupción tiene 8 bytes, el procesador multiplica el número del vector de interrupción por 8 y luego lo agrega a la dirección de la tabla de descriptores de interrupción en IDTR. La suma de las direcciones solicitadas es la descripción de interrupción correspondiente al número del vector de interrupción.
  2. El procesador verifica el nivel de privilegio.
    Dado que la interrupción se notifica al procesador a través del número del vector de interrupción, el número del vector de interrupción es solo un número entero y no hay RPL, por lo que el RPL no participa en la verificación del nivel de privilegio del transferencia de nivel de privilegio causada por la interrupción. La verificación de privilegios de la puerta de interrupción es similar a la puerta de llamada: para la interrupción suave iniciada por el software, el nivel de privilegio actual CPL debe estar entre el descriptor de puerta DPL y el segmento de código de destino DPL en la puerta. Esto es para evitar que los programas de usuario en el nivel de privilegio 3 llamen activamente a ciertas rutinas que solo sirven al kernel.
    (a) Si las interrupciones son causadas por interrupciones suaves int n, int3 y into, estas son interrupciones iniciadas activamente en el proceso del usuario. Debido al control del código de usuario, el procesador necesita verificar el nivel de privilegio actual CPL y el descriptor de puerta DPL Este es el control El límite inferior del privilegio de entrar por la puerta. Si la autoridad de CPL es mayor o igual que el DPL, se pasa la verificación del "umbral" del nivel de privilegio y se pasa al siguiente paso del "marco de la puerta". se ingresa la verificación. De lo contrario, el procesador lanza una excepción. (b) Este paso verifica el límite superior del nivel de privilegio (marco de la puerta)
    : El procesador necesita verificar el nivel de privilegio actual CPL y el segmento de código de destino DPL correspondiente al selector. registrado en el descriptor de puerta. Si la autoridad de CPL es menor que el segmento de código de destino DPL, la verificación pasa; de lo contrario, si la CPL es mayor o igual que el segmento de código de destino DPL, el procesador lanzará una excepción, es decir , excepto para regresar desde un nivel de privilegio alto con una instrucción de devolución, una transferencia de privilegio solo puede ocurrir de bajo a alto.
    Si la interrupción es causada por un dispositivo externo y una excepción, solo se verifican directamente la CPL y la DPL del segmento de código de destino, que es lo mismo que el paso b) anterior, y se requiere que la autoridad de CPL sea menor que la DPL. del segmento de código de destino; de lo contrario, el procesador genera una excepción.
  3. Ejecute el controlador de interrupciones.
    Después de pasar la verificación de privilegios, cargue el selector de segmento de código de destino del descriptor de puerta en el registro de segmento de código CS, cargue la dirección de desplazamiento del controlador de interrupciones en el descriptor de puerta en EIP y comience a ejecutar el controlador de interrupciones.
    inserte la descripción de la imagen aquí
  • Después de que ocurre la interrupción, el bit NT y el bit TF en eflags se establecerán en 0. Si el descriptor de puerta correspondiente a la interrupción es una puerta de interrupción, el bit IF del registro de bandera eflags se establecerá automáticamente en 0 para evitar el anidamiento de interrupciones, es decir, durante el procesamiento de la interrupción volvió a aparecer una nueva interrupción, que es para evitar que vuelva a ocurrir la misma interrupción durante el proceso de procesamiento de una interrupción. Esto provoca una excepción de protección general (GP). Esto significa que, de forma predeterminada, el procesador ejecuta la rutina de manejo de interrupciones en el descriptor de puerta de interrupción en modo no perturbado.

  • Si la interrupción correspondiente es una puerta de tarea o una puerta trampa, la CPU no pondrá el bit IF a 0. Se permite el anidamiento de interrupciones porque las puertas trampa se utilizan principalmente para la depuración, lo que permite que la CPU responda a interrupciones de nivel superior. Para la puerta de tareas, se trata de ejecutar una nueva tarea, y la tarea debe realizarse bajo la condición de abrir e interrumpir; de lo contrario, los recursos de la CPU se monopolizarán y el sistema operativo degenerará de multitarea a monotarea.

  • La instrucción que regresa de la interrupción es iret, que extrae datos de la pila para registrar cs, eip, eflags, etc., y juzga si se debe restaurar la pila anterior según si el nivel de privilegio ha cambiado, es decir, si se debe restaurar la pila anterior. restaure el valor en la posición de SS_old y ESP_old en la pila Pop para registrar ss y esp. Cuando el controlador de interrupciones termine de ejecutarse y regrese, restaure el contenido de eflags de la pila mediante la instrucción iret.

Empujar la pila cuando se produce una interrupción

Después de que ocurre la interrupción, dado que CS carga un nuevo selector de segmento de código de destino, al procesador no le importa si el nuevo selector es el mismo que el selector actual en cualquier registro de segmento, o si los dos selectores apuntan al mismo segmento, siempre que el segmento Se carga el registro, el registro del búfer del descriptor de segmento se actualizará y el procesador pensará que se ha cambiado un segmento, que pertenece a una transferencia entre segmentos, es decir, una transferencia lejana. Por lo tanto, después de que el proceso actual es interrumpido por una interrupción, para continuar ejecutando el proceso después de regresar de la interrupción, el procesador guarda automáticamente los valores actuales de CS y EIP en la pila utilizada por el controlador de interrupciones. Los procesadores usan diferentes pilas en diferentes niveles de privilegios. En cuanto a qué pila usa el controlador de interrupciones, depende del nivel de privilegios en el que se encuentre en ese momento, porque las interrupciones pueden ocurrir en cualquier nivel de privilegios. Además de guardar CS y EIP, también es necesario guardar el registro de bandera EFLAGS. Si se trata de una conversión de nivel de privilegios, también se deben enviar los registros SS y ESP.

Echemos un vistazo a la situación de apilamiento y el orden de los registros anteriores, y no discutiremos el contenido de la verificación de privilegios aquí:

  1. Después de que el procesador encuentra el descriptor de interrupción correspondiente de acuerdo con el número del vector de interrupción, compara el CPL con el DPL del segmento de código de destino correspondiente al selector en el descriptor de la puerta de interrupción. Si la autoridad de CPL es menor que el DPL, significa que necesita transferirse a un nivel de privilegio más alto. Es necesario cambiar a una pila con privilegios altos. Esto significa que después de ejecutar el controlador de interrupciones, si desea volver correctamente al proceso actualmente interrumpido, también debe restaurar la pila a la pila anterior en este momento. Entonces, el procesador guarda temporalmente los valores de la pila anterior SS y ESP, indicados como SS_old y ESP_old, y luego encuentra en TSS la misma pila que el segmento de código de destino nivel DPL y la carga en los registros SS y ESP, indicados como SS_new y ESP_new, y luego los SS_old y ESP_old previamente guardados temporalmente se insertan en la nueva copia de seguridad de la pila para recargarlos en el registro de segmento de pila SS y el puntero de pila ESP al regresar. Dado que SS_old son datos de 16 bits, el operando de la pila en modo de 32 bits es de 32 bits, por lo que SS_old se extiende en 16 bits con 0 para convertirse en datos de 32 bits y luego se inserta en la pila. En este momento, el contenido de la nueva pila se muestra en la Figura A.

  2. Inserte el registro EFLAGS en la nueva pila y el contenido de la nueva pila se muestra en la Figura B.

  3. Dado que es necesario cambiar al segmento de código de destino, para esta transferencia entre segmentos, es necesario guardar CS y EIP en la pila actual para realizar una copia de seguridad, que se registran como CS_old y EIP_old, para que se pueda restaurar el proceso interrumpido. después de que se completa la ejecución del programa de interrupción. De manera similar, CS_old son datos de 16 bits, sus 16 bits superiores deben completarse con 0, expandirse a datos de 32 bits y luego insertarse en la pila. En este momento, el contenido de la nueva pila se muestra en la Figura C.

  4. Algunas excepciones tendrán un código de error. Este código de error se utiliza para informar en qué segmento ocurrió la excepción, es decir, la ubicación donde ocurrió la excepción. Por lo tanto, el código de error contiene información como el selector, y el código de error será empujado a la pila inmediatamente después de EIP, registrado como ERROR_CODE. En este momento, el contenido de la nueva pila se muestra en la Figura D.
    inserte la descripción de la imagen aquí

  • Si en el paso 1 se determina que no hay transferencia de nivel de privilegios involucrada, no buscará una nueva pila en el TSS, pero continuará usando la pila anterior actual, por lo que es imposible restaurar la pila anterior. Al mismo tiempo, los datos en la pila no están disponibles cuando ocurre una interrupción. Incluye SS_old y ESP_old. Por ejemplo, cuando se produce la interrupción, el programa del núcleo se está ejecutando actualmente, que es del nivel de privilegio 0 al nivel de privilegio 0, y no hay ningún cambio de nivel de privilegio.

inserte la descripción de la imagen aquí

  • Después de que el procesador ingresa la interrupción y ejecuta el controlador de interrupciones, debe regresar al proceso interrumpido, que es el proceso inverso al de ingresar la interrupción. El regreso de una interrupción se logra con la instrucción iret. Iret, es decir, interrupción ret, esta instrucción está dedicada a regresar del controlador de interrupciones. Suponiendo que en modo de 32 bits, extrae datos de 32 bits de la parte superior de la pila actual para registrar EIP, CS y EFLAGS respectivamente. La instrucción iret no conoce la exactitud de los datos en la pila. Solo es responsable de extraer los datos de la parte superior de la pila, 4 bytes a la vez, en los registros relevantes. Por lo tanto, antes de usar iret, debe asegurarse que los datos de la parte superior de la pila Los datos son correctos y el orden de la parte superior de la pila es EIP, CS, EFLAGS y ESP, SS según cambie el nivel de privilegio.
  • Dado que el registro de segmento CS es de 16 bits, los 16 bits superiores de los datos de 32 bits devueltos por la pila se descartan y sólo los 16 bits inferiores se cargan en CS. Si el procesador descubre que el nivel de privilegio cambiará después de regresar, continuará devolviendo dos datos de doble palabra a ESP y SS, donde SS también es un registro de 16 bits, por lo que después de mostrar datos de 32 bits, solo los inferiores Se cargarán 16 bits en SS. La instrucción iret significa regresar de una interrupción, por lo que es la última instrucción en el controlador de interrupciones.
  • Instrucciones similares incluyen iretw e iretd, iretw se usa en modo de 16 bits e iretd se usa en modo de 32 bits. iret es la abreviatura de iretw e iretd. Ya sea que esté codificado en modo de 16 bits o en modo de 32 bits, solo se puede usar la instrucción iret. Si se compila en iretw o iretd depende de la longitud de la palabra especificada por el pseudo- BITS de instrucciones.

Hablemos del proceso de retorno del controlador de interrupciones:
(1) Cuando el procesador ejecuta la instrucción iret, sabe que para realizar un retorno lejano, primero necesita devolver el selector de segmento de código CS_old y el puntero de instrucción del proceso interrumpido. de la pila EIP_old. En este momento, es necesario realizar una verificación del nivel de privilegios. Primero verifique el selector CS CS_old en la pila y juzgue si cambiar el nivel de privilegio durante el proceso de devolución de acuerdo con su bit RPL, es decir, la futura CPL.
(2) El selector CS en la pila es CS_old. Verifique el nivel de privilegio de acuerdo con el DPL del segmento de código correspondiente a CS_old y el RPL en CS_old. Si se pasa la verificación, los registros CS y EIP deben actualizarse inmediatamente. Dado que CS_old ha expandido los 16 bits superiores a 0 cuando se insertó en la pila, ahora son datos de 32 bits y el registro de segmento CS es de 16 bits, por lo que el procesador descarta los 16 bits superiores de CS_old y carga los 16 inferiores. bits a CS y carga EIP_old en el registro EIP, después de lo cual el puntero de la pila apunta a EFLAGS. Si no hay transferencia de nivel de privilegio involucrada al ingresar a la interrupción, el puntero de la pila es ESP_old en este momento (lo que indica que después de ingresar la interrupción anterior, se continúa usando la pila anterior). De lo contrario, el puntero de la pila es ESP_new (lo que indica que la nueva pila registrada en TSS se usa después de ingresar la interrupción anterior).
(3) Coloque los EFLAGS guardados en la pila en el registro de bandera EFLAGS. Si es necesario cambiar el nivel de privilegio después de juzgar en el paso 1, el puntero de la pila es ESP_new, que apunta a ESP_old en la pila. De lo contrario, al ingresar una interrupción, se transfiere horizontalmente y se usa la pila anterior. En este momento, el puntero de la pila es ESP_old y no se insertan datos en la pila debido a esta interrupción, y el puntero de la pila apunta al parte superior de la pila antes de que ocurriera la interrupción.
(4) Si se considera en el paso 1 que es necesario cambiar el nivel de privilegio al regresar, es decir, es necesario restaurar la pila anterior, entonces es necesario cargar ESP_old y SS_old en los registros ESP y SS respectivamente. Y descarta los valores originales en los registros SS y ESP.SS_new y ESP_new realizan comprobaciones de nivel de privilegios al mismo tiempo. Además, dado que el procesador completó SS_old con 0 cuando se insertó en la pila, ahora son datos de 32 bits, por lo que antes de recargar en el registro de segmento de pila SS, los 16 bits superiores de SS_old deben eliminarse y descartado, y solo su SS de baja carga de 16 bits.

código de error de interrupción

Algunas interrupciones enviarán un código de error a la pila, que es un poco como "últimas palabras, proporcionan pistas", que se utiliza para indicar en qué segmento ocurrió la interrupción. Por tanto, la parte más importante del código de error es el selector, pero este selector puede recuperar descriptores en varias tablas. El código de error consta de varias partes, el formato se muestra en la figura.
inserte la descripción de la imagen aquí

  • EXT significa evento externo, es decir, un evento externo, que se utiliza para indicar si la fuente de interrupción proviene del exterior del procesador. Si la fuente de interrupción es una interrupción no enmascarable NMI o un dispositivo externo, EXT es 1, de lo contrario es 0.

Controlador de interrupción programable 8259A

  • 8259A se utiliza para gestionar y controlar interrupciones enmascarables. Se muestra protegiendo interrupciones periféricas, implementando juicios de prioridad sobre ellas y proporcionando funciones como números de vectores de interrupción a la CPU. La razón por la que se llama programable es que las funciones anteriores se pueden configurar mediante programación.

  • Los procesadores Intel admiten un total de 256 interrupciones, pero el 8259A solo puede administrar 8 interrupciones, por lo que para admitir más dispositivos de interrupción, se proporciona otra solución que combina varios 8259A (el término oficial es cascada). Con la combinación de cascada, cada 8259A se denomina 1 chip. Si se utiliza el método en cascada, es decir, se conectan varios chips 8259A en serie y se pueden conectar en cascada un máximo de 9, es decir, se pueden admitir un máximo de 64 interrupciones. N segmentos de 8259A pueden admitir 7n + 1 fuentes de interrupción a través de la cascada. Cuando se conecta en cascada, solo un segmento de 8259A es el segmento maestro y el resto son segmentos esclavos. La interrupción del chip esclavo solo se puede pasar al chip maestro y luego el chip maestro la pasa a la CPU, es decir, solo el chip maestro enviará la señal de interrupción INT a la CPU.

  • Cada dispositivo externo que se ejecuta de forma independiente es una fuente de interrupción. Las interrupciones que envían solo pueden ser conocidas por el maestro de la CPU cuando están conectados a la línea de señal de solicitud de interrupción (IRQ: InterruptReQuest). Esto significa que cuando enciende la computadora, habrá be Consulte IRQ1...IRQn, estos son los números de interrupción asignados a dispositivos externos.

inserte la descripción de la imagen aquí

Diagrama esquemático de la estructura interna del 8259A:
inserte la descripción de la imagen aquí

  • INT: después de que el 8259A selecciona la solicitud de interrupción con la mayor prioridad, envía una señal a la CPU.
  • INTA: INT Reconocimiento, señal de respuesta de interrupción. INTA ubicado en 8259A recibe la señal de respuesta a interrupción desde la interfaz INTA de la CPU.
  • IMR: Registro de máscara de interrupción, registro de máscara de interrupción, el ancho es de 8 bits, utilizado para enmascarar la interrupción de un determinado periférico.
  • IRR: registro de solicitud de interrupción, registro de solicitud de interrupción, el ancho es de 8 bits. Su función es aceptar y bloquear la señal de interrupción filtrada por el registro IMR, que está lleno de interrupciones en espera de ser procesadas, lo que es "equivalente" a la cola de señales de interrupción sin procesar mantenida por 5259A.
  • PR: Priority Resolver, árbitro de prioridad. Cuando se producen varias interrupciones al mismo tiempo, o cuando llega una nueva solicitud de interrupción, compárela con la interrupción que se está procesando actualmente para encontrar la interrupción con mayor prioridad.
  • ISR: registro en servicio, registro de servicio de interrupción, el ancho es de 8 bits. Cuando se atiende una interrupción, se guarda en este registro.

Flujo de trabajo del 8259A: cuando un dispositivo periférico envía una señal de interrupción, la señal de interrupción finalmente se envía al 8259A porque la placa base ya ha apuntado la ruta de la señal a una interfaz IRQ del chip 8259A.

  1. 8259A primero verifica si la señal de interrupción de la interfaz IRQ ha sido enmascarada en el registro IMR. El bit en el registro IMR es 1, que significa enmascaramiento de interrupción y 0 significa liberación de interrupción. Si el bit correspondiente a la IRQ se ha puesto a 1, significa que la interrupción de la interfaz IRQ ha sido enmascarada. Luego descarte la señal de interrupción; de lo contrario, envíela al registro TIR,
  2. Después de enviar al registro IRR, configure el BIT correspondiente en el registro IRR donde se encuentra la interfaz IRQ. El papel del registro TIR es "equivalente" a una cola de interrupciones pendientes. En el momento apropiado, el árbitro de prioridad PR seleccionará una interrupción con la prioridad más alta del registro IRR. El juicio de prioridad aquí es muy simple, es decir, cuanto menor sea el número de interfaz IRQ, mayor será la prioridad, por lo que la prioridad IRQ0 es máxima.
  3. Después de eso, el 8259A enviará la señal INTR a la CPU a través de la interfaz INT en el circuito de control. Después de que la señal se envía a la interfaz INTR de la CPU, la CPU sabe que se acerca la interrupción de la carta y que hay otro trabajo que hacer, por lo que después de que la CPU ejecuta la instrucción en cuestión, se comunica inmediatamente a la interfaz INTA. del 8259A a través de su propia interfaz INTA. Responda una señal de respuesta de interrupción, lo que indica que ya estoy listo para la CPU y que puede continuar trabajando en el 8259A.
  4. Después de recibir esta señal, el 8259A establece inmediatamente el BIT correspondiente a la interrupción con la prioridad más alta seleccionada en ese momento en el registro ISR en 1. Este registro indica la interrupción que se está procesando actualmente y, al mismo tiempo, la interrupción debe eliminarse de la "cola de interrupción pendiente" se elimina de la TIR, es decir, el BIT correspondiente a 1 de la interrupción se establece en 0 cuando la TIR gana el premio.
  5. Después de eso, la CPU enviará la señal INTA al 8259A nuevamente, esta vez para obtener el número del vector de interrupción correspondiente a la interrupción. Dado que el número de vector de interrupción inicial de 8259A no es 0 en la mayoría de los casos (el número de vector de interrupción inicial se modifica, el motivo se mencionará más adelante), el número de vector de interrupción inicial + el número de interfaz IRQ es el número de vector de interrupción del dispositivo. Se puede ver que aunque el dispositivo externo enviará una señal de interrupción, no sabe que hay un número de vector de interrupción y no sabe que el agente de interrupción le asignará dicho número entero (como 8259A).
  6. Posteriormente, 8259A envía este número de vector de interrupción a la CPU a través del bus de datos del sistema. Después de que la CPU obtiene el número del vector de interrupción del bus de datos, lo utiliza como índice en la tabla de vectores de interrupción o en la tabla de descriptores de interrupción para encontrar el controlador de interrupciones correspondiente y ejecutarlo.

Si la "Notificación EOI (fin de interrupción)" del 8259A está configurada en modo no automático (modo manual), debe haber un código para enviar EOI al 8259A al final del controlador de interrupciones. Después de que el 8259A reciba el EOI, lo hará El BIT correspondiente en el registro ISR se establece en 0 para la interrupción. Si la "notificación EOI" está configurada en modo automático, después de que el 8259A reciba la segunda señal INTA en este momento, es decir, el INTA en el que la CPU le solicita al 8259A el número del vector de interrupción, el 8259A establecerá automáticamente el BIT correspondiente de este interrupción en el ISR a 0 .




Programando el 8259A

La programación del 8259A consiste en inicializarlo, configurar el modo en cascada del segmento principal y del segmento esclavo, especificar el número del vector de interrupción inicial y configurar varias tareas.

El número del vector de interrupción es algo lógico, es físicamente el número de interfaz IRQ en 8259A. El orden de los números IRQ en el 8259A es fijo, pero los números de vectores de interrupción correspondientes no lo son. Esto es en realidad una asignación del hardware al software. Al configurar el 8259A, la interfaz IRQ se puede asignar a diferentes números de vectores de interrupción.

Hay dos conjuntos de registros dentro del 8259A, un conjunto es el conjunto de registros de comando de inicialización, que se utiliza para guardar las palabras de comando de inicialización (InitializationCommand Words, ICW), y hay 4 ICW en total, ICW1~ICW4. El otro grupo de registros es el grupo de registros de comando de operación, que se utiliza para guardar la palabra de comando de operación (Palabra de comando de operación, OCW). Hay 3 OCW en total, OCW1 ~ OCW3. Por lo tanto, nuestra programación del 8259A también se divide en dos partes: inicialización y operación.

  • Parte de esto se inicializa con ICW, que se utiliza para determinar si se requiere conexión en cascada, establecer el número del vector de interrupción inicial y establecer el modo de finalización de la interrupción. Su programación es enviar una serie de ICW al puerto de 8259A. Dado que el estado de funcionamiento del 8259A debe determinarse desde el principio, es necesario escribir muchas configuraciones a la vez. Algunas configuraciones están relacionadas y son dependientes. Tal vez una configuración posterior dependerá de la configuración escrita por un ICW anterior. Por lo tanto, esta parte requiere una secuencia estricta, y ICW1, ICW2, ICW3 e ICW4 deben escribirse en secuencia.
  • La otra parte es utilizar OCW para operar y controlar el 8259A. La máscara de interrupción y el final de interrupción antes mencionados se implementan enviando OCW al puerto 8259A. El orden en el que se envían los OCW no es fijo y no importa cuál de los tres se envía primero.
    inserte la descripción de la imagen aquí
    ICW1 se utiliza para inicializar el modo de conexión de 8259A y el modo de activación de la señal de interrupción. El modo de conexión se refiere a trabajar con un solo chip o trabajo en cascada de varios chips, y el modo de activación se refiere a si la señal de solicitud de interrupción se activa por nivel o por flanco.
  • Tenga en cuenta que ICW1 debe escribirse en el puerto 0x20 del chip maestro y en el puerto 0xA0 del chip esclavo.
  • IC4 indica si se debe escribir ICW4, lo que significa que no es necesario utilizar todas las palabras de control de inicialización de ICW. Cuando IC4 es 1, significa que ICW4 debe escribirse más tarde, y si es 0, no es así. Tenga en cuenta que IC4 debe ser 1 para sistemas x86.
  • SNGL significa único, si SNGL es 1, significa chip único, si SNGL es 0, significa cascada. Permítanme hablar de ello aquí, si en el modo en cascada, esto implica la cuestión de qué interfaz IRQ se utiliza para que el maestro (1) y el esclavo (múltiple) se conecten entre sí, de modo que cuando SNGL es 0, el maestro y el esclavo son también se requiere ICW3.
  • ADI significa intervalo de dirección de llamada, que se utiliza para configurar el intervalo de llamada de 8085, y no es necesario configurar x86.
  • LTIM significa modo activado por nivel/borde, que se utiliza para configurar el modo de detección de interrupción, LTIM es 0 significa activado por flanco, LTIM es 1 significa activado por nivel.
  • 1 en el cuarto bit está fijo, que es la bandera de ICW1.
  • Los bits 5.º a 7.º están dedicados al procesador 8085, no son necesarios para x86, simplemente se configuran en 0 directamente.

inserte la descripción de la imagen aquí
ICW2 se utiliza para establecer el número del vector de interrupción inicial, que es la asignación de la interfaz IRQ del hardware al número del vector de interrupción lógica mencionado anteriormente. Dado que las interfaces IRQ en cada chip 8259A están organizadas secuencialmente, nuestra configuración aquí es especificar el número de vector de interrupción al que se asigna IRQ0, y los números de vector de interrupción correspondientes de otras interfaces IRQ se organizarán automáticamente en secuencia.

  • Tenga en cuenta que ICW2 debe escribirse en el puerto 0x21 del chip maestro y en el puerto 0xA1 del chip esclavo.
  • Debido a que solo necesitamos configurar el número del vector de interrupción de IRQ0, el número del vector de interrupción de IRQ1~IRQ7 es la extensión de IRQ0, por lo que solo somos responsables de completar los 5 bits superiores de T3~T7 y los 3 bits inferiores de ID0. ~ID2 no es nuestra responsabilidad. Debido a que solo completamos los 5 bits superiores, cualquier número es múltiplo de 8 y este número representa el número del vector de interrupción inicial establecido. Esto está diseñado intencionalmente. Los 3 bits inferiores pueden representar 8 números de vectores de interrupción, que son importados automáticamente por 8259A de acuerdo con la disposición de las 8 interfaces IRQ. El valor de IRQ0 es 000, el valor de IRQ1 es 001 y el valor de IRQ2 es 010... …y así sucesivamente, de modo que los 5 bits superiores más los 3 bits inferiores representan el número del vector de interrupción realmente asignado por cualquier interfaz IRQ.

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí
ICW3 solo se requiere en el modo en cascada (si SNGL en ICW1 es 0) y se usa para configurar qué interfaz IRQ se usa para la interconexión entre el maestro y el esclavo.
Debido a los diferentes métodos de conexión en cascada del segmento principal y del segmento esclavo, para este ICW3, el segmento maestro y el segmento esclavo tienen sus propias estructuras diferentes.

  • ICW3 necesita escribir en el puerto 0x21 del chip maestro y en el puerto 0xA1 del chip esclavo.
  • Para el chip maestro, la interfaz IRQ correspondiente al bit establecido en 1 en ICW3 se usa para conectarse al chip esclavo, y si es 0, significa conectarse a un dispositivo externo. Por ejemplo, si el chip maestro IRQ2 e IRQ5 están conectados al chip esclavo, el ICW3 del chip maestro es 00100100
  • Para el chip esclavo, es necesario configurar el modo de conexión con el chip maestro 8259A, "no es necesario" especificar qué interfaz IRQ usar para conectarse con el chip maestro, la interfaz en el chip esclavo se usa especialmente para conectar en cascada el chip maestro no es la IRQ. La forma de configurar el chip esclavo para conectarse al chip maestro es especificar la interfaz IRQ que utiliza el chip maestro para conectarse a sí mismo en el chip esclavo.
  • Al responder a una interrupción, el chip maestro enviará el número de interfaz IRQ que está en cascada con el chip esclavo, y todos los chips esclavos lo comparan con los 3 bits inferiores de su propio ICW3, y si son consistentes, se consideran enviados a ellos mismos. Por ejemplo, si el maestro usa la interfaz IRQ2 para conectar el esclavo A y usa la interfaz IRQ5 para conectar el esclavo B, el valor de ICW3 del esclavo A debe establecerse en 00000010 y el valor de ICW3 del esclavo B debe establecerse en 00000101. Por lo tanto, los 3 bits inferiores de ID0 ~ ID2 en el chip esclavo ICW3 son suficientes y los 5 bits superiores no son necesarios, solo 0.

inserte la descripción de la imagen aquí

  • Los bits 7.º a 5.º no están definidos, simplemente se establecen en 0 directamente.
  • SFNM significa modo especial completamente anidado. Si SFNM es 0, significa modo completamente anidado. Si SFNM es 1, significa modo especial completamente anidado.
  • BUF indica si el chip 8259A está funcionando en modo búfer. Si BUF es 0, funciona en modo sin búfer; si BUF es 1, funciona en modo búfer.
  • Cuando se conectan en cascada varios 8259A, si funcionan en el modo de búfer, se utiliza M/S para especificar si el 8259A es el chip principal o el chip esclavo. Si M/S es 1, significa que es el chip maestro, si M/S es 0, significa que es el chip esclavo. Si funciona en modo sin búfer (BUF es 0), M/S no es válido.
  • AEOI significa Fin automático de interrupción, 8259A solo puede continuar procesando la siguiente interrupción cuando recibe la señal de fin de interrupción. Este elemento se usa para configurar si se permite que 8259A finalice automáticamente la interrupción. Si AEOI es 0, significa que no es automático, es decir, la interrupción finaliza manualmente. Podemos enviar señales EOI manualmente a los chips maestro y esclavo del 8259A en el controlador de interrupciones o en la función principal. Este tipo de comando de tipo "operación" se lleva a cabo a través del OCW que se presentará a continuación. Si AEOI es 1, significa fin automático de la interrupción.
  • μPM representa el tipo de microprocesador (microprocesador), este elemento es por compatibilidad con procesadores antiguos. Si μPM es 0, significa procesador 8080 o 8085, si μPM es 1, significa procesador x86.



inserte la descripción de la imagen aquí
OCW1 se utiliza para proteger la señal de interrupción del dispositivo externo conectado al 8259A; de hecho, OCW1 está escrito en el registro IMR. El blindaje aquí significa si se debe reenviar la señal de interrupción desde el dispositivo externo a la CPU. Dado que todas las interrupciones de dispositivos externos son interrupciones enmascarables, en última instancia están sujetas al control del bit IF en el registro de bandera eflags. Si IF es 0, todas las interrupciones enmascarables están enmascaradas. Es decir, cuando IF es 0, incluso si el 8259A envía el número del vector de interrupción del dispositivo externo, la CPU lo ignora.

  • Tenga en cuenta que OCW1 debe escribirse en el puerto 0x21 del maestro o en el puerto 0xA1 del esclavo.
  • M0~M7 corresponden a IRQ0~IRQ7 de 8259A, si un determinado bit es 1, la señal de interrupción en el IRQ correspondiente se enmascara. De lo contrario, si un determinado bit es 0, se liberará la señal de interrupción IRQ correspondiente.

inserte la descripción de la imagen aquí

  • OCW2 se utiliza para configurar el modo de finalización de interrupción y el modo de prioridad.
  • Tenga en cuenta que OCW2 debe escribirse en el puerto 0x20 del chip maestro y en el puerto 0xA0 del chip esclavo.
  • La configuración de OCW2 es relativamente complicada y se deben combinar varios bits de atributos para combinar varios modos de trabajo de 8259A. Los 3 bits altos R, SL y EOI pueden definir múltiples métodos de finalización de interrupción y métodos de ciclo de prioridad.
  • R, Rotación, indica si se debe establecer la prioridad de interrupción de forma circular. Si R es 1, significa ciclo automático de prioridad, si R es 0, significa que no hay ciclo automático y se adopta el modo de prioridad fija.
  • SL, Nivel específico, indica si se debe especificar un nivel de prioridad. El nivel se especifica con los 3 bits inferiores. El SL aquí es solo para activar el interruptor de los 3 bits inferiores, por lo que SL también indica si los 3 bits inferiores de L2 ~ L0 son válidos. SL es 1 significa válido, SL es 0 significa no válido.
  • EOI, Fin de interrupción, es el bit de comando de fin de interrupción. Si EOI se establece en 1, el bit correspondiente en el registro ISR se borrará a 0, es decir, la interrupción procesada actualmente se borrará, lo que indica el final del procesamiento. Enviar activamente EOI a 8259A es el método para finalizar manualmente la interrupción, por lo que existe un requisito previo para usar este comando, es decir, el bit AEOI en ICW4 es 0 y solo se usa cuando la interrupción no finaliza automáticamente.
  • 00 en el cuarto al tercer dígito es la identificación de OCW2.
  • L2~L0 se utilizan para determinar la codificación de la prioridad. Hay dos tipos aquí. Uno se utiliza para EOI, que indica el nivel de prioridad interrumpido, y el otro se utiliza para el ciclo de prioridad, que especifica el nivel de prioridad inicial más bajo.
    inserte la descripción de la imagen aquí



inserte la descripción de la imagen aquí

  • OCW3 se utiliza para configurar el modo de protección especial y el modo de consulta
  • Tenga en cuenta que OCW3 debe escribirse en el puerto 0x20 del chip maestro o en el puerto 0xA0 del chip esclavo.
  • El bit 7 no se utiliza.
  • El ESMM (Habilitar modo de máscara especial) de 6 bits y el SMM (Modo de máscara especial) de 5 bits se utilizan juntos para habilitar o deshabilitar el modo de máscara especial. ESMM es el bit de habilitación del modo de máscara especial, que es un interruptor. SMM es el bit de modo de máscara especial. El modo de enmascaramiento especial sólo es válido cuando el modo de enmascaramiento especial está habilitado. Es decir, si ESMM es 0, SMM no es válido. Si ESMM es 1 y SMM es 0, significa que no funciona en modo de blindaje especial. Si tanto ESMM como SMM son 1, funcionará oficialmente en modo de protección especial.
  • 01 en los dígitos 4.º a 3.º es la identificación de OCW3, y 8259A juzga qué palabra de control es a través de estos dos dígitos.
  • P, comando de encuesta, comando de consulta, cuando P es 1, configure 8259A para interrumpir el modo de consulta, de modo que al leer el registro,
  • RR, Leer registro, leer comando de registro. Se utiliza junto con el bit RIS. Los registros sólo se pueden leer cuando RR es 1.
  • RIS, selección de registro de interrupción de lectura, bit de selección de registro de interrupción de lectura, como su nombre lo indica, es utilizar este bit para seleccionar el registro que se va a leer. Es un poco similar al índice en el registro de la tarjeta gráfica. Si RIS es 1, significa seleccionar el registro ISR, si RIS es 0, significa seleccionar el registro IRR. Estos dos registros se pueden leer si el valor de RR es 1.

Primero se debe completar la inicialización del 8259A, los pasos son:

  • Independientemente de si 8259A está en cascada o no, ICW1 e ICW2 deben estar disponibles y escritos en secuencia.
  • Solo cuando el bit SNGL en ICW1 es 0, significa conexión en cascada, y la conexión en cascada necesita configurar el segmento maestro y el segmento esclavo, por lo que es necesario escribir ICW3 en el segmento maestro y el segmento esclavo respectivamente. Tenga en cuenta que el formato de ICW3 es diferente en maestro y esclavo.
  • Solo es necesario escribir en ICW4 cuando IC4 en ICW1 es 1. Sin embargo, IC4 debe ser 1 para sistemas x86.
  • En el sistema x86, para inicializar el 8259A en cascada, se requieren los cuatro ICW, para inicializar el 8259A de un solo chip, no se requiere ICW3 y se requieren todos los demás.

Sólo después de la inicialización anterior del 8259A se puede operar con OCW.



//interrupt.c
#include "interrupt.h"
#include "print.h"
#include "io.h"


/*中断门描述符结构体*/
struct gate_desc {
    
    
   unsigned short func_offset_low_word;
   unsigned short selector;
   unsigned char  dcount;   //此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑
   unsigned char  attribute;
   unsigned short func_offset_high_word;
};




static struct gate_desc idt[33];  // idt是中断描述符表,本质上就是个中断门描述符数组
extern int intr_entry_table[33];





static void idt_desc_init(void) 
{
    
    
   int i;
   for (i = 0; i < 33; i++) 
   {
    
    
		idt[i].func_offset_low_word = ((unsigned int)intr_entry_table[i]) & 0x0000FFFF;
		idt[i].selector=0b1000;		//代码段选择子
		idt[i].dcount = 0;			//未使用
		idt[i].attribute=0b10001110;
		idt[i].func_offset_high_word = ((unsigned int)intr_entry_table[i] & 0xFFFF0000) >>16;
   }

   put_str("   idt_desc_init done\n");
} 

/* 初始化可编程中断控制器8259A */
static void pic_init(void) {
    
    

   /* 初始化主片 */
   outb (0x20, 0x11);   // ICW1: 边沿触发,级联8259, 需要ICW4.
   outb (0x21, 0x20);   // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.
   outb (0x21, 0x04);   // ICW3: IR2接从片. 对于主片,ICW3 中置 1 的那一位对应的 IRQ 接口用于连接从片
   outb (0x21, 0x01);   // ICW4: 8086模式, 正常EOI

   /* 初始化从片 */
   outb (0xa0, 0x11);	// ICW1: 边沿触发,级联8259, 需要ICW4.
   outb (0xa1, 0x28);	// ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.
   outb (0xa1, 0x02);	// ICW3: 设置从片连接到主片的IR2引脚
   outb (0xa1, 0x01);	// ICW4: 8086模式, 正常EOI

   /* 打开主片上IR0,也就是目前只接受时钟产生的中断 */
   outb (0x21, 0xfe);	//OCW1主片
   outb (0xa1, 0xff);	//OCW1 从片

   put_str("   pic_init done\n");
}



void idt_init() 
{
    
    
	put_str("idt_init start\n");
	idt_desc_init();	   // 初始化中断描述符表
	
	pic_init();		   // 初始化8259A
	 
	 
	//第0~15位是表界限,即IDT减1,可容纳8192个中段描述符;第16~47位时IDT的基地址。 
	/* 加载idt */ 
   unsigned long long int idt_operand = ((sizeof(idt) - 1) | (( unsigned long long int)(unsigned int)idt << 16));
   asm volatile("lidt %0" : : "m" (idt_operand));
   put_str("idt_init done\n");
}


//kernel.c
#include "print.h"
#include "io.h"


void intr_entry_0() 
{
    
    
    put_str("intr_entry_0\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_1() 
{
    
    
    put_str("intr_entry_1\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_2() 
{
    
    
    put_str("intr_entry_2\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_3() 
{
    
    
    put_str("intr_entry_3\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_4() 
{
    
    
    put_str("intr_entry_4\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_5() 
{
    
    
    put_str("intr_entry_5\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_6() 
{
    
    
    put_str("intr_entry_6\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_7() 
{
    
    
    put_str("intr_entry_7\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}

void intr_entry_8() 
{
    
    
    put_str("intr_entry_8\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}

void intr_entry_9() 
{
    
    
    put_str("intr_entry_9\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_a() 
{
    
    
    put_str("intr_entry_A\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_b() 
{
    
    
    put_str("intr_entry_b\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_c() 
{
    
    
    put_str("intr_entry_c\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_d() 
{
    
    
    put_str("intr_entry_d\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_e() 
{
    
    
    put_str("intr_entry_e\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_f() 
{
    
    
    put_str("intr_entry_f\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_10() 
{
    
    
    put_str("intr_entry_10\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_11() 
{
    
    
    put_str("intr_entry_11\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_12() 
{
    
    
    put_str("intr_entry_12\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_13() 
{
    
    
    put_str("intr_entry_13\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_14() 
{
    
    
    put_str("intr_entry_14\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_15() 
{
    
    
    put_str("intr_entry_15\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_16() 
{
    
    
    put_str("intr_entry_16\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_17() 
{
    
    
    put_str("intr_entry_17\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_18() 
{
    
    
    put_str("intr_entry_18\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_19() 
{
    
    
    put_str("intr_entry_19\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_1a() 
{
    
    
    put_str("intr_entry_1a\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}



void intr_entry_1b() 
{
    
    
    put_str("intr_entry_1b\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_1c() 
{
    
    
    put_str("intr_entry_1c\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_1d() 
{
    
    
    put_str("intr_entry_1d\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_1e() 
{
    
    
    put_str("intr_entry_1e\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("addl $4, %esp;iret");
}


void intr_entry_1f() 
{
    
    
    put_str("intr_entry_1f\n");

    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


void intr_entry_20() 
{
    
    
	static int i=0;
    put_str("intr_entry_20\n");
	put_int(i);
	 put_str("\n");
	i++;
    // 向主片和从片发送中断结束命令(EOI)
    outb(0xA0, 0x20); // 向从片发送EOI
	outb(0x20, 0x20); // 向主片发送EOI
	
	asm volatile ("leave");
	asm volatile ("iret");
}


int intr_entry_table[33]={
    
    
(int)intr_entry_0,
(int)intr_entry_1,
(int)intr_entry_2,
(int)intr_entry_3,
(int)intr_entry_4,
(int)intr_entry_5,
(int)intr_entry_6,
(int)intr_entry_7,
(int)intr_entry_8,
(int)intr_entry_9,
(int)intr_entry_a,
(int)intr_entry_b,
(int)intr_entry_c,
(int)intr_entry_d,
(int)intr_entry_e,
(int)intr_entry_f,
(int)intr_entry_10,
(int)intr_entry_11,
(int)intr_entry_12,
(int)intr_entry_13,
(int)intr_entry_14,
(int)intr_entry_15,
(int)intr_entry_16,
(int)intr_entry_17,
(int)intr_entry_18,
(int)intr_entry_19,
(int)intr_entry_1a,
(int)intr_entry_1b,
(int)intr_entry_1c,
(int)intr_entry_1d,
(int)intr_entry_1e,
(int)intr_entry_1f,
(int)intr_entry_20};

¿Por qué agregar "dejar"?
inserte la descripción de la imagen aquí

objdump -d kernel.oVer el código de desensamblado con el comando muestra que el compilador agrega códigos para guardar el puntero base anterior, crear un puntero base nuevo y asignar espacio para variables locales al comienzo de la función . Por lo tanto, debe usar leaveel comando equivalente mov esp, ebp pop ebppara equilibrar la pila.

Supongo que te gusta

Origin blog.csdn.net/qq_17111397/article/details/132085675
Recomendado
Clasificación