You are on page 1of 15

c 


    
 

c ! c"#!$%"c %&#'#! (#%

Los microprocesadores Intel  y  se desarrollan a partir de un


procesador anterior, el , que, en sus diversas encarnaciones -incluyendo el
Zilog )- ha sido la CPU de 8 bits de mayor éxito.

Poseen una arquitectura interna de 16 bits y pueden trabajar con operandos de


8 y 16 bits; una capacidad de direccionamiento de 20 bits (hasta 1 Mb) y
comparten el mismo juego de instrucciones.

La filosofía de diseño de la familia del 8086 se basa en la compatibilidad y la


creación de sistemas informáticos integrados, por lo que disponen de diversos
coprocesadores como el 8089 de E/S y el 8087, coprocesador matemático de
coma flotante. De acuerdo a esta filosofía y para permitir la compatibilidad con
los anteriores sistemas de 8 bits, el 8088 se diseñó con un bus de datos de 8 bits,
lo cual le hace más lento que su hermano el 8086, pues éste es capaz de cargar
una palabra ubicada en una dirección par en un solo ciclo de memoria mientras el
8088 debe realizar dos ciclos leyendo cada vez un byte.

Disponen de 92 tipos de instrucciones, que pueden ejecutar con hasta 7 modos


de direccionamiento. Tienen una capacidad de direccionamiento en puertos de
entrada y salida de hasta 64K (65536 puertos), por lo que las máquinas
construidas entorno a estos microprocesadores no suelen emplear la
entrada/salida por mapa de memoria, como veremos.

Entre esas instrucciones, las más rápidas se ejecutan en 2 ciclos teóricos de


reloj y unos 9 reales (se trata del movimiento de datos entre registros internos) y
las más lentas en 206 (división entera con signo del acumulador por una palabra
extraída de la memoria). Las frecuencias internas de reloj típicas son 4.77 MHz
en la versión 8086; 8 MHz en la versión 8086-2 y 10 MHz en la 8086-1.
Recuérdese que un MHz son un millón de ciclos de reloj, por lo que un
PC a  a 4,77 MHz puede ejecutar de 20.000 a unos 0,5 millones de
instrucciones por segundo, según la complejidad de las mismas (un 486 a 50
MHz, incluso sin memoria caché externa es capaz de ejecutar entre 1,8 y 30
millones de estas instrucciones por segundo).

El microprocesador Intel  se caracteriza por poseer dos modos de


funcionamiento completamente diferenciados: el    en el que se
encuentra nada más ser conectado a la corriente y el *  en el que
adquiere capacidad de proceso multitarea y almacenamiento en memoria virtual.
El proceso multitarea consiste en realizar varios procesos de manera
aparentemente simultánea, con la ayuda del sistema operativo para conmutar
automáticamente de uno a otro optimizando el uso de la CPU, ya que mientras un
proceso está esperando a que un periférico complete una operación, se puede
atender otro proceso diferente. La memoria virtual permite al ordenador usar más
memoria de la que realmente tiene, almacenando parte de ella en disco: de esta
manera, los programas creen tener a su disposición más memoria de la que
realmente existe; cuando acceden a una parte de la memoria lógica que no existe
físicamente, se produce una interrupción y el sistema operativo se encarga de
acceder al disco y traerla.

Cuando la CPU está en modo protegido, los programas de usuario tienen un


acceso limitado al juego de instrucciones; sólo el proceso a
 -
normalmente el sistema operativo- está capacitado para realizar ciertas tareas.
Esto es así para evitar que los programas de usuario puedan campar a sus anchas
y entrar en conflictos unos con otros, en materia de recursos como memoria o
periféricos. Además, de esta manera, aunque un error software provoque el
cuelgue de un proceso, los demás pueden seguir funcionando normalmente, y el
sistema operativo podría abortar el proceso  . Por desgracia, con el DOS
el 286 no está en modo protegido y el cuelgue de un solo proceso -bien el
programa principal o una rutina operada por interrupciones- significa la caída
inmediata de todo el sistema.

El 8086 no posee ningún mecanismo para apoyar la multitarea ni la memoria


virtual desde el procesador, por lo que es difícil diseñar un sistema multitarea
para el mismo y casi imposible conseguir que sea realmente operativo.
Obviamente, el 286 en modo protegido pierde absolutamente toda la
compatibilidad con los procesadores anteriores. Por ello, en este libro sólo
trataremos el modo real, único disponible bajo DOS, aunque veremos alguna
instrucción extra que también se puede emplear en modo real.

Las características generales del 286 son: tiene un bus de datos de 16 bits, un
bus de direcciones de 24 bits (16 Mb); posee 25 instrucciones más que el 8086 y
admite 8 modos de direccionamiento. En modo virtual permite direccionar hasta
1 Gigabyte. Las frecuencias de trabajo típicas son de 12 y 16 MHz, aunque
existen versiones a 20 y 25 MHz. Aquí, la instrucción más lenta es la misma que
en el caso del 8086, solo que emplea 29 ciclos de reloj en lugar de 206. Un 286
de categoría media (16 MHz) podría ejecutar más de medio millón de
instrucciones de estas en un segundo, casi 15 veces más que un 8086 medio a 8
MHz. Sin embargo, transfiriendo datos entre registros la diferencia de un
procesador a otro se reduce notablemente, aunque el 286 es más rápido y no sólo
gracias a los MHz adicionales.

Versiones mejoradas de los Intel 8086 y 8088 se encuentran también en los


procesadores '#c+ y '#c+ respectivamente. Ambos son compatibles
Hardware y Software, con la ventaja de que el procesado de instrucciones está
optimizado, llegando a superar casi en tres veces la velocidad de los originales en
algunas instrucciones aritméticas. También poseen una cola de prebúsqueda
mayor (cuando el microprocesador está ejecutando una instrucción, si no hace
uso de los buses externos, carga en una cola o o de unos pocos bytes las
posiciones posteriores a la que está procesando, de esta forma una vez que
concluye la instrucción en curso ya tiene internamente la que le sigue). Además,
los NEC V20 y V30 disponen de las mismas instrucciones adicionales del 286 en
modo real, al igual que el y el 

Por su parte, el  dispone de una arquitectura de registros de 32 bits, con un


bus de direcciones también de 32 bits (direcciona hasta 4 Gigabytes = 4096 Mb)
y más modos posibles de funcionamiento: el modo real (compatible 8086), el
modo protegido (relativamente compatible con el del 286), un modo protegido
propio que permite -¡por fin!- romper la barrera de los tradicionales segmentos y
el modo «virtual 86», en el que puede emular el funcionamiento simultáneo de
varios 8086. Una vez más, todos los modos son incompatibles entre sí y
requieren de un sistema operativo específico: si se puede perdonar al fabricante la
pérdida de compatibilidad del modo avanzados del 286 frente al 8086, debido a
la lógica evolución tecnológica, no se puede decir lo mismo del 386 respecto al
286: no hubiera sido necesario añadir un nuevo modo protegido si hubiera sido
mejor construido el del 286 apenas un par de años atrás. Normalmente, los 386
suelen operar en modo real (debido al DOS) por lo que no se aprovechan las
posibilidades multitarea ni de gestión de memoria. Por otra parte, aunque se
pueden emplear los registros de 32 bits en modo real, ello no suele hacerse -para
mantener la compatibilidad con procesadores anteriores- con lo que de entrada se
está tirando a la basura un 50% de la capacidad de proceso del chip, aunque por
fortuna estos procesadores suelen trabajar a frecuencias de 16/20 MHz
(obsoletas) y normalmente de 33 y hasta 40 MHz.
El , es una variante del 386 a nivel de hardware, aunque es compatible en
software. Básicamente, es un 386 con un bus de datos de sólo 16 bits -más lento,
al tener que dar dos pasadas para un dato de 32 bits-. De hecho, podría haber sido
diseñado perfectamente para mantener una compatibilidad hardware con el 286,
aunque el fabricante lo evitó probablemente por razones comerciales.

El  se diferencia del 386 en la integración en un solo chip del


coprocesador 387. También se ha mejorado la velocidad de operación: la versión
de 25 MHz dobla en términos reales a un 386 a 25 MHz equipado con el mismo
tamaño de memoria caché. La versión , no se diferencia en el tamaño del
bus, también de 32 bits, sino en la ausencia del 387 (que puede ser añadido
externamente). También existen versiones de 486 con buses de 16 bits, el primer
fabricante de estos chips, denominados 486SLC, ha sido Cyrix. Una tendencia
iniciada por el 486 fue la de duplicar la velocidad del reloj interno (pongamos por
caso de 33 a 66 MHz) aunque en las comunicaciones con los buses exteriores se
respeten los 33 MHz. Ello agiliza la ejecución de las instrucciones más largas:
bajo DOS, el rendimiento general del sistema se puede considerar prácticamente
el doble. Son los chips DX2 (también hay una variante a 50 MHz: 25 x 2). La
culminación de esta tecnología viene de la mano de los DX4 a 75/100 MHz
(25/33 x 3).

El  , último procesador de Intel en el momento de escribirse estas


líneas, se diferencia respecto al 486 en el bus de datos (ahora de 64 bits, lo que
agiliza los accesos a memoria) y en un elevadísimo nivel de optimización y
segmentación que le permite, empleando compiladores optimizados, simultanear
en muchos casos la ejecución de dos instrucciones consecutivas. Posee dos
cachés internas, tiene capacidad para predecir el destino de los saltos y la unidad
de coma flotante experimenta elevadas mejoras. Sin embargo, bajo DOS, un
Pentium básico sólo es unas 2 veces más rápido que un 486 a la misma
frecuencia de reloj. Comenzó en 60/90 MHz hasta los 166/200/233 MHz de las
últimas versiones (Pentium Pro y MMX), que junto a diversos clones de otros
fabricantes, mejoran aún más el rendimiento. Todos los equipos Pentium
emplean las técnicas DX, ya que las placas base típicas corren a 60 MHz. Para
hacerse una idea, por unas 200000 pts de 1997 un equipo Pentium MMX a 233
MHz es cerca de 2000 veces más rápido en aritmética entera que el IBM PC
original de inicios de la década de los 80; en coma flotante la diferencia aumenta
incluso algunos órdenes más de magnitud. Y a una fracción del coste (un millón
de pts de aquel entonces que equivale a unos 2,5 millones de hoy en día). Aunque
no hay que olvidar la revolución del resto de los componentes: 100 veces más
memoria (central y de vídeo), 200 veces más grande el disco duro... y que un
disco duro moderno  
aa datos 10 veces más deprisa que la a 
 de
aquel IBM PC original. Por desgracia, el software no ha mejorado el
rendimiento, ni remotamente, en esa proporción: es la factura pasada por las
técnicas de programación cada vez a un nivel más alto (aunque nadie discute sus
ventajas).

Una característica de los microprocesadores a partir del 386 es la


disponibilidad de a 
    de alta velocidad de acceso -muy pocos
nanosegundos- que almacenan una pequeña porción de la memoria principal.
Cuando la CPU accede a una posición de memoria, cierta circuitería de control se
encarga de ir depositando el contenido de esa posición y el de las posiciones
inmediatamente consecutivas en la memoria caché. Cuando sea necesario acceder
a la instrucción siguiente del programa, ésta ya se encuentra en la caché y el
acceso es muy rápido. Lo ideal sería que toda la memoria del equipo fuera caché,
pero esto no es todavía posible actualmente. Una caché de tamaño razonable
puede doblar la velocidad efectiva de proceso de la CPU. El 8088 carecía de
memoria caché, pero sí estaba equipado con una unidad de lectura adelantada de
instrucciones con una cola de prebúsqueda de 4 bytes: de esta manera, se
agilizaba ya un tanto la velocidad de proceso al poder ejecutar una instrucción al
mismo tiempo que iba leyendo la siguiente.

!#&%"!-%.#(/.#(

Estos procesadores disponen de 14 registros de 16 bits (el 286 alguno más,


pero no se suele emplear bajo DOS). La misión de estos registros es almacenar
las posiciones de memoria que van a experimentar repetidas manipulaciones, ya
que los accesos a memoria son mucho más lentos que los accesos a los registros.
Además, hay ciertas operaciones que sólo se pueden realizar sobre los registros.
No todos los registros sirven para almacenar datos, algunos están especializados
en apuntar a las direcciones de memoria. La mecánica básica de funcionamiento
de un programa consiste en cargar los registros con datos de la memoria o de un
puerto de E/S, procesar los datos y devolver el resultado a la memoria o a otro
puerto de E/S. Obviamente, si un dato sólo va a experimentar un cambio, es
preferible realizar la operación directamente sobre la memoria, si ello es posible.
A continuación se describen los registros del 8086.

AX SP CS IP
BX BP DS flags
CX SI SS
DX DI ES
Registros Registros Registros Registro puntero
de datos punteros de de de instrucciones
pila e segmento y flags
índices

!*   

AX, BX, CX, DX: pueden utilizarse bien como registros de 16 bits o como
dos registros separados de 8 bits (byte superior e inferior) cambiando la X por H
o L según queramos referirnos a la parte alta o baja respectivamente. Por
ejemplo, AX se descompone en AH (parte alta) y AL (parte baja).
Evidentemente, ¡cualquier cambio sobre AH o AL altera AX!: valga como
ejemplo que al incrementar AH se le están añadiendo 256 unidades a AX.

AX = Acumulador.

Es el registro principal, es utilizado en las instrucciones de multiplicación y


división y en algunas instrucciones aritméticas especializadas, así como en
ciertas operaciones de carácter específico como entrada, salida y traducción.
Obsérvese que el 8086 es suficientemente potente para realizar las operaciones
lógicas, la suma y la resta sobre cualquier registro de datos, no necesariamente el
acumulador.

BX = Base.
Se usa como registro base para referenciar direcciones de memoria con
direccionamiento indirecto, manteniendo la dirección de la base o comienzo de
tablas o matrices. De esta manera, no es preciso indicar una posición de memoria
fija, sino la a  (así, haciendo avanzar de unidad en unidad a BX, por
ejemplo, se puede ir accediendo a un gran bloque de memoria en un bucle).

CX = Contador.
Se utiliza comúnmente como contador en bucles y operaciones repetitivas
de manejo de cadenas. En las instrucciones de desplazamiento y rotación se
utiliza como contador de 8 bits.

DX = Datos.
Usado en conjunción con AX en las operaciones de multiplicación y
división que involucran o generan datos de 32 bits. En las de entrada y salida se
emplea para especificar la dirección del puerto E/S.

!*  *
Definen áreas de 64 Kb dentro del espacio de direcciones de 1 Mb del
8086. Estas áreas pueden solaparse total o parcialmente. No es posible acceder a
una posición de memoria no definida por algún segmento: si es preciso, habrá de
moverse alguno.

CS = Registro de segmento de código (code segment).


Contiene la dirección del segmento con las instrucciones del programa.
Los programas de más de 64 Kb requieren cambiar CS periódicamente.

DS = Registro de segmento de datos (data segment).


Segmento del área de datos del programa.

SS = Registro de segmento de pila (stack segment).


Segmento de pila.

ES = Registro de segmento extra (extra segment).


Segmento de ampliación para zona de datos. Es extraordinariamente útil
actuando en conjunción con DS: con ambos se puede definir dos zonas de 64 Kb,
tan alejadas como se desee en el espacio de direcciones, entre las que se pueden
intercambiar datos.

!*    

SP = Puntero de pila (stack pointer).


Apunta a la cabeza de la pila. Utilizado en las instrucciones de manejo de
la pila.

BP = Puntero base (base pointer).


Es un puntero de base, que apunta a una zona dentro de la pila dedicada al
almacenamiento de datos (variables locales y parámetros de las funciones en los
programas compilados).

- !*   :

SI = Índice fuente (source index).


Utilizado como registro de índice en ciertos modos de direccionamiento
indirecto, también se emplea para guardar un valor de desplazamiento en
operaciones de cadenas.
DI = Índice destino (destination index).
Se usa en determinados modos de direccionamiento indirecto y para
almacenar un desplazamiento en operaciones con cadenas.

-      o contador de programa:

IP = Puntero de instrucción (instruction pointer).


Marca el desplazamiento de la instrucción en curso dentro del segmento
de código. Es automáticamente modificado con la lectura de una instrucción.

- !*    o de indicadores (0 *).

Es un registro de 16 bits de los cuales 9 son utilizados para indicar diversas


situaciones durante la ejecución de un programa. Los bits 0, 2, 4, 6, 7 y 11 son
indicadores de condición, que reflejan los resultados de operaciones del
programa; los bits del 8 al 10 son indicadores de control y el resto no se utilizan.
Estos indicadores pueden ser comprobados por las instrucciones de salto
condicional, lo que permite variar el flujo secuencial del programa según el
resultado de las operaciones.

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF

i? CF (Carry Flag): Indicador de acarreo. Su valor más habitual es a 


a  en una suma o resta.
i? OF (Overflow Flag): Indicador de desbordamiento. Indica que el resultado
de una operación no cabe en el tamaño del operando destino.
i? ZF (Zero Flag): Indicador de resultado 0 o comparación igual.
i? SF (Sign Flag): Indicador de resultado o comparación negativa.
i? PF (Parity Flag): Indicador de paridad. Se activa tras algunas operaciones
aritmético-lógicas para indicar que el número de bits a uno resultante es
par.
i? AF (Auxiliary Flag): Para ajuste en operaciones BCD.
i? DF (Direction Flag): Indicador de dirección. Manipulando bloques de
memoria, indica el sentido de avance (ascendente/descendente).
i? IF (Interrupt Flag): Indicador de interrupciones: puesto a 1 están
permitidas.
i? TF (Trap Flag): Indicador de atrape (ejecución paso a paso).
!#&%"!-%.#(/!-c#% .-!#%%1#!-!#%

Los 386 y superiores disponen de muchos más registros de los que vamos a
ver ahora. Sin embargo, bajo el sistema operativo DOS sólo se suelen emplear
los que veremos, que constituyen básicamente una extensión a 32 bits de los
registros originales del 8086.

Se amplía el tamaño de los registros de datos (que pueden ser accedidos en


fragmentos de 8, 16 ó 32 bits) y se añaden dos nuevos registros de segmento
multipropósito (FS y GS). Algunos de los registros aquí mostrados son realmente
de 32 bits (como EIP en vez de IP), pero bajo sistema operativo DOS no pueden
ser empleados de manera directa, por lo que no les consideraremos.


-.-%.#.!#cc-'
#'"-

Son los distintos modos de acceder a los datos en memoria por parte del
procesador. Antes de ver los modos de direccionamiento, echaremos un vistazo a
la sintaxis general de las instrucciones, ya que pondremos alguna en los
ejemplos:
???????????????  ?????  
?   ?

Donde destino indica dónde se deja el resultado de la operación en la que


pueden participar (según casos) FUENTE e incluso el propio DESTINO. Hay
instrucciones, sin embargo, que sólo tienen un operando, como la siguiente, e
incluso ninguno:
???????????????  ?????  
?
Como ejemplos, aunque no hemos visto aún las instrucciones utilizaremos un
par de ellas: la de copia o movimiento de datos (MOV) y la de suma (ADD).

3.4.1. - ORGANIZACIÓN DE DIRECCIONES: SEGMENTACIÓN.

Como ya sabemos, los microprocesadores 8086 y compatibles poseen


registros de un tamaño máximo de 16 bits que direccionarían hasta 64K; en
cambio, la dirección se compone de 20 bits con capacidad para 1Mb, hay por
tanto que recurrir a algún artificio para direccionar toda la memoria. Dicho
artificio consiste en la aa
: se trata de dividir la memoria en grupos de
64K. Cada grupo se asocia con un registro de segmento; el desplazamiento
(  a) dentro de ese segmento lo proporciona otro registro de 16 bits. La
dirección absoluta se calcula multiplicando por 16 el valor del registro de
segmento y sumando el offset, obteniéndose una dirección efectiva de 20 bits.
Esto equivale a concebir el mecanismo de generación de la dirección absoluta,
como si se tratase de que los registros de segmento tuvieran 4 bits a 0
(imaginarios) a la derecha antes de sumarles el desplazamiento:
Ô  ? ? ????

?

En la práctica, una dirección se indica con la notación SEGMENTO:OFFSET;


además, una misma dirección puede expresarse de más de una manera: por
ejemplo, 3D00h:0300h es equivalente a 3D30:0000h. Es importante resaltar que
no se puede acceder a más de 64 Kb en un segmento de datos. Por ello, en los
procesadores 386 y superiores no se deben emplear registros de 32 bit para
generar direcciones (bajo DOS), aunque para los cálculos pueden ser interesantes
(no obstante, sí sería posible configurar estos procesadores para poder
direccionar más memoria bajo DOS con los registros de 32 bits, aunque no
resulta por lo general práctico).

3.4.2. - MODOS DE DIRECCIONAMIENTO.

- Direccionamiento inmediato: El operando es una constante situada detrás del


código de la instrucción. Sin embargo, como registro destino no se puede indicar
uno de segmento (habrá que utilizar uno de datos como paso intermedio).
???????????????????? 

El número hexadecimal 0fffh es la constante numérica que en el


direccionamiento inmediato se le sumará al registro AX. Al trabajar con
ensambladores, se pueden definir símbolos   (ojo, no variables) y es
más intuitivo:
??????????Ô  ??? ???

?????????????  ?  ?


?????????????????
??? Ô  ?

Si se referencia a la dirección de memoria de una variable de la siguiente


forma, también se trata de un caso de direccionamiento inmediato:
????????Ô  ?????????

?????????????  ?? ?  ?


?????????????????
????
 ?Ô  ????? ?Ô  ?Ô?  ?Ô?
Ô ?

Porque hay que tener en cuenta que cuando traduzcamos a números el


símbolo podría quedar:
????? !??????? ?
?????????????????
??? ?

- Direccionamiento de registro: Los operandos, necesariamente de igual


tamaño, están contenidos en los registros indicados en la instrucción:
?????????????????
??? ?
?????????????????
???" #?

- Direccionamiento directo o absoluto: El operando está situado en la


dirección indicada en la instrucción, relativa al segmento que se trate:
?????????????????
??? $% ?
?????????????????
??? !$&'( ?

Esta sintaxis (quitando la 'h' de hexadecimal) sería la que admite el programa


DEBUG (realmente habría que poner, en el segundo caso, ES: en una línea y el
MOV en otra). Al trabajar con ensambladores, las h 2 en memoria se
pueden referenciar con etiquetas simbólicas:
?????????????????
??? Ô  ?
?????????????????
??? !Ô  ?
?
?????????Ô  ????????' & ????????  ?Ô?)   ?

En el primer ejemplo se transfiere a AX el valor contenido en la


dirección apuntada por la etiqueta  sobre el segmento de datos (DS) que se
asume por defecto; en el segundo ejemplo se indica de forma explícita el
segmento tratándose del segmento ES. La dirección efectiva se calcula de la
forma ya vista con anterioridad: Registro de
segmento * 16 + desplazamiento_de_dato (este desplazamiento depende de la
posición al ensamblar el programa).

- Direccionamiento indirecto: El operando se encuentra en una dirección


señalada por un registro de segmento*16 más un registro base (BX/BP) o índice
(SI/DI). (Nota: BP actúa por defecto con SS).
????????
??? $*+????????????? ?$*+?
????????
??? !$  ?????????$  ? ??

- Indirecto con índice o indexado: El operando se encuentra en una dirección


determinada por la suma de un registro de segmento*16, un registro de índice, SI
o DI y un desplazamiento de 8 ó 16 bits. Ejemplos:
????????
??? $  +????????????
??? Ô)$ ?
???????????$  + *???????????????Ô)$  *?

- Indirecto con base e índice o indexado a base: El operando se encuentra en


una dirección especificada por la suma de un registro de segmento*16, uno de
base, uno de índice y opcionalmente un desplazamiento de 8 ó 16 bits:
????????
??? !$*  +????????????
??? !Ô)$*$ ?
????????
???!$*  + ????????????
???!Ô)$*$  ?

c2   *  * 3  

Como se ve en los modos de direccionamiento, hay casos en los que se indica


explícitamente el registro de segmento a usar para acceder a los datos. Existen
unos segmentos asociados por defecto a los registros de desplazamiento (IP, SP,
BP, BX, DI, SI); sólo es necesario declarar el segmento cuando no coincide con
el asignado por defecto. En ese caso, el ensamblador genera un byte adicional (a
modo de prefijo) para indicar cuál es el segmento referenciado. La siguiente tabla
relaciona las posibles combinaciones de los registros de segmento y los de
desplazamiento:

CS SS DS ES
IP Sí No No No
SP No Sí No No
BP con prefijo por defecto con prefijo con prefijo
BX con prefijo con prefijo por defecto con prefijo
SI con prefijo con prefijo por defecto con prefijo
DI con prefijo con prefijo por defecto con prefijo(1)
(1) También por defecto en el manejo de cadenas.

Los 386 y superiores admiten otros modos de direccionamiento más


sofisticados, que se verán en el próximo capítulo, después de conocer todas las
instrucciones del 8086. Por ahora, con todos estos modos se puede considerar que
hay más que suficiente. De hecho, algunos se utilizan en muy contadas
ocasiones.

4( ( 

La pila es un bloque de memoria de estructura (5- (Last Input First Output:


último en entrar, primero en salir) que se direcciona mediante desplazamientos
desde el registro SS (segmento de pila). Las posiciones individuales dentro de la
pila se calculan sumando al contenido del segmento de pila SS un
desplazamiento contenido en el registro puntero de pila SP. Todos los datos que
se almacenan en la pila son de longitud palabra, y cada vez que se introduce algo
en ella por medio de las instrucciones de manejo de pila (PUSH y POP), el
puntero se decrementa en dos; es decir, la pila avanza hacia direcciones
decrecientes. El registro BP suele utilizarse normalmente para apuntar a una
cierta posición de la pila y acceder indexadamente a sus elementos -generalmente
en el caso de variables- sin necesidad de desapilarlos para consultarlos.

La pila es utilizada frecuentemente al principio de una subrutina para


preservar los registros que no se desean modificar; al final de la subrutina basta
con recuperarlos en orden inverso al que fueron depositados. En estas
operaciones conviene tener cuidado, ya que la pila en los 8086 es común al
procesador y al usuario, por lo que se almacenan en ella también las direcciones
de retorno de las subrutinas. Esta última es, de hecho, la más importante de sus
funciones. La estructura de pila permite que unas subrutinas llamen a otras que a
su vez pueden llamar a otras y así sucesivamente: en la pila se almacenan las
direcciones de retorno, que serán las de la siguiente instrucción que provocó la
llamada a la subrutina. Así, al retornar de la subrutina se extrae de la pila la
dirección a donde volver. Los compiladores de los lenguajes de alto nivel la
emplean también para pasar los parámetros de los procedimientos y para generar
en ella las variables  
 -variables locales que existen durante la
ejecución del subprograma y se destruyen inmediatamente después-. Por ello, una
norma básica es que se debe desapilar siempre todo lo apilado para evitar una
pérdida de control inmediata del ordenador.

Ejemplo de operación sobre la pila (todos los datos son arbitrarios):


1'!-&!
.##6#
(-

Aunque las instrucciones del procesador no serán vistas hasta el próximo


capítulo, con objeto de ayudar a la imaginación del lector elaboraremos un
primer programa de ejemplo en lenguaje ensamblador. La utilidad de este
programa es dejar patente que lo único que entiende el 8086 son números,
aunque nosotros nos referiremos a ellos con unos símbolos que faciliten
entenderlos. También es interesante este ejemplo para afianzar el concepto de
registro de segmento.

En este programa sólo vamos a emplear las instrucciones MOV, ya conocida,


y alguna otra más como la instrucción INC (incrementar), DEC (disminuir una
unidad) y JNZ (saltar si el resultado no es cero). Suponemos que el programa está
ubicado a partir de la dirección de memoria 14D3:7A10 (arbitrariamente elegida)
y que lo que pretendemos hacer con él es limpiar la pantalla. Como el ordenador
es un PC con monitor en color, la pantalla de texto comienza en B800:0000 (no
es más que una zona de memoria). Por cada carácter que hay en dicha pantalla,
comenzando arriba a la izquierda, a partir de la dirección B800:0000 tenemos dos
bytes: el primero, con el código ASCII del carácter y el segundo con el color. Lo
que vamos a hacer es rellenar los 2000 caracteres (80 columnas x 25 líneas) con
espacios en blanco (código ASCII 32, ó 20h en hexadecimal), sin modificar el
color que hubiera antes. Esto es, se trata de poner el valor 32 en la dirección
B800:0000, la B800:0002, la B800:0004... y así sucesivamente.

El programa quedaría en memoria de esta manera: La primera columna indica


la dirección de memoria donde está el programa que se ejecuta (CS=14D3h e
IP=7A10h al principio). La segunda columna constituye el código máquina que
interpreta el 8086. Algunas instrucciones ocupan un byte de memoria, otras dos
ó tres (las hay de más). La tercera columna contiene el nombre de las
instrucciones, algo mucho más legible para los humanos que los números:
& !???*(??????
?? "????????????? ? ?,'?Ô  ?
??  Ô  ?
& ! ???*-??*-????
?? *- ?????????? ?Ô? ?  ?
Ô?)   ?
& !???- ?-???????
?? ?????????????? ) ? ?Ô?
Ô  ? ? ?  ?
& !-???**??????
??* ??????????????? ) ? ?) ?
 ? ?Ô? ?)   ?
& !*?????'????
??*. ?+?$* '????) ?*. ?+?)  ?
Ô  ?/? '??Ô?-? ?
& ! ???& ?????????? ??*?????????????????* *?01? ) ? ?
?Ô?  ?
& ! ???& ?????????? ??*?????????????????* *?01? ) ? ?
  ? ? ?
& !'???&(?????????? ??????????????????? 0?01?/Ô ??
 ? ?
& !'???%? -???????23??0-????????????????? ?? ?? ?  ?-?
? ?, ?& !*?

Como se puede ver, la segunda instrucción (bytes de código máquina 0B8h, 0 y 0B8h
colocados en posiciones consecutivas) está colocada a partir del desplazamiento 7A13h, ya
que la anterior que ocupaba 3 bytes comenzaba en 7A10h. En el ejemplo cargamos el valor
0B800h en DS apoyándonos en AX como intermediario. El motivo es que los registros de
segmento no admiten el direccionamiento inmediato. A medida que se van haciendo
programas, el ensamblador da mensajes de error cuando se encuentra con estos fallos y
permite ir aprendiendo con facilidad las normas, que tampoco son demasiadas. La
instrucción „ !" equivale a decir: # aa 
a
aa 

 (DS:[BX] para ser más exactos)a $%aa  !"&. El valor 0F8h del
código máquina de la última instrucción es el complemento a dos (número negativo) del
valor 8.

Normalmente, casi nunca habrá que ensamblar  consultando unas tablas, como
hemos hecho en este ejemplo. Sin embargo, la mejor manera de aprender ensamblador es
no olvidando la estrecha relación de cada línea de programa con la CPU y la memoria.

You might also like