lunes, 19 de marzo de 2007

Curso de ensamblador (Parte 2)

Hola a todos los seguidores del curso de ensamblador.

A petición de algunos lectores de este curso, incluir‚ en esta lección una ampliación de la primera. Más concretamente, desarrollar‚ un poco más el tema de los chips de apoyo (inteligentes, porgramables, etc.) que toque tan ligeramente. Aunque aún no es el momento de estudiarlos por separado, y por tanto en profundidad, daré una relación de ellos, y que función realizan.

Bueno, menos rollo y al grano:

LECCION 3:

- CHIPS DE APOYO (Ampliación de la lección 1): -------------------------------------------- Ya vimos en la primera lección que se entendía por chips de apoyo, soporte, etc. También llamados controladores, ya que controlan una parte del hardware para ir aligerando el trabajo de la CPU. De esta forma la CPU tiene mas tiempo para la ejecución del programa correspondiente. En muchos casos, estos chips son programables. Por supuesto, estos chips pueden ser programados por el programador en ensamblador (valga la redundancia), con lo cual no trabajan por su cuenta, sino que aceptan las instrucciones que les hacen funcionar a través de la CPU.

A continuación se da una relación de los diferentes chips de apoyo o controladores del Pc:

- El controlador programable de interrupciones (chip 8259) En un Pc, una de las tareas esenciales de la CPU consiste en responder a las interrupciones del hardware. Una interrupción del hardware es una señal generada por un componente del ordenador que indica que ese componente requiere la atención del procesador. Por ejemplo el reloj del sistema, el teclado, y los controladores de disco, generan interrupciones de hardware en un momento dado para que se lleve a cabo su tarea. En ese momento, la CPU responde a cada interrupción, llevando a cabo la actividad de hardware apropiada, ejecutando lo que se llama rutina de atención a la interrupción, que es una porción de código que se ejecuta como respuesta a una petición de interrupción.

--- Tomemos como ejemplo el teclado. (Puede ser conveniente leer antes el apartado 'Interrupciones', que viene desarrollado mas abajo).

El usuario pulsa una tecla. Inmediatamente, la circuitería digital del periférico detecta la pulsación de la tecla y almacena su "código de rastreo" (toda tecla tiene asociado un código de 8 bits denominado scan code) en un registro reservado para tal fin, llamado puerto de teclado. (Mas adelante, al hablar de puertos, se amplía la información). Entonces, el teclado activa una línea de petición de interrupción, mas concretamente, la línea IR1 del 8259. (IR son las siglas de Interrupt Request, o petición de interrupción. También se puede decir IRQ, que es a lo que estamos mas acostumbrados, sobre todo cuando instalamos una tarjeta de sonido o algo por el estilo). A continuación, el 8259 activa el pin INTR de la CPU. (El pin INTR se activa cada vez que se produce una petición de interrupción, es una línea externa que comunica al Procesador con el exterior). Por último, y resumiendo mucho, la CPU termina la instrucción en curso, y ejecuta la rutina de atención a la interrupción. Al terminar de ejecutar esta rutina, el control vuelve a la siguiente instrucción por donde se había quedado en el programa en curso. Todos los registros deben tener el valor que tenían antes de ejecutar dicha rutina.

--- El controlador programable de interrupciones se llama a menudo por sus siglas: PIC.

- El controlador DMA (chip 8237). Algunas partes del ordenador son capaces de transferir datos hacia y desde la memoria, sin pasar a través de los registros de la CPU. Esta operación se denomina acceso directo a memoria o DMA (Direct Memory Access), y la lleva a cabo un controlador conocido como controlador DMA. El propósito principal de dicho controlador, es el de permitir a las unidades de disco leer y escribir datos prescindiendo de pasar por los registros del microprocesador. De esta forma, las transferencias de datos se hacen mas rápidas. Pero esto es sólo en teoría, ya que con los modernos procesadores que cuentan con una frecuencia de proceso varias veces mas rápida que la del bus, el controlador DMA, apenas ofrece ninguna ventaja.

- El Interface de periferia (chip 8255). El interface de periferia crea una conexión entre la CPU y los dispositivos periféricos como el teclado y el altavoz. Actúa como una especie de intermediario utilizado por la CPU para comunicar determinadas señales al dispositivo deseado.

- El generador de reloj (chip 8248). Este generador siministra las señales de reloj que coordinan el microprocesador y los periféricos. Produce una señal oscilante de alta frecuencia. Por ejemplo, en el IBM PC original esta frecuencia era de 14,31818 megahercios o millones de ciclos por segundo. No hay que confundir esta frecuencia con la frecuencia del procesador. Otros chips que necesitan una señal de tiempo regular, la obtienen del generador de reloj, dividiendo la frecuencia base por una constante para obtener la frecuencia que necesitan para realizar sus tareas. Por ejemplo, el 8088 del IBM PC, funcionaba a 4,77 MHz, una tercera parte de la frecuencia base. El bus interno del IBM PC y el temporizador utilizan una frecuencia de 1,193 MHz, es decir, un cuarto del ratio del 8088 y una doceava parte del ratio base.

- El temporizador o timer (chip 8253). Este chip genera señales de tiempo a intervalos regulares controlados por software. Esto es, que podemos cambiar la frecuencia de estos intervalos por medio de un programa. El timer dispone de varias líneas de salida, funcionando cada una con una frecuencia independiente a las otras, y conectadas cada una a otros componentes del sistema. Una función esencial del contador es la de generar un tic-tac de reloj que mantenga actualizada la hora del día. Otra de las señales producidas por el contador puede ser utilizada para controlar la frecuencia de los tonos producidos por el altavoz del ordenador.

- El controlador de vídeo (chip 6845). El controlador de vídeo, al contrario del resto de chips de apoyo presentados hasta ahora, no se encuentra en la placa madre del PC, sino que está depositado en una tarjeta de video colocada en una ranura de ampliación. Es el corazón de las tarjetas de video CGA, EGA, VGA, etc.

- Controladores de entrada/salida. Los PCs tienen varios subsistemas de entrada/salida con circuitería de control especializada que proporciona un interfaz entre la CPU y el hardware de E/S. Por ejemplo, el teclado tiene un chip controlador propio que transforma las señales eléctricas producidas por las pulsaciones de teclas en un código de 8 bits que representa la tecla pulsada. Todas las unidades de disco disponen de circuitería independiente que controla directamente la unidad. La CPU se comunica con el controlador a través de un interfaz. Los puertos serie y paralelo también disponen de sus propios controladores de entrada/salida.

- Los coprocesadores matematicos (8087/80287/80387). Son utilizados en caso de estar disponibles en el ordenador, para trabajar con números en coma flotante y coma real, cosa que el 8086 no puede hacer.

Todos estos chips, se conectan entre sí, a través del BUS, que ya sabemos en que consiste.

LECCION 4:

- ENTRADA/SALIDA (COMUNICACION CON EL HARDWARE I). - INTERRUPCIONES (COMUNICACION CON EL HARDWARE II). - LA PILA DEL 8086. --------------------------------------------------------------------

Hola a todos. En esta lección vamos a tratar aspectos muy interesantes de la programación del Pc, interrupciones, puertos, etc. Espero que tengais claro lo del error que os comentaba en la lección 1, acerca de los buses. Si no es así, a que esperais para preguntar... Bueno, seguimos con lo nuevo:

- ENTRADA/SALIDA (COMUNICACION CON EL HARDWARE I). ------------------------------------------------ La comunicación entre un programa y el hardware, es decir los chips de apoyo y las tarjetas de ampliación, se efectúa mediante los llamados Ports (puertos, en castellano). Estos puertos son zonas de memoria de 1 o 2 bytes de tamaño, en las cuales se depositan los datos que van a ser utilizados por los chips o tarjetas, y también se depositan los datos que devuelven estos chips o tarjetas al procesador.

En el Pc, existe una zona de memoria de 64 Ks, ajena a la memoria principal, dedicada a los puertos. Es decir, estos 64 Ks de memoria no tienen nada que ver con la memoria disponible para los programas.

Para realizar los movimientos de datos entre puertos y procesador existen instrucciones especiales: IN y OUT. Tanto IN como OUT tienen dos formas de uso, las cuales utilizan el registro AL o AX para almacenar los datos a leer o escribir. La forma difiere en función de que se quiera acceder a un puerto menor de 256 o mayor.

+ M‚todo directo o estático: corresponde cuando se quiere acceder a un puerto menor de 256.

IN AL,10H ---> lee un byte del puerto 10h IN AX,10H ---> lee una palabra del puerto 10h OUT 0FFH,AL --> escribe el valor de AL en el puerto 0FFH

+ M‚todo inndirecto o dinamico: corresponde al caso de querer acceder a un puerto mayor de 256, para lo cual se utiliza el registro DX indicando el número de puerto.

IN AL,DX ----> lee un byte del puerto indicado por DX. (Antes hemos tenido que introducir en DX la dirección del puerto). OUT DX,AX ----> escribe la palabra contenida en AX en el puerto DX.

Algunos ejemplos de puerto son: 60H ----------> acepta entradas de teclado. 61h ----------> controla el altavoz. 3F0H-3F7H ----> Opera sobre la controladora de discos.

En el PC cualquier subsistema, exceptuando la memoria, está controlado por el procesador a través de los puertos.

- INTERRUPCIONES (COMUNICACION CON EL HARDWARE II). --------------------------------------------------- Las interrupciones constituyen la forma en que la circuitería externa informa al microprocesador de que algo ha sucedido (como que se ha pulsado una tecla, por ejemplo) y solicita que se emprenda alguna acción. Pero no acaba ahí su utilidad, ya que las interrupciones ademas son el medio principal de comunicción entre las funciones de la BIOS y el DOS. En este segundo caso, son mal llamadas interrupciones. Mas bien habría que decir funciones, ya que nos sirven para hacer una llamada a una función BIOS o DOS, como por ejemplo la acción de cambiar de modo de video, para la cual se utiliza la interrupción 10h (Driver o controlador de vídeo), con el número adecuado de función. Mas adelante veremos cómo llamar a una función.

Al primer tipo de interrupciones se les denomina interrupciones de hardware, y son las interrupciones reales. Esto es, que estando un programa en ejecución, se interrumpe éste para ejecutar un trozo de código necesario para atender a la petición de un dispositivo, como puede ser el teclado. Acto seguido, se reanuda la ejecución del programa en cuestión. Son las interrupciones que vimos en la lección 3, al hablar del PIC.

Al segundo tipo de interrupciones se les denomina interrupciones de software, y son las 'ficticias', ya que no hay ningún dispositivo pidiendo atención del procesador, sino que es el programa del usuario el que ejecuta una función BIOS o DOS, mediante una interrupción. En este caso, no se interrumpe el programa de forma súbita, sino que es dicho programa el que lanza una interrupción, la cual tiene su rutina de atención a la interrupción (como vimos en la lección 3) 'conectada' a un grupo de funciones o rutinas.

Veamos las interrupciones con mas detalle:

+ La tabla de vectores: --------------------- Toda interrupción aceptada conduce a la ejecución de un subprograma específico, como hemos visto. Pero cómo sabe el procesador dónde empieza este subprograma, una vez que atiende a la interrupción... La respuesta nos la da la tabla de vectores.

Esta tabla de vectores contiene las direcciones de comienzo o punteros al subprograma de atención a la interrupción. La tabla está compuesta de 256 entradas. Es decir, son posibles 256 interrupciones diferentes en el PC.

Cada una de estas entradas, contiene la dirección de inicio del código de atención a una interrupción, en la siguiente forma: 2 primeros bytes (una palabra) que contienen la dirección base del segmento, y los 2 últimos bytes que contienen el desplazamiento. En total 4 bytes para indicar el comienzo de una interrupción, en la forma segmento:desplazamiento.

Ya vimos en la segunda lección cómo transformar una dirección segmentada (segmento:desplazamiento) en una dirección física o real.

Durante la aceptación de una interrupción, el 8086 carga la dirección base del segmento en el registro CS y el desplazamiento en el contador de programa IP. De esta forma, la siguiente instrucción a ejecutar, que viene dada por los registros CS:IP, ser la primera del subprograma de atención a la interrupción.

+ Pines (líneas de bus) para demandar interrupción desde el exterior. ------------------------------------------------------------------- Existen 3 líneas externas jerarquizadas que son, por orden de prioridades decrecientes: RESET, NMI e INTR. Sólo INTR es enmascarable (cuando un pin de demanda de interrupción está enmascarado -inhabilitado- la activación del pin, no produce ninguna interrupción).

Es decir, que si se activan los pines RESET o NMI, siempre van a conducir a la ejecución de una interrupción. Pero si se activa el pin INTR, tenemos dos opciones (dependiendo de si está enmascarado o no), que son hacer caso omiso de la petición de interrupción, o atender dicha interrupción, respectivamente.

Pin INTR: Una petición de interrupción sobre este pin es enmascarable mediante el bit IF (bandera de interrupción) del registro FLAGS o registro de estado. Este bit IF, es la mascara de INTR. Para saber si está enmascarada o no la línea INTR, se mira este flag. El cual puede tener (obviamente) dos valores: 0 y 1. Enmascarado es 0.

Para manipular este bit, disponemos de dos instrucciones en ensamblador: CLI (Clear IF, o borrar flag IF) que lo pone con valor 0. STI (Set IF, o activar flag IF) que lo pone con valor 1.

La petición de interrupción se realiza activando el pin INTR con nivel alto (1) y debe mantenerse así hasta que por el pin INTA (pin asociado al pin INTR. Es activo a nivel bajo (0), indicando que se ha aceptado la interrupción solicitada por medio del pin INTR) el 8086 indique que ha sido aceptada.

Entonces... Contamos con el pin INTR para pedir al procesador atención a

una interrupción, y con el pin asociado INTA que estr con valor (0)

cuando la interrupción haya sido aceptada.

INTR ---> Interrupt Request (petición de interrupción). INTA ---> Interrupt Accepted (interrupción aceptada).

Veamos cómo actúa la CPU desde que se activa el pin INTR hasta que se retorna del subprograma de atención a la interrupción:

Debido a que la interrupción interrumpir al programa en ejecución en cualquiera de sus instrucciones, es necesario resguardar el contenido del registro de estado (FLAGS), para que al volver de la interrupción, tengan las banderas el mismo valor. Y sobre todo, hay que guardar la dirección de la siguiente instrucción a ejecutar en el programa actual.

Pero dónde se guardan todos estos datos... En una zona de memoria denominada PILA, la pila del procesador. (Explicación en el último apartado de esta lección). Al acto de introducir un dato en la pila se le denomina apilar, y a sacarlo de la misma se le denomina desapilar.

Pues bien, el procesador hará lo siguiente: - Apila el contenido del registro de estado (flags) - Apila la dirección de retorno (contenido de los registros CS e IP). - Inhibe las interrupciones (IF=0 y TF=0, mas adelante se comenta la utilidad del flag TF o TRACE). Esto se hace para que no se produzca otra interrupción durante la secuencia de aceptación de la interrupción. Esto es muy importante. - Activa el pin INTA (lo pone a nivel bajo). El dispositivo que ha solicitado la interrupción, al notar el cambio en el pin INTA, queda enterado de la aceptación de la interrupción. - Lee el número del vector de interrupción del bus de datos. Previamente, el dispositivo lo ha depositado en respuesta a la activación del pin INTA. - Obtiene la dirección del subprograma de atención a la interrupción. Dicha dirección se encuentra (como hemos visto antes) almacenada en la tabla de vectores. - El 8086 ejecuta la subrutina que finaliza con la instrucción IRET, o Retorno de Interrupción, cuya ejecución restituye en CS e IP la dirección de retorno salvada en la pila, y en el registro de estado el valor de los flags.

Al restaurar los flags, se anula la inhibición anterior de IF y TF, con lo cual, otra vez se aceptan interrupciones. Pudiendo así tener interrupciones en cascada.

Repasar el ejemplo de la pulsación de tecla de la lección 3, a ver si ahora se ve con mas claridad.

Pin NMI: Este pin está reservado a acontecimientos graves, como puede ser un corte de corriente, un error de memoria, del bus, etc.

La activación de NMI no conlleva ninguna lectura en el bus de datos del nº de vector de interrupción, sino que la CPU directamente busca el vector de interrupción número 2.

Pin RESET: Inicializa el sistema. En la petición de RESET no se almacena nada en la pila ni se accede a la tabla de vectores para conseguir la dirección de comienzo. Al activar el pin RESET, el registro de estado queda borrado (0). CS = 0FFFFh. IP = 00000h. De esta manera, la siguiente instrucción a ejecutar por el procesador es la contenida a partir de FFFF:0, código de reinicialización y carga del sistema operativo. Son los últimos bytes de la ROM. El resto de registro de segmentos quedan con valor 0. DS = 0000 ES = 0000 SS = 0000

+ Interrupciones internas o desvíos. ---------------------------------- El microprocesador 8086 tiene 2 interrupciones internas: 'División imposible' y 'funcionamiento paso a paso (TRACE)'.

División imposible: Se produce cuando se divide por 0, o cuando el cociente resultante de la división no cabe en el registro preparado para contenerlo. En ambos casos, se ejecuta la interrupción 0.

Funcionamiento paso a paso: Si el programador coloca a (1) el bit TF (TRACE) del registro de estado, al final de cada instrucción, la CPU bifurcará a la posición de memoria indicada por el vector de interrupción número 1. Esto es lo que utilizan los debuggers o depuradores de código para hacer un seguimiento del programa, instrucción por instrucción.

Mas adelante, cuando hablemos acerca de la programación de utilidades residentes, entraremos en la programación práctica de las interrupciones. Valga lo dicho hasta ahora como base teórica.

- La pila del procesador: ------------------------- La pila es una característica interna del 8086. Es una estructura de datos situada en la RAM. Proporciona a los programas un lugar donde almacenar datos de forma segura, pudiendo compartirlos con otros procedimientos o programas de forma cómoda y práctica.

La función mas importante de la pila es la de mantener las direcciones de retorno en las llamadas a procedimientos e interrupciones, así como guardar los parámetros pasados a estos procedimientos. La pila también se utiliza para almacenamiento temporal de datos dentro de un programa, y para muchas cosas mas que se aprenden con la práctica.

La pila tiene su nombre por analogía con los montones de platos apilados (pilas de platos). Cuando un dato nuevo es introducido en la pila, se dice que es apilado (push) debido a que se sitúa por encima de los demas, es decir se sitúa en la CIMA de la pila.

Una pila opera en el orden último-en-entrar - primero-en-salir: LIFO (LAST IN FIRST OUT) o lo que es lo mismo, el último en entrar es el primero en salir.

Esto significa que cuando la pila se utiliza para seguir la pista de los retornos de las subrutinas, la primera llamada a subrutina que se hizo, es la última que se devuelve. De esta manera, la pila mantiene ordenado el funcionamiento del programa, las subrutinas y rutinas de tratamiento de interrupción, sin importar la complejidad de la operación.

La pila crece en orden inverso. Es decir, a medida que se añaden nuevos datos, la cima de la pila se acerca mas a posiciones mas bajas de memoria.

Existen 3 registros destinados a gestionar la pila. Registro de segmento de pila (SS): que indica la dirección base del segmento de pila Puntero de pila (SP): que apunta a la cima de la pila. Puntero base de pila (BP): que se usa para moverse a través de la pila sin cambiar la cima. Se suele utilizar para acceder a los distintos parámetros al llamar a una función.

Los elementos que se almacenan en la pila son del tipo palabra (2 bytes). Esto quiere decir, entre otras cosas, que el puntero de pila (SP), así como el puntero base de pila (BP), incrementan/decrementan en 2 su valor para apuntar a un nuevo elemento dentro de la pila, fruto de apilar o desapilar un elemento.

También conlleva el que si queremos almacenar un byte en la pila, primero lo debemos convertir en palabra (2 bytes), y luego almacenar esa palabra. Esto es muy sencillo, sólo hay que meter ese byte o registro de 8 bits en un registro de 16 bits y almacenar este registro.

Las instrucciones para manejar la pila son: PUSH ---> Guarda un dato en la pila. Decrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir. Ejemplo: PUSH AX --> Apila el contenido de AX en la cima de la pila.

POP ----> Obtiene un dato de la pila. Incrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir. Ejemplo: POP AX --> Desapila el contenido de la cima de la pila en el registro AX. Es decir, AX contendrá el valor que hubiera en la cima de la pila, y el puntero de pila se actualiza incrementándolo en 2.

PUSHF --> Guarda el contenido del registro de estado (FLAGS) en la pila. Decrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir. No es necesario indicar sobre que actúa esta instrucción, lo lleva implícito en su nombre PUSHF (PUSH FLAGS).

POPF ---> Introduce en el registro FLAGS el contenido de la cima de la pila. Incrementando SP en 2 unidades, para que apunte al nuevo elemento a introducir. Al igual que con la instrucción anterior, no es necesario indicar sobre que actúa esta instrucción POPF (POP FLAGS).

Conviene recordar el hecho de que la pila crece en orden inverso al normal, es decir de direcciones de memoria altas a direcciones bajas. Por lo tanto es necesario tener en cuenta el uso que se va a hacer de la pila en el programa, debido a que si reservamos espacio en nuestro programa para una pila pequeña, en caso de sobrepasarla haciendo muchos push seguidos, machacaría nuestro programa.

Hay que tener en cuenta que no sólo es nuestro programa el que utiliza la pila mediante la instrucción PUSH y mediante llamadas a procedimientos, interrupciones, etc. Sino que mientras nuestro programa corre se están sucediendo numerosas interrupciones que conllevan muchos PUSH. Por ejemplo, 18'2 veces por segundo se produce la interrupción de reloj, con lo cual, todas estas veces se está apilando y posteriormente quitando información de la pila.

Por regla general, basta con tener una pila de unos 2 KS, es decir, espacio para almacenar 1024 elementos. Es muy difícil que se sobrepase este tamaño.

---

Bueno... Aquí seguro que hay dudas. Venga, decidme que quereis que explique mas detenidamente, que dentro de un par de lecciones empezamos a programar, y hay que tenerlo todo claro.



Artículos relacionados


No hay comentarios: