INTERRUPCIONES ENSAMBLADOR

27
11.1. Interrupciones Las interrupciones son un mecanismo por el que un dispositivo externo puede provocar que el procesador interrumpa momentáneamente la ejecución del programa para atender su petición y luego continuar con el programa desde el punto en que lo había dejado. Pero el procesador 8086 amplía este concepto y permite que un programa pueda generar una interrupción en cualquier momento. A este tipo de interrupciones se las conoce como interrupciones sofware para diferenciarlas de las interrupciones hardware, generadas por dispositivos externos al procesador. Existen, no obstante, de 8 a 15 interrupciones que no se llaman de la misma forma que las interrupciones de software, sino mediante el controlador de interrupciones, como interrupciones hardware. 11.1.1. Polling versus Interrupciones Ambos son protocolos de comunicación entre dispositivos de E/S y el microprocesador. En capítulos posteriores veremos una técnica más llamada DMA. 11.1.1.1. Polling También llamada técnica de las consultas sucesivas. Es la más simple de las dos. Para ella los dispositivos se conectan al bus de direcciones, al de datos y al de control. El procesador se encarga de ir consultando a los dispositivos conectados a él si necesitan atención o no. En caso negativo se pasa a consultar al siguiente. En cuanto uno conteste que sí, se empezará la transmisión. Esto requiere de un programa llamado bucle de consulta que compruebe un bit del dispositivo de su interfaz. Con este método se sabe exactamente cuándo se pregunta a un dispositivo, cuándo se comunica con él y cuánto se tarda en darle servicio. Es una operación sincronizada con el programa.

Transcript of INTERRUPCIONES ENSAMBLADOR

Page 1: INTERRUPCIONES ENSAMBLADOR

11.1. Interrupciones

Las interrupciones son un mecanismo por el que un dispositivo externo

puede provocar que el procesador interrumpa momentáneamente la

ejecución del programa para atender su petición y luego continuar con el

programa desde el punto en que lo había dejado. Pero el procesador 8086

amplía este concepto y permite que un programa pueda generar una

interrupción en cualquier momento. A este tipo de interrupciones se las

conoce como interrupciones sofware para diferenciarlas de las

interrupciones hardware, generadas por dispositivos externos al procesador.

Existen, no obstante, de 8 a 15 interrupciones que no se llaman de la misma

forma que las interrupciones de software, sino mediante el controlador de

interrupciones, como interrupciones hardware.

11.1.1. Polling versus Interrupciones

Ambos son protocolos de comunicación entre dispositivos de E/S y el

microprocesador. En capítulos posteriores veremos una técnica más

llamada DMA.

11.1.1.1. Polling

También llamada técnica de las consultas sucesivas. Es la más simple de las

dos. Para ella los dispositivos se conectan al bus de direcciones, al de datos

y al de control. El procesador se encarga de ir consultando a los dispositivos

conectados a él si necesitan atención o no. En caso negativo se pasa a

consultar al siguiente. En cuanto uno conteste que sí, se empezará la

transmisión. Esto requiere de un programa llamado bucle de consulta que

compruebe un bit del dispositivo de su interfaz. Con este método se sabe

exactamente cuándo se pregunta a un dispositivo, cuándo se comunica con

él y cuánto se tarda en darle servicio. Es una operación sincronizada con el

programa. Se consigue así establecer fácilmente unas prioridades,

consultando antes al dispositivo de mayor prioridad. Por otro lado, este

sistema representa una carga para el programa y responde con excesiva

lentitud a la llamada del dispositivo, pues hasta que el programa no

consulte no se establece la comunicación.

11.1.1.2. Interrupciones

Su filosofía es contraria a la anterior, consiste en que son los dispositivos de

E/S los que solicitan atención al procesador, quien al recibirla, puede

Page 2: INTERRUPCIONES ENSAMBLADOR

aceptarla o no. Puede ser que el proceso que se esté efectuando en ese

momento requiera la prohibición de esa interrupción, se habla entonces de

enmascaramiento de interrupciones. Si no están enmascaradas las

interrupciones se suspende el programa que se estaba ejecutando y el

control se transfiere a una rutina de atención al dispositivo en cuestión, que

una vez terminado, vuelve a ejecutar el proceso interrumpido por donde se

suspendió. Para realizar este proceso, la unidad de entrada y salida (su

inferfaz) tiene que enviar lo que se llama un vector (la clave que le

identifica), normalmente por el bus de datos.

11.1.1.3. Conclusión

El método polling es más sencillo que el interruptivo, pero más lento.

Imaginemos que estamos escribiendo en el ordenador a razón de una

pulsación por segundo. El microprocesador debe interrumpir su ejecución

cada segundo para recoger esta información. Sin embargo, es posible que a

veces escribamos más rápido y otras veces más lento, por lo que el

procesador debería mirar más a menudo si ha habido pulsación de tecla. En

muchas ocasiones volverá de vacío. Por tanto, vemos que este sistema

desperdicia mucho tiempo. Por el contrario, en el sistema interruptivo el

microprocesador no deja de hacer sus tareas y, cuando le llega una petición

de atención, deja momentáneamente su proceso para atender esta petición

(siempre que el proceso que estuviese haciendo no fuese delicado).

11.1.2. Interrupciones internas o excepciones

Las genera la propia CPU cuando se produce una situación anormal o

cuando llega su momento. Intel definió del 0 al 20h para uso interno de la

CPU, desgraciadamente IBM se saltó esta especificación y redefinió del 0 al

1Fh para su propio uso, por lo que existen definiciones duplicadas en las

tablas.

INT 0: Error de división, generada

automáticamente cuando el

cociente no cabe en el registro o el

divisor es cero. Sólo puede ser

generada mediante DIV o IDIV. El

8088/8086 guardan en la pila la

sentencia siguiente a la que causó

la excepción, mientras que el 286

y superiores guardan la sentencia

que la generó.

Page 3: INTERRUPCIONES ENSAMBLADOR

INT 1: Paso a paso. Se produce

tras cada instrucción cuando el

procesador está en modo traza

(utilizado para la depuración de

programas).

INT 2: Interrupción no

enmascarable. Tiene prioridad

absoluta y se produce incluso

aunque estén inhibidas las

interrupciones para indicar un

hecho muy urgente.

INT 3: Utilizada para poner puntos

de ruptura en la depuración de

programas.

INT 4: Desbordamiento. Se

dispara cuando se ejecuta un INTO

y había desbordamiento. Si no hay

desbordamiento INTO equivale a

NOP.

INT 5: rango excedido en la

instrucción BOUND (sólo 186 y

superiores). Ha sido

incorrectamente empleada por IBM

para volcar la pantalla por

impresora.

INT 6: Código de operación

inválido (sólo a aprtir del 186). Se

produce al ejecutar una instrucción

indefinida. En la pila se guarda el

CS:IP de la instrucción ilegal.

INT 7: Dispositivo no disponible

(sólo a partir del 286).

INT 8: Excepción de doble fallo (a

partir del 286)

Page 4: INTERRUPCIONES ENSAMBLADOR

INT 9: Desbordamiento del

segmento del coprocesador (a

partir del 286)

INT A: Segmento de estado de

tarea inválida (a partir del 286)

INT B. Segmento no presente (a

partir del 286)

INT C: Excepción de pila (a partir

del 286)

INT D: Excepción de protección

general (a partir del 286)

INT E: Fallo de página (a partir del

286)

INT F: Reservado

INT 10: Error de coprocesador ( a

partir del 286)

11.1.3. Interrupciones hardware (IRQs)

Acrónimo de Interrupt ReQuest o Petición de Interrupción. Es la

denominación habitual de las interrupciones hardware, generadas por la

circuitería del ordenador en respuesta a algún evento. En orden de

prioridad:

IRQ Interrupción Función

0 8Se produce con una frecuencia de 18,2 veces por segundo. Hay un

pulso cada 55 milisegundos.

1 9 Generada al pulsar o soltar una tecla.

2 A Retrazo vertical en EGA/VGA

8 70 Generados en los AT y superiores por el segundo chip controlador de

interrupciones9 71

10 72

11 73

12 74

13 75

14 76

Page 5: INTERRUPCIONES ENSAMBLADOR

15 77

3 B Se requiere servicio COM2 o COM4

4 C Se requiere servicio COM1 o COM3

5 D Disco duro o datos requeridos por LPT2

6 E Servicio de disquete requerido

7 F Datos requeridos por LPT1

Tabla 11-01 - Interrupciones Hardware

En el PC, los dispositivos susceptibles de provocar interrupciones están

conectados al chip 8259, que es el que está conectado a la pata de petición

de interrupción del procesador y es el que genera las señales de

interrupción en la forma en que las espera éste. Este circuito soporta ocho

líneas de entrada de petición de interrupción (IRQ0 A IRQ7). El número de

interrupción generada por el 8259 se calcula como un número base de 8 al

que se le suma un número entre 0 y 7 correspondiente a la línea de

interrupción, por lo que las ocho distintas IRQs generan interrupciones entre

8 y 15.

11.1.4. Interrupciones software

Acceder directamente a cada uno de los elementos del ordenador sería

terrible, además de que existen multitud de modelos diferentes, cada uno

con sus propias identificaciones y formas diferentes de programarlas. Para

allanar este problema existe la BIOS que es un extenso conjunto de rutinas

de entrada/salida que podemos usar para comunicarnos con el ordenador

sin tener en cuenta el modelo de sus componentes, de esta manera

estandarizamos nuestro código.

11.1.4.1. Localización

La BIOS se encuentra en la memoria ROM. Cuando se enciende la

computadora "arranque en frío" el procesador ingresa un estado de

restablecer, pone todas las localidades de la memoria en cero, realiza una

verificación de la paridad de memoria y coloca FFFFh en el registro CS y

cero en el IP, por lo que la primera instrucción a ejecutar está en FFFF:0000

que es el punto de entrada de la BIOS. A partir de aquí, la BIOS verifica los

diferentes puertos para identificar e inicializar dispositivos que están

conectados, y a continuación define el "Vector de Interrupciones" a partir de

la localización 0000:0000 de la memoria, que es una secuencia de punteros

lejanos (4 bytes) a las rutinas de interrupción de la BIOS. A continuación

comprueba si existe un sistema operativo en el disco para acceder a su

primer sector que contiene el cargador de arranque que es un sistema

operativo temporal que recoge el control de la BIOS para cargar en memoria

el sistema operativo final.

Page 6: INTERRUPCIONES ENSAMBLADOR

Una vez que se ha cargado el MS-DOS en memoria tenemos relleno el

Vector de Interrupciones que comprende las localidades de memoria

0000:0000 – 0000:03FF donde se hayan todas las interrupciones de la BIOS

(desde 00h hasta 1Fh) y del DOS (desde 20h hasta 3FFh), que comienza a

partir de la última de la BIOS, por ejemplo, la primera del DOS es 20h para

la terminación del programa. Puesto que son punteros lejanos (4 bytes) y su

posición se corresponde con el número de la interrupción en la forma: para

la interrupción i, la posición i*4. Así, para INT 21h buscaremos en 21h * 4h =

84h, es decir en 0000:0084.

Existe además un área de datos reservado para la BIOS de 256 bytes (100h)

a partir de la posición 0040:0000 con abundante información del sistema.

Obsérvese que 0040:0000h equivale a 0000:0400h, que está justo a

continuación de la última interrupción MS-DOS.

11.1.4.2. Ejemplo / traceo

Usaremos el programa dosCM3 para rastrear el uso de la interrupción INT

21h:

F:\Alfonso\Codigos\Cap8>debug DOSCM3.COM

-u 100, 10B

0CC8:0100 BA0C01 MOV DX,010C

0CC8:0103 B409 MOV AH,09

0CC8:0105 CD21 INT 21

0CC8:0107 B8004C MOV AX,4C00

0CC8:010A CD21 INT 21

-r ES

ES 0CC8

:0

-d ES:84 l4

0000:0080 7C 10 A7 00

|...

-p

AX=0000 BX=0000 CX=002D DX=010C SP=FFFE BP=0000 SI=0000

DI=0000

DS=0CC8 ES=0000 SS=0CC8 CS=0CC8 IP=0103 NV UP EI PL NZ NA

PO NC

0CC8:0103 B409 MOV AH,09

-p

Page 7: INTERRUPCIONES ENSAMBLADOR

AX=0900 BX=0000 CX=002D DX=010C SP=FFFE BP=0000 SI=0000

DI=0000

DS=0CC8 ES=0000 SS=0CC8 CS=0CC8 IP=0105 NV UP EI PL NZ NA

PO NC

0CC8:0105 CD21 INT 21

-t

AX=0900 BX=0000 CX=002D DX=010C SP=FFF8 BP=0000 SI=0000

DI=0000

DS=0CC8 ES=0000 SS=0CC8 CS=00A7 IP=107C NV UP DI PL NZ NA

PO NC

00A7:107C 90 NOP

-q

En primer lugar desensamblamos el código para tenerlo presente, a

continuación anulamos el valor de ES para poder acceder al área 0000:0084

del vector de Interrupciones donde se guarda el puntero al código de la

interrupción 21h (obsérvese que 21h * 4h = 84h) donde obtenemos el valor

"7C 10 A7 00" en formato little endian. Seguidamente ejecutamos el código

hasta llegar a "INT 21" donde ejecutamos con "t" para tracearla y

comprobamos que saltamos a la posición 00A7:107C, que es justamente el

valor que obtuvimos en ES:84 traducido del little endian. A continuación

salimos.

11.1.4.3. Proceso de la interrupción

Las interrupciones pueden ser activadas directamente por el ensamblador

invocando el número de la interrupción a través de la instrucción INT y el

número de función deseada pasada generalmente en el registro AH. Cuando

se ejecuta esta instrucción el procesador:

1. Mira hacia la dirección de la rutina de

interrupción en la Tabla del Vector de

Interrupciones (en la posición

0000:0000), llegando a la deseada

mediante la fórmula 0000:i*4 donde i

es el número de interrupción.

2. Borra la bandera de atrape (TF) y

activa la bandera de interrupciones

(IF)

Page 8: INTERRUPCIONES ENSAMBLADOR

3. Guarda el registro de banderas en la

pila, el segmento de código actual

(CS) y el puntero de instrucciones

actual (IP), en este orden (la

instrucción actual es la siguiente a la

sentencia INT). Al igual que con

"CALL" esto asegura que el control

vuelve a la siguiente posición lógica

en el programa.

4. Salta a la dirección de la rutina de

interrupciones, como se especificó en

la Tabla del Vector de Interrupciones.

5. Ejecuta el código de la rutina de

interrupciones hasta encontrarse una

instrucción "IRET".

6. Recupera de la pila los valores de los

registros IP, CS y de banderas.

Hemos visto que todas las interrupciones guardan en la pila el registro de

banderas y el CS:IP de la siguiente instrucciones, pero las interrupciones

internas o excepciones sólo guardan la CS:IP de la instrucción causante.

Además, las excepciones de división del 8086/88 son diferentes, pues

devuelven la instrucción siguiente a la división.

11.1.5.Tabla de interrupciones del sistema

INT Situación Nombre

00h CPU División por cero

01h CPU Ejecución paso a paso

02h CPU No enmascarable (NMI)

03h CPU Puntos de ruptura

04h CPU Desbordamiento (INTO)

05h BIOS Volcar pantalla por impresora

06h CPU Código de operación incorrecto

07h CPU Reservada

08h IRQ0 IRQ 0: Contador de hora del sistema

09h IRQ1 IRQ 1: Interrupción de teclado

0Ah IRQ2 IRQ 2: canal E/S, segundo 8259 del AT

0Bh IRQ3 IRQ 3: COM2

Page 9: INTERRUPCIONES ENSAMBLADOR

0Ch IRQ4 IRQ 4: COM 1

0Dh IRQ5 IRQ 5: disco duro XT, LPT2 en AT, retrazo vertical PCjr

0Eh IRQ6 IRQ 6: Controlador del disquete

0Fh IRQ7 IRQ 7: LPT1

10h BIOS Servicios de vídeo

11h BIOS Listado de equipos

12h BIOS Tamaño de memoria

13h BIOS Servicios de disco

14h BIOS Comunicaciones en serie

15h BIOS Servicios del sistema

16h BIOS Servicios de teclado

17h BIOS Servicios de impresora

18h BIOS IBM Basic (ROM del Basic)

19h BIOS Arranque del sistema

1Ah BIOS Fecha/hora del sistema

1Bh BIOS Acción de CTRL-Break

1Ch BIOS Proceso periódico del usuario

1Dh BIOS Dirección de la tabla de parámetros de vídeo

1Eh BIOS Dirección de la tabla de parámetros de disquete

1Fh BIOS Dirección de caracteres gráficos

20h DOS Fin de programa

21h DOS Llamar función DOS

22h DOS Dirección de terminación

23h DOS Dirección de la rutina CTRL-BREAK del DOS

24h DOS Dirección manipulador de errores críticos

25h DOS Lectura absoluta de disco

26h DOS Lectura absoluta de disco

27h DOS Finalización de programa residente

28h DOS El DOS está desocupado

29h DOS DOS TTY (impresión en pantalla)

2Ah DOS Red local MS net

2Bh-2Dh DOS Uso interno del DOS

2Eh DOS Procesos Batch

2Fh DOS Multiplex

30h CPM Compatibilidad CP/M-80 (xx:YYyy en JMP Xxxx:YYhh)

31h DPMI Compatibilidad CP/M-80 (XX en JMP Xxx:YYyy)

32h   Reservada

33h   Controlador del ratón

34h-3Fh   Reservadas

Page 10: INTERRUPCIONES ENSAMBLADOR

40h BIOS Interrupción de disquete

41h BIOS Parámetros del disco duro 1

42h BIOS Apunta a la INT 10h original de la BIOS si existe VGA

43h BIOS Caracteres gráficos EGA

44h-45h BIOS Reservadas

46h BIOS Parámetros del disco duro 2

47h-49h BIOS Reservadas

4Ah BIOS Alarma del usuario

4Bh-5Fh BIOS Reservadas

60h-66h   Para uso de los programas

67h EMS Interrupción de EMS (controlador EMS)

68h-6Fh   Reservadas

70h IRQ8 IRQ 8: Reloj de tiempo real AT

71h IRQ9 IRQ 9: IRQ 2 redireccionada

72h IRQ10 IRQ 10: reservada

73h IRQ11 IRQ 11: reservada

74h IRQ12 IRQ 12: Interrupción de ratón IBM

75h IRQ13 IRQ 13: error de coprocesador matemático

76h IRQ14 IRQ 14: controlador de disco fijo

77h IRQ15 IRQ 15: reservada

78h-7Fh   Reservadas

80h-85h   Reservadas para el Basic

86h-F0h DOS Usadas por el BASIC

F1h-FFh   Para uso de los programas

Tabla 11-02 - Tabla de interrupciones del sistema

11.1.6. ISRs

Acrónimo de Interruption Service Routine o Rutina de Servicio a la

Interrupción. Es la rutina para cada tipo de interrupción. Ya hemos visto que

básicamente, cuando ejecutamos un "INT" metemos en la pila el registro de

banderas, el CS y el IP, que apuntan a la siguiente instrucción, y

redireccionamos la ejecución a la rutina de interrupción pasada en "INT" que

termina cuando encuentra un "IRET" que hace un "POP" de la pila del IP, CS

y del registro de banderas. Es decir, el funcionamiento es muy similar al de

una subrutina llamada con un "CALL" y terminada con un "RET". También

hemos dicho que la dirección de la rutina a llamar con un "INT" se encuentra

en la tabla de vectores de interrupción localizada a partir de 0000:0000h.

Veamos algunas características y consejos interesantes de las ISR.

11.1.6.1. Preservar contenido de registros

Page 11: INTERRUPCIONES ENSAMBLADOR

Según la filosofía que hemos comentado al principo de este capítulo, las

interrupciones pueden ser ejecutadas en cualquier momento, por ejemplo,

supongamos que tenemos el siguiente código:

MOV AX, 500

ADD BX, AX

Y la interrupción que tratamos se ejecuta entre ambas, de tal modo que

modifica el contenido del registro AX, el resultado puede ser muy grave. Por

tanto, es conveniente preservar el contenido de todos los registros en el

código de nuestra ISR, el registro de banderas ya se hace de forma

automática, de nosotros depende hacer el resto con un "PUSH" de todos los

registros y un "POP" de éstos al terminar la ISR antes del "IRET".

11.1.6.2. Deshabilitar ejecución de interrupciones

De nuevo, debido a que las interrupciones pueden ser ejecutadas en

cualquier momento, hay veces que es necesario deshabilitar esta

posibilidad para asegurarnos de que no se va a alterar cierto sector crítico

de nuestro código.

La bandera IF indica si las interrupciones están activadas o desactivadas

con un 1 y un 0 respectivamente. Esto se hace con STI y CLI

respectivamente.

Supongamos que queremos definir dentro de nuestra ISR una pila propia en

otro segmento:

MOV AX, 9000h

MOV SS, AX

MOV SP, 3000h

Pero ocurre una interrupción justo de pués de "MOV SS, AX". Ahora el

registro de pila SS contiene un valor, seguramente, diferente al esperado en

la ISR que ha tomado el control, con lo que el resultado puede ser

desastroso. Una forma de evitar esto es desactivando temporalmente las

interrupciones:

MOV AX, 9000h

CLI ; Deshabilitamos interrupciones

MOV SS, AX

MOV SP, 3000h

STI ; Habilitamos interrupciones

De esta forma nos aseguramos que en la parte crítica de nuestro código no

ocurre ninguna interrupción.

Page 12: INTERRUPCIONES ENSAMBLADOR

Sin embargo, existen ciertas situaciones críticas en las que ocurren

excepciones, aunque las tengamos deshabilitadas con CLI. Es el caso, por

ejemplo de fallos importantes de hardware.

En el caso de modificar manualmente el contenido del vector de

interrupciones, sería necesario deshabilitar éstas, por si se ejecuta alguna

antes de completar este proceso, pero ya hemos visto que esto no siempre

es suficiente puesto que un CLI no inhibe una interrupción no enmascarable,

por lo que una opción buena sería modificar la doble palabra de un solo

golpe con "REP MOVS" con lo que no damos oportunidad a ninguna

interrupción a actuar en medio. Obsérvese que si hemos cambiado sólo una

palabra de las dos y una salta una interrupción en medio que hace uso de la

que estamos cambiando, el resultado puede ser impredecible, puesto que

sólo hemos cambiado su dirección a medias.

11.1.6.3. Reservar suficiente espacio de pila

Aunque para nuestro código no necesitemos mucho espacio de pila, sí será

necesario reservar el suficiente para las posibles interrupciones que

pudieran surgir durante su ejecución.

11.1.7. Gestión de interrupciones

Vamos a ver algunos ejemplos de creación de interrupciones y solapamiento

de otras.

Es necesario informar al controlador de interrupciones que ya ha sido

atendida una IRQ inmediatamente antes de retornar de la rutina de servicio

a la interrupción con IRET. Esto se hace enviando el valor 20h al puerto 20h,

que es recogido por el 8259. No es necesario hacer esto, sin embargo, en el

caso de que nuestra ISR pase el control a la antigua ISR, puesto que esta

última se encargará de hacerlo.

MUY IMPORTANTECuando se intercepta una interrupción, con nuestra propia ISR, hay que procurar

preservar los registros de segmento, no sólo en la ISR, sino en el resto de procedimientos

que usemos fuera de ella, especialmente con las interrupciones del reloj. En mi caso, he

interceptado la 1Ch del reloj en el Tetris y me daba problemas algún otro procedimiento.

Tuve que poner en cada procedimiento explícitamente qué valían DS y ES. Imagínate

que usamos variables, pero nuestra rutina ya no sabe qué vale DS o ES, podremos tener

resultados insospechados.

Se puede reemplazar una rutina de interrupción con nuestro código, para lo

cuál debe cumplir:

Provee una nueva ISR para

manejar la interrupción.

Page 13: INTERRUPCIONES ENSAMBLADOR

Guardamos la dirección de la ISR

que queremos cambiar.

Reemplaza la dirección de la

antigua rutina en la Tabla del

Vector de Interrupciones con la

dirección de la nueva rutina.

La nueva rutina debería ser

siempre definida como LEJANA y

terminar con una instrucción

"IRET" en lugar de "RET".

Antes de terminar el programa,

volvemos a poner en la Tabla del

Vector De Interrupciones la

dirección guardada de la ISR

cambiada original.

Lo que normalmente haremos será modificar temporalmente una ISR por

una nuestra para cumplir con algún tipo de gestión. Una vez terminada,

deberíamos restaurar el método original. Sin embargo, no tenemos por qué

hacer esto forzosamente, existen algunas localidades en la Tabla de

Vectores de Interrupciones reservadas para las interrupciones del usuario,

que si utilizamos, no es necesario volver a poner el valor original, de modo

que nuestra interrupción estará disponible durante toda la vida de la sesión

del MS-DOS. Asimismo quizás queramos modificar alguna ISR

indefinidamente para establecer un nuevo controlador de dispositivos, por

ejemplo.

El uso de interrupciones nos ayuda a la creación de programas haciéndolos

más pequeños y fáciles de entender, más estándares y menos dependientes

del tipo de hardware instalado, puesto que son la BIOS y el DOS los

encargados de proporcionarnos estos servicios.

11.1.7.1. Interrupciones para gestión de interrupciones

El MS-DOS nos proporciona ciertas herramientas que nos facilitan la gestión

de interrupciones.

Int AH Inf. Extra Descripción

21h 25hAL = nº interrupción (0 a 255) DS:DX =

dirección nueva ISR

Establece una nueva ISR para el

número de interrupción indicado

en AL

Page 14: INTERRUPCIONES ENSAMBLADOR

21h 35h AL = nº interrupción (0 a 255)

Devuelve en ES:BX la dirección de

la ISR correspondiente al número

de interrupción pasado en AL

2Fh

En AH ponemos el identificador del programa

que engancha con la interrupción. Del 00h al BFh

están reservados, sólo disponemos para los

programas del C0h al FFh. AL es el código de

función

A esta interrupción se la llama

multiplex. Es un método para

verificar la presencia de un TSR y

comunicarse con él.

Tabla 11-03 - Funciones para gestión de Interrupciones

Con la función 35h obtenemos de la Tabla de Vectores de Interrupción la

dirección de la ISR que queremos modificar. Esta función la guardamos. Con

la función 25h establecemos la nueva ISR que sustituirá a la anterior,

básicamente lo que hace es meter en aquella casilla de la Tabla del Vector

de Interrupciones la dirección de nuestra ISR. Una vez que nuestra ISR haya

cumplido su cometido deberemos restaurar la dirección de la antigua ISR

que tenemos guardada en su casilla de la Tabla del Vector de

Interrupciones. Este es el método recomendable para la gestión de

interrupciones.

11.1.7.2. Métodos para la gestión de interrupciones

El método deseado, puesto que es el más sencilloy el estándar porque nos

lo ofrece el MS-DOS, es el uso de las interrupciones que hemos visto arriba.

Sin embargo, la interrupción 21h también puede ser interceptada, con lo

que el primer método podría no funcionar. En este caso tan extraordinario

podríamos vernos obligados a hacerlo a mano.

Equivalente a la función 25h

PUSH ES

MOV DI, Vector*4 ; Dirección en la Tabla de Vectores a

cambiar

MOV AX, 0

MOV ES, AX

LEA SI, newISR ; DS: SI -> newISR

MOV CX, 2

CLI ; Desactivamos interrupciones

REP MOVSW

STI

POP ES

Equivalente a la función 35h

PUSH DS

PUSH ES

Page 15: INTERRUPCIONES ENSAMBLADOR

MOV SI, Vector*4 ; Dirección en la Tabla de Vectores a

guardar

PUSH DS

POP ES

MOV AX, 0

MOV DS, AX

LEA DI, oldISR

MOV CX, 2

CLI ; Desactivamos interrupciones

REP MOVSW

STI

POP ES

POP DS

11.1.8. Ejemplos

Veremos algunos códigos de ejemplo de intercepción de interrupciones.

ImportanteEs más que conveniente ejecutar estos ejemplos desde la ventana de comandos de

Windows. Si posees un W95 o W98 te irán todos bien, pero si tienes un XP seguramente

no te funcionarán algunos, en este caso habría que echar mano de una plataforma como

DOSBox, especialmente los programas residentes. Y es que WXP no parece tratarlos muy

bien.

11.1.8.1. Int 4h

IntCM1 IntCF1 IntCN1 Resultado en pantalla

Código Código Código C:\Trabajo\AOE\Codigos\Cap11>IntCF1

Mensaje de desbordamiento desde mi ISR[bin] [bin] [bin]

Source 11-01 - Intercepción de la interrupción 4

Con la función 35h de Int 21h la dirección de la ISR de la interrupción 4h en

ES:BX, que guardamos en la variable "vector" (little endian). Con la función

25h de Int 21h modificamos en la Tabla de Vectores de Interrupción la

dirección de la ISR de la Int 4h por nuestra nueva rutina en "newISR". A

continuación comprobamos si funciona. Primero hacemos una multiplicación

que no desborda, por lo que el "INTO" equivale a un "NOP". La siguiente

multiplicación sí desborda, por lo que el "INTO" pasa a "newISR" que

muestra el mensaje. Finamente, reponemos la dirección de la ISR original de

la Int 4h.

Obsérvese que la nueva ISR termina con un "IRET".

11.1.8.2. Int 21h

Page 16: INTERRUPCIONES ENSAMBLADOR

IntCM2 IntCF2 IntCN2 Resultado en pantalla

Código Código Código C:\Trabajo\AOE\Codigos\Cap11>IntCM2

Instalada nueva Int 21h

alfonso

Has pulsado 8 teclas

[bin] [bin] [bin]

Source 11-02 - Intercepción de la interrupción 21h

Primero guardamos la dirección de la ISR original en la variable "OldInt21h"

que se encuentra dentro de la rutina "NuevaInt21h" e instalamos la nueva

ISR para la interrupción 21h correspondiente al procedimiento NuevaInt21h.

En un bucle vamos recogiendo las teclas pulsadas con la función 1h de Int

21h y terminamos al pulsar la tecla retorno. Esta función vuelca por pantalla

la tecla pulsada, por lo que vamos viendo lo que vamos escribiendo. Para

terminar restauramos en la Tabla de Vectores de Interrupción la dirección

de la ISR original de la Int 21h.

Nuestra nueva ISR para Int 21h activa las interrupciones con "STI", guarda el

registro de banderas con "PUSHF" (puesto que el último POP de IRET es

"POPF") y hace un "CALL" lejano (DB 9Ah) a la ISR original de la Int 21h para

que se encargue de todo el trabajo duro. A continuación incrementamos el

contador. Esto se ejecuta cada vez que pulsamos una tecla, con lo que

finalmente tendremos la cuenta de teclas pulsadas. Obsérvese que la tecla

retorno también cuenta.

Obsérvese que nuestra ISR la podemos construir de otra manera quizás más

limpia y elegante. Puesto que hacemos una llamada a la antigua ISR de la

Int 21h y ésta finaliza con un "IRET" y el acceso a nuestra ISR se efectúa

mediante una "INT", podríamos hacer la llamada a la antigua ISR mediante

un simple "JMP". No necesitamos un "PUSHF" puesto que esto ya lo hizo la

"INT" que llamó a nuestra ISR, la cuál tampoco necesitamos terminar con

una "IRET", puesto que eso ya lo hace la antigua ISR. En resumen, el código

del procedimiento "NuevaInt21h" quedaría como sigue:

NuevaInt21h PROC FAR

; Propósito: Nueva ISR para INT 21h, cuenta las teclas pulsadas

hasta intro

; entrada : NumTeclas

; salida : NumTeclas

; Destruye : Ninguna

STI ; Activa bandera de

interrupciones

INC BYTE PTR [NumTeclas] ; Código de la tecla pulsada

JMP [CS:OldInt21h]

CLI ; to old BIOS INT9 handler

NuevaInt21h ENDP

Page 17: INTERRUPCIONES ENSAMBLADOR

La variable "OldInt21h" la hemos situado en la zona de datos. Tenemos el

código completo en IntC?2b.asm2. Obsérvese que cuando entramos en una

ISR sólo conocemos el valor del registro "CS", que en un archivo "COM"

coincide con "DS".

IntCM2b IntCF2b IntCN2b Resultado en pantalla

Código Código Código C:\Trabajo\AOE\Codigos\Cap11>IntCN2b

Instalada nueva Int 21h

alfonso

Has pulsado 8 teclas

[bin] [bin] [bin]

Source 11-03 - Intercepción de la interrupción 21h

11.1.8.3. Int 9h

IntCM3 IntCF3 IntCN3 Resultado en pantalla

Código Código Código C:\Trabajo\AOE\Codigos\Cap11>IntCM3

Instalada nueva Int 9h

Has pulsado 0 teclas

[bin] [bin] [bin]

Source 11-04 - Intercepción de la interrupción 9

El mecanismo es igual al programa anterior sólo que en el bucle usamos el

puerto 60h para recoger la tecla pulsada y la comparamos con el código

scan "1Ch" para comprobar si hemos pulsado la tecla retorno, en cuyo caso

salimos. Al pulsar o soltar una tecla se activa la IRQ1 correspondiente a la

interrupción 9h, que es la que interceptamos en este ejemplo. Por tanto, en

nuestra ISR distinguimos esta posibilidad para incrementar el contador sólo

cuando pulsamos una tecla.

11.1.8.4. Int 8h

Vamos a ver en este ejemplo cómo interceptamos la IRQ0 que se produce

18,2 veces por segundo (se genera un pulso cada 55 milisegundos) y que

gestiona el tiempo. El resultado es simular un reloj en modo texto, cuyas

agujas se mueven cada dos segundos.

IntCM4 IntCF4 IntCN4 Resultado en pantalla

Código Código Código

[bin] [bin] [bin]

Source 11-05 - Intercepción de la interrupción 8

El código de intercepción de la interrupción 8h es similar a como lo hemos

venido haciendo. En la nueva ISR preservamos los registros que usamos e

incrementamos el contador de pulsos y el puntero a las "agujas" guardadas

Page 18: INTERRUPCIONES ENSAMBLADOR

en la variable "Indica". Cuando el contador de pulsos ha llegado a los dos

segundos pintamos en la esquina superior derecha la aguja apuntada por

"Posicion", el cuál movemos un punto a la derecha hasta que llega a la

última aguja, en cuyo caso le hacemos apuntar a la primera. Además

limpiamos la variable que cuenta los pulsos.

11.1.8.5. Reloj en tiempo real

Vamos a ver otro ejemplo de intercepción de la Int 8h para mostrar en la

esquina superior derecha de la pantalla un reloj en tiempo real.

RelojCM1 RelojCF1 RelojCN1 Resultado en pantalla

Código Código Código

17:01:14

C:\Trabajo\AOE\Codigos\Cap11>RelojCM1[bin] [bin] [bin]

Source 11-06 - Reloj interceptando Int 8h

Primero guardamos la dirección de la ISR original de la Int 8h. A

continuación la cambiamos por la nuestra, situada en el procedimiento

"Reloj". Para obtener la hora actual accedemos al CMOS a través de los

puertos 71h y 72h (más información en el siguiente capítulo) que se

encuentran en formato BCD empaquetado, por lo que necesitamos una

rutina que lo convierta a ASCII y lo imprima por pantalla, esta rutina es el

procedimiento "BCD2ASCII" que usa el acceso directo a memoria de vídeo

para escribir en pantalla. Una vez establecida nuestra rutina esperamos con

un bucle a pulsar la tecla escape, tras lo cuál restituimos la ISR original de la

Int 8h.

Este ejemplo es interesante, pues es la base para la construcción de un TSR

que muestre por pantalla un reloj en tiempo real en la esquina superior

izquierda de la pantalla, lo veremos en el siguiente capítulo.

Obsérvese que para hacer un salto lejano en NASM es necesario el uso del

prefijo "FAR" delante de la dirección a saltar.

11.1.8.6. Muestra teclado

Echemos un vistazo al programa "KeybC?1.asm" que finalmente colgué en

el capítulo 9.1.4.. En este ejemplo interceptamos la Int 9h para mostrar

gráficamente qué teclas pulsamos y soltamos. Además tenemos la

posibilidad de pulsar varias teclas a la vez, muy útil en los videojuegos.

11.1.8.7. Hora y fecha actuales

Page 19: INTERRUPCIONES ENSAMBLADOR

En este ejemplo no interceptamos interrupciones, simplemente, usamos

éstas para obtener la fecha y hora actuales.

IntCM5 IntCF5 IntCN5 Resultado en pantalla

Código Código Código C:\Trabajo\AOE\Codigos\Cap11>IntCN5

La fecha actual es 01-08-2010

La hora actual es 17:11:04[bin] [bin] [bin]

Source 11-07 - Hora y fecha actuales con interrupciones

11.1.8.8. Establece rutina propia para interrupción

Vamos a ver un ejemplo con sintaxis FASM (otras dos también disponibles)

de cómo establecemos nuestra propia rutina ISR en un vector de

interrupción libre que luego podremos usar con una instrucción "Int" normal:

VectLCM1 VectLCF1 VectLCN1 Resultado en pantalla

Código Código Código C:\Trabajo\AOE\Codigos\Cap11>VectLCF1

El vector 80h está libre

Este es un mensaje desde la interrupcion 80h

C:\Trabajo\AOE\Codigos\Cap11>VectLCF1

El vector 81h está libre

Este es un mensaje desde la interrupcion 81h

C:\Trabajo\AOE\Codigos\Cap11>

[bin] [bin] [bin]

Source 11-08 - Búsqueda de un vector de interrupción libre

Observamos en la salida la cadena de texto imprimida por nuestra ISR. Si

ejecutamos de nuevo el programa, el vector 80h ya no está libre porque lo

ocupamos en la anterior ejecución. Podemos comprobar haciendo otro

programa que podemos llamar a la interrupción 80h y nos imprimirá un

mensaje (el número de interrupción libre que mostrará será el último

establecido), de modo que tendremos este servicio disponible hasta que

otro programa pise el código de nuestra ISR o cerremos nuestra sesión de

MS-DOS. Un hecho destacado en este código es que usamos un artificio

para modificar el código en ejecución, para lo que definimos la etiqueta

"NumVect" que apunta al número de la sentencia "INT 0", que modificamos

en cuanto conocemos el número de vector libre. De esta forma usamos

nuestra ISR como un procedimiento cualquiera con la salvedad de que la

llamamos con una interrupción en lugar de un "CALL".

Normalmente, cuando modificamos un vector de interrupción, tras haber

finalizado su trabajo, deberíamos dejarlo en su formato original, no hacerlo

así puede resultar peligroso. En este programa no lo hemos hecho porque el

objetivo perseguido es mostrar el uso de nuestra ISR con una instrucción

"Int".

Page 20: INTERRUPCIONES ENSAMBLADOR

11.1.8.9. Buscar Interrupción

Supongamos que hemos instalado una interrupción que alguien quiere usar

pero no sabe dónde está. Para resolver este problema debemos dotar a

nuestra ISR de un método de identificación, normalmente haciendo que la

función 0 del gestor de la interrupción devuelva una cadena o un código

identificativo que otros programas podrán testear antes de usarlo. Además

ya hemos visto que usualmente todas las interrupciones disponen de varias

funciones ofreciendo un trabajo diferente según la elegida. Es importante

recalcar que la interrupción que hayamos creado permanecerá funcional

mientras no hay otro programa que pise el código de nuestra ISR, cuando

cargamos otro programa en memoria, lo más probable es que lo pise. La

forma correcta de crear una interrupción y que ésta permanezca funcional

en memoria indefinidamente es a través de una TSR que veremos en el

capítulo siguiente, pues así nos aseguramos que esa porción de memoria

ocupada por nuestra ISR no va a ser pisada por ningún otro código. Vamos a

ver con un ejemplo todo ello dentro de un mismo programa por lo que

acabamos de comentar.

VectLCM2 VectLCF2 VectLCN2 Resultado en pantalla

Código Código Código c:\Trabajo\aoe\codigos\cap11>VectLCM2

Interrupcion establecida en el vector 80h

Mensaje de la funcion 1 de la interrupcion

80h

Mensaje de la funcion 2 de la interrupcion

80h

No existe esta funcion

[bin] [bin] [bin]

Source 11-09 - Creamos interrupción con funciones e identificación

Primero buscamos un vector de interrupción libre y aquí establecemos la

dirección de nuestra ISR que chequea si le pedimos su identificación (AH=0)

en cuyo caso devuelve AX=1212h, si AH=1 ejecuta la función 1 que manda

un mensaje a pantalla y si AH=2 ejecuta la función 2 que manda otro

mensaje a pantalla, si AH posee cualquier otro valor indica que no existe

esa función. Tras esto buscamos la interrupción recién creada entre las 128

últimas posiciones de la tabla de interrupciones. Para ello comprueba que

no es un vector de interrupción libre, en cuyo caso le pide que se identifique

y si el resultado devuelto es el correcto llama a las funciones 1, 2 y 3.

Finalmente restablece el valor original en el vector de interrupciones y sale

al DOS.

Como apunte interesante, cabe destacar que en el procedimiento

RestoreOldInt la orden "LDS DX, OldInt" cambia el valor de DS, por lo que si

hacemos antes "MOV AL, NumIntL" fallará por razones obvias. Además,

Page 21: INTERRUPCIONES ENSAMBLADOR

quizás ya te hayas percatado que tanto en FASM como en NASM hay que

utilizar "LDS DX, [OldInt]".

11.1.9. Interrupción Multiplex

Hasta ahora, para encontrar una interrupción teníamos que recorrer el

vector de interrupciones y preguntar por ella, lo cuál implica ejecutar cada

una de las interrupciones. Es natural estremecerse ante este sistema

porque podemos llegar a ejecutar cientos de códigos de los que, en

principio, no tenemos por qué saber nada y lo normal será que se bloquee el

sistema. Disponemos de un mecanismo mucho más eficiente y elegante

desde la versión 3.0 del MS-DOS, se trata de la interrupción multiplex 2Fh,

que básicamente es un gestor de interrupciones, cuando un programa

necesita usar un código residente, genera una interrupción 2Fh. En AH debe

ir el identificador de la interrupción. Para engancharse a la interrupción

múltiple, no debemos sustituirla sin más, puesto que existen multitud de

programas que también la siguen utilizando, por lo que deberemos

interceptarla como ya sabemos dejando una puerta abierta para ejecutar la

ISR estándar en el caso de que no pasemos por nuestro código. Puesto que

ésta es una interrupción software, el registro de banderas puede ser

utilizado por algún gestor para devolver información, por lo que no

deberíamos terminar la Int 2Fh con un "IRET", usaremos en su lugar "RET n"

donde n indica el número de bytes adicionales a extraer de la pila.

Obsérvese que Int 2Fh equivale a "PUSHF" y "CALL 2Fh".

VectLCM3 VectLCF3 VectLCN3 Resultado en pantalla

Código Código Código c:\Trabajo\aoe\codigos\cap11>VectLCN3

Mensaje de la funcion 1[bin] [bin] [bin]

Source 11-10 - Creamos interrupción con funciones e identificación vía múltiplex

Antes de interceptar la interrupción 2Fh guardamos su dirección del vector

de interrupciones para restablecerla al final del programa. En nuestra ISR

"NueVector" comprobamos que preguntan por nosotros mirando si AH =

C0h, si no es así saltamos a la ISR original de 2Fh. Si nos buscaban a

nosotros, el desarrollo es similar al VectLC?2.asm. Probamos nuestro código

con el procedimiento "LlamaInt" donde primero comprobamos que está

disponible y a continuación la llamamos de nuevo usando la función AL = 1

para mostrar un mensaje por pantalla.