lunes, 12 de marzo de 2007

Curso de ensamblador (parte 1)

Esta primera lección va a ser sobre todo teórica.

No se puede programar en ensamblador sin tener un conocimiento mas o menos profundo acerca de las peculiaridades de la maquina que vamos a programar. Esto es obvio, ya que en ensamblador tenemos un control total del sistema, por tanto, cuanto mas sepamos acerca de el, mas provecho podremos sacar del mismo, y mejores resultados obtendremos.

En primer lugar debemos saber con que estamos trabajando, y no me refiero al lenguaje en sí, sino al ordenador en cuestión. Tendremos que conocer de que partes consta, como se comunican entre sí, etc.

Esta parte teórica es casi imprescindible para poder entender ciertas técnicas de programación.

Deciros también que vamos a estudiar el Pc en general, no distinguiremos entre distintas familias de procesadores (8086,286,386,etc) ya que vamos a utilizar ensamblador del 8086. En realidad es el ensamblador que yo utilizo. Por tanto, aunque estemos programando sobre un 80386 o un 80486, desarrollaremos código ejecutable para la familia 80x86 en general.

Cualquier exposición que haga ser valida para toda la familia Pc.

Bueno... Despues de este preambulo, pasamos al meollo de la cuestión:

Un Pc es un ordenador o computador, como mas os guste, compuesto principalmente por procesador, chips de memoria, varios chips inteligentes o programables, y el bus de datos y direcciones. Junto con todo esto, nos encontramos los periféricos como son monitor, disqueteras, teclado, etc, que se comunican con el procesador. Esto (la comunicación del procesador con periféricos) se ver mas adelante.

El procesador:

Es el chip que ejecuta los programas. El procesador o CPU, lleva a cabo una gran variedad de cálculos, comparaciones numéricas y transferencias de datos como respuesta a las peticiones de los programas que están siendo ejecutados en memoria.

La CPU controla las operaciones basicas del ordenador enviando y recibiendo señales de control, direcciones de memoria y datos de un lugar a otro del ordenador a través de un grupo de 'sendas electrónicas' llamadas BUS. Localizadas a lo largo de este bus están las puertas de entrada y salida (E/S en castellano o I/O en inglés), las cuales conectan a la memoria y a los chips de apoyo al bus. Los datos pasan a través de estas puertas de E/S mientras viajan desde y hasta la CPU y otras partes del ordenador.

Como os decía antes, trataremos el procesador 8086, siendo válido todo lo que digamos para el resto de procesadores de la familia PC.

El procesador 8086 es un procesador de 16 bits. Esto quiere decir entre otras cosas, que el procesador va a manipular en una sóla operación datos de hasta 16 bits. Es decir, cuando transfiera datos a la memoria o los traiga desde ella, lo podrá hacer de 16 bits en 16 bits. Aquí juega un papel decisivo el BUS de datos, ya que es por el por donde circulan los datos en las transferencias. Mas detalles unas cuantas líneas mas abajo.

El procesador cuenta con una serie de registros usados para realizar las operaciones de cálculo, y como almacenamiento de datos. Para que os hagais una idea, un registro del procesador es algo así como una zona de memoria dentro del procesador donde se puede almacenar información, de forma que el acceso a esta información es instantáneo, ya que no hay que utilizar el bus de datos que conecta el procesador con la memoria para obtener dichos datos.

Estos registros se dividen en 5 grupos, según sus funciones:

1.- Registros de datos: AX, BX, CX y DX.

Se usan para cálculo y almacenamiento de propósito general. Son utilizados por los programas para realizar cálculos, así como para transferir datos de una posición de memoria a otra, ya que no se puede hacer de forma directa. Es decir, que no podemos transferir un dato de la posición de memoria X a la posición Y sin antes depositar ese dato temporalmente en un registro del procesador.

Estos registros tienen una longitud de 16 bits, pero podemos descomponerlos cuando nos interese en un par de registros de 8 bits. Quedando de la forma siguiente:

AX = AH + AL

Siendo AX el registro de 16 bits, compuesto por la conjunción (que no la suma) de el registro AH de 8 bits (los 8 bits mas signiicativos o de mas a la izquierda) y el registro AL de 8 bits (los 8 bits menos significativos o de mas a la derecha).

BX = BH + BL

CX = CH + CL

DX = DH + DL

Para estos tres registros se aplica lo mismo que para el registro AX.

Cada uno de estos registros tiene funciones especiales que es interesante conocer. Por ejemplo el registro AX es el llamado acumulador, hace que muchas operaciones tengan una forma mas corta, ya que lo especifican implícitamente.

Es decir, que hay operaciones que actúan sobre el registro AX en particular.

BX se suele utilizar en muchas instrucciones como registro base, sobre todo en transeferencias de datos entre memoria y procesador.

CX es el registro contador, muchas instrucciones lo utilizan para hacer incrementos o decrementos automáticos, para realizar bucles, etc. DX es el registro de datos, se suele utilizar para operaciones de 32 bits, para almacenar los 16 bits (o palabra) mas significativos.

 

2.- Registros Indice: SI, DI, BP y SP. Se utilizan para acceder a memoria cuando se establece el modo de direccionamiento mediante indexación o con punteros. SI y DI indican el índice fuente y destino respectivamente. BP y SP indican el puntero base y el puntero de la pila respectivamenete. Mas adelante hablaremos de la pila (que es un tipo de datos estructurado).

Estos 4 registros son de 16 bits, y no pueden ser utilizados como registros dobles de 8 bits.

3.- Registros de Segmento: CS, DS, SS y ES. Estos registros apuntan al principio de un bloque de 64 ks de memoria o segmento, de ahí lo de la 'S' con la que finalizan todos los registros: CS: registro segmento de código. Establece al área donde se halla el programa en ejecución. DS: registro segmento de datos. Especifica la zona donde el programa lee y escribe los datos por defecto. SS: registro segmento de pila. Especifica el área donde se encuentra la pila del sistema. ES: registro segmento extra. Se utiliza como una extensión del segmento de datos. Es decir, indica otro área de datos aparte del especificado por DS.

4.- Puntero de instrucción: IP. Su longitud es de 16 bits como el resto de registros. Indica la dirección de la siguiente instrucción a ejecutar, y su valor es ajustado durante la ejecución de la instrucción en curso. Esta dirección est en el área de 64 ks de direcciones especificado por CS.

CS e IP en conjunción conforman la dirección física real de la siguiente instrucción a ejecutar. Mas adelante detallaremos este asunto.

5.- Registro FLAGS. O banderas de estado.

Su longitud es de 16 bits. Cada uno de estos bits contiene cierta información booleano (verdadero o falso). Según el valor de cada uno de estos bits sea 1(verdadero) ó 0(falso), informar del estado de alguna situación en particular.

Dentro del registro de FLAGS hay 7 bits que no se utilizan. Los nombres de los utilizados son: Of, Df, If, Tf, Sf, Zf, Af, Pf y Cf.

Estos bits se clasifican en dos grupos: - Flags de estado (Cf, Af, Of, Zf, Pf y Sf): muestran el estado del procesador. - Flags de control ( Df, If, Tf): determinan como el procesador responderá a determinadas situaciones. El programador manipulará estos bits para controlar el modo de ejecución de algunas instrucciones.

A continuación se muestra el significado de cada uno de los flags:

Cf: Bit de Carry (acarreo), se activa (se pone a 1) si se produce acarreo en una operación aritmética. Pf: Bit de paridad, se activa si el resultado de una operación tiene paridad par, es decir, si el resultado tiene un nº par de unos. Af: Bit de carry auxiliar, se activa si una operación aritmética produce acarreo de peso 16. Zf: Bit de cero, se activa si una operación produce 0 como resultado. Suele ser el mas utilizado(mas consultado) por los programadores, por lo menos por mí (AESOFT). Se utiliza para comparaciones de datos y para otras muchas cosas como ciertos bucles,etc. Sf: Bit de signo, se activa si el bit mas significativo de un resultado es 1. Por convención cuando se opera con números negativos, se utiliza el bit de mayor peso para indicar el signo: si el bit es cero, entonces se trata de un número positivo, si es 1, se trata de número negativo. Ya veremos todo esto mas adelante. Tf: Bit trap o desvío. Si Tf=1, el procesador ejecuta las instrucciones una a una bajo control del usuario. Se pone a 1 este bit para realizar depuraciones del código que se está ejecutando. De esta forma se puede seguir el flujo del programa. If: Bit de interrupción, si vale 1, las interrupciones están permitidas, y si vale 0, no. Df: Se usa en las instrucciones que manipulan cadenas de bytes. Según coloque el programador este bit, a '0' o a '1', las cadenas de bytes serán tratadas en sentido de direcciones crecientes o decrecientes. Of: Bit de overflow, indica desbordamiento en una operación aritmética.

 

La Memoria: Los chips de memoria se dedican meramente a almacenar la información hasta que se necesita. El número y la capacidad de almacenamiento de estos chips que hay dentro del ordenador determinan la cantidad de memoria que podremos utilizar para los programas y los datos. Normalmente la memoria siempre se ha dividido en dos tipos principales, como son la RAM y la ROM. De todos será conocida esta distinción y sus caraterísticas, así que no entro en el tema. Si alguien tiene alguna duda, que lo diga. Pues bien, en principio, nosotros vamos a trabajar con el rango de memoria 0 - 1048576, es decir, vamos a trabajar con el primer Mb(megabyte). Existen técnicas para tratar la memoria que hay en direcciones superiores a éstas, pero eso ya se ver en otro momento.

Pues bien, en ese Megabyte inicial, tenemos tanto RAM como ROM. La memoria RAM que es la que usamos para almacenar tanto programas como datos empieza en las direcciones bajas y llega hasta el inicio de la ROM. La ROM, evidentemente está situada en las posiciones altas de memoria, y es ahí donde se almacenan las rutinas mas básicas del ordenador, como las rutinas de acceso a discos, pantalla, etc.

Un tema muy importante en el mundo del PC es la denominada barrera de los 640 Ks que seguro habreis oido hablar. Esto quiere decir que aunque tengamos instalados 8 Megabytes en nuestro ordenador, sólo podremos ejecutar nuestro programa en las primeras 640 ks.

Para poder acceder a los 7 Megabytes restantes debemos utilizar funciones especiales de manejo de memoria extendida, expandida, etc. Pero bueno, eso no nos interesa ahora en absoluto. Os remito al fichero MEMO.ZIP que podreis encontrar en el BBS, en el cual, un usuario (Pc-adicto) nos ofrece una exposición mas amplia acerca de la memoria del Pc.

 

Chips inteligentes: Se trata de chips de apoyo, de los que se sirve el procesador o el usuario. Estos chips existen debido a que el procesador no puede controlar todo el ordenador sin ayuda. Al delegar ciertas funciones de control a otros chips, le queda mas tiempo libre para atender a su propio trabajo. Estos chips de apoyo pueden ser responsables de procesos tales como el flujo de información a través de la circuitería interna(como el controlador de interrupciones y el controlador DMA) y controlar el flujo de información de uno a otro dispositivo(como un monitor o una unidad de disco) concectado al ordenador. En resumen, estos chips están ahí para librar de trabajo al procesador.

 

Bus de direcciones: El bus de direcciones del Pc utiliza 20 líneas de señal para transmitir las direcciones de memoria y de los dispositivos conectados al bus. Como a través de cada una de esas 20 lineas pueden viajar dos posibles valores(0 ó 1, es decir tensíon alta o tensión baja), el ordenador puede especificar 2^20 (2 elevado a 20) direcciones. Es decir puede direccionar 1 Megabyte. Estamos hablando del 8086 con un bus de direcciones de 20 bits. Modelos superiores como el 80386 o el pentium direccionan muchísimo mas, tanto como les permite su bus: 32 bits en el caso del 386 y 64 bits en el caso del pentium. Es decir, 2^32 y 2^64 direcciones respectivamente.

 

Bus de datos: El bus de datos trabaja con el bus de direcciones para transportar los datos a través del ordenador. Este bus de datos es de 16 bits, al igual que el tama través o de registro. Aunque no tiene que coincidir, como sucede con el procesador 8088, el cual tiene un tamaño de registro de 16 bits y un bus de datos de 8 bits. Esto es así por simple economía. Por ahorrar costes. Claro que tiene su parte mala, y es que es mas lento al realizar transferencias de datos.

Bueno, bueno, bueno... Todo esto que parece muy rollero es muy importante. No es lo mismo programar sabiendo con que se trabaja que programar a ciegas. Muchas veces es imprescindible recrear gráficamente ciertas instrucciones, para darse cuenta de un fallo en el programa.

Venga, ahora quiero que me conteis dudas que teneis, aclaraciones, etc. Que la próxima lección ya es mas práctica. Pero hasta que no se tenga claro cómo funciona el ordenador, no se puede hacer que funcione correctamente.

Lo dicho, espero mensajes vuestros antes de la siguiente lección. Un saludo de AESOFT.

 

LECCION 2:

- DIRECCIONAMIENTO DE MEMORIA EN EL 8086. - SEGMENTACION.

Hola a todos los seguidores del curso de ensamblador de AESOFT. En esta lección vamos a ver cómo direcciona la memoria el 8086, es decir, cómo el 8086 accede a cada una de las posiciones de memoria.

La forma en que la CPU construye las direcciones de memoria es muy importante para la programación del sistema, debido a que constantemente utilizamos instrucciones de transferencias de datos, de acceso a funciones de la BIOS, del DOS, etc.

Mas adelante estudiaremos la BIOS. Valga por ahora que es un conjunto de utilidades y procedimientos grabados en la ROM (memoria de sólo lectura), los cuales se encargan de acceder al nivel mas bajo en cuanto a programación se refiere. Es decir, estas rutinas se encargan de manipular el hardware por nosotros. BIOS son las siglas de Basic Input Output System (Sistema básico de entrada/salida).

En cuanto al DOS (sistema operativo de disco), decir que aquí nos referi- mos no a las utilidades o comandos que trae consigo, que es lo típico que se enseña en academias e institutos, sino a la estructura interna del mismo: interrupción 21h, 24h, etc. Ya veremos también que es una interrupción.

Bien, antes de entrar de lleno en el tema, conviene saber un poco del por que del mismo. Es decir, que llevó a que fuera de esta forma y no de otra.

A principio de los años 80, Intel (fabricante de la familia de procesadores 80x86) se propuso dar un gran paso adelante con respecto a la competencia. En aquel tiempo los microprocesadores que imperaban entre los ordenadores domésticos eran de 8 bits, es decir, tenían un ancho de bus de datos de 8 bits, el tamaño de palabra de memoria era de 8 bits, y los registros del procesador eran de 8 bits. Un claro ejemplo de esto fue el microprocesador Z80 (de la empresa Zilog), el cual estaba incorporado en máquinas tan famosas como los spectrum, amstrad, msx, etc.

Como he dicho, el ancho del bus de datos era de 8 bits. Esto quiere decir que todas las transferencias de datos que se hicieran se harían de 8 en 8 bits, es decir, byte a byte.

Pues bien, aunque el microprocesador era de 8 bits, y la mayoría de registros también lo fuera, había alguno mayor (16 bits). Me estoy refiriendo sobre todo al registro de direcciones que era de 16 bits. De esta forma, un amstrad cpc-464 podía acceder a 64 ks de memoria. 64 Ks es la máximo que podía direccionar el z80 original.

En ese momento Intel se planteó superar esa barrera de las 64 Ks, pero tenía un problema. El z80 por ejemplo, había consguido tener registros de 16 bits cuando el microprocesador es de 8. Pero pasar de 16 bits de capacidad en registros en aquellos momentos no era posible para los microprocesadores. Es decir, no había suficientes avances tecnológicos como para conseguir tamaños de registros mayores en un microprocesador. De tal manera que había que buscar una fórmula diferente... Y ahí es cuando surgió el tema de los segmentos que tantos quebraderos de cabeza a dado hasta ahora y sigue dando.

A Intel se le ocurrió la idea de construir una dirección de 20 bits de ancho y colocarla en el bus de direcciones para poder dirigirse a la memoria. Pero al ser los registros de 16 bits, sólo había una solución posible para crear este ancho de 20 bits: Usar 2 registros de 16 bits!!!

El 8086 divide el espacio de direcciones (1 Mbyte) en segmentos, cada uno de los cuales contiene 64 Ks de memoria (la máxima direccionable por un sólo registro). Entonces, para direccionar una posición de memoria nos valemos de dos registros: Registro de segmento y de offset. Ya vimos en la lección anterior que había varios registros de segmento: cs (registro de segmento de código), ds (de datos), etc.

Pues bien, este primer registro (de segmento), indica dónde comienza el trozo de 64 Ks que buscamos. Y el segundo registro (el de offset), contiene el desplazamiento dentro de ese segmento.

Bien. Hemos visto que son necesarios 2 registros para direccionar ese Mbyte de memoria, y tenemos un bus de direcciones de 20 bits. Esto nos conduce a que el microprocesador debe realizar unas operaciones sobre estos dos registros para obtener la dirección física de 20 bits. Esto se logra de la siguiente manera: El 8086 mueve el valor del segmento 4 bits a la izquierda y le suma el valor del desplazamiento para crear una dirección de 20 bits.

Gráficamente: Tenemos dos registros de 16 bits. DS: XXXXXXXXXXXXXXXX BX: XXXXXXXXXXXXXXXX 15 87 0 15 87 0 Byte alto Byte bajo Byte alto Byte bajo

(mas significativo)(menos significativo)

El primer registro, es el de segmento (en este caso, segmento DS, de datos). El segundo registro es el de offset o desplazamiento. En este caso utilizamos el registro BX para direccionar dentro de el segmento. Podíamos haber utilizado también el registro SI, el DI, etc. A partir de estos dos registros, debemos acceder a una posición de memoria física dentro del Mbyte de que disponemos para el 8086.

Pongamos que el registro DS tiene el valor 0B800h (en hexadecimal) (podeis utilizar SB-CALCU de SAN BIT para hacer los cambios de base, y trabajar con bases diferentes a la decimal. También para la decimal, por supuesto). Y el registro BX contiene el valor 0037h.

Tenemos pues (en binario): DS: 1011100000000000 BX: 0000000000110111 Para obtener la dirección física de memoria, y teniendo en cuenta todo lo dicho relativo a segmentos, el microprocesador acturaría así: (Gráficamente)

Haría una suma de la siguiente forma:

DS: 1011100000000000 BX: + 0000000000110111 ---------------------------- 10111000000000110111

Obteniendo así la dirección de 20 bits necesaria para cubrir todo el Mbyte.

Si ese número (10111000000000110111) que está en binario, lo pasamos a hexadecimal, tenemos que la dirección física correspondiente a la anterior segmentada es: 0B8037h.

De todo lo anterior, se desprende que los segmentos empiezan siempre en direcciones divisibles por 16. Mas técnicamente: cada segmento comienza en una dirección de párrafo. Un párrafo son 16 bytes. Por supuesto nunca habrá un segmento que empiece en una dirección impar, por ejemplo. Como ejemplo: El primer segmento posible empieza en la dirección física 0. El segundo empieza en la dirección

Esto es mas complejo de lo que parece. Si tienes alguna duda, ya sabes...

Si le das vueltas a la idea, te darás cuenta que diferentes combinaciones de direcciones segmentadas dan una misma dirección física.

También se puede apreciar que los segmentos se pueden superponer unos a otros, pueden ser idénticos, o pueden encontrarse en partes totalmente lejanas en la memoria.

Si llegados a este punto no comprendes el tema de los segmentos, no sigas, ya que te perderías. Dime que no entiendes y lo explicaré mas detalladamente.

Es todo por ahora. Ah, y no sé si recibísteis la primera lección. Parece que todo estaba claro. Si en éste no os surge ninguna duda, ya me mosqueo }:-)))



Artículos relacionados


No hay comentarios: