|
|
1.1.- EL LENGUAJE DE MAQUINA Y EL LENGUAJE ENSAMBLADOR.
Todo
procesador, grande o pequeño, desde el de una calculadora hasta el de un
supercomputador, ya sea de propósito general o específico, posee un lenguaje
único que es capaz de reconocer y ejecutar. Por razones que resultan obvias,
este lenguaje ha sido denominado Lenguaje
de Máquina y más que ser propio de un computador pertenece a su
microprocesador. El lenguaje de máquina está compuesto
por una serie de instrucciones, que son
las únicas que pueden ser reconocidas y
ejecutadas por el microprocesador. Este lenguaje es un conjunto de números que representan las operaciones que realiza el
microprocesador a través de su circuitería interna. Estas instrucciones, por
decirlo así, están grabadas o "alambradas" en el hardware y no pueden
ser cambiadas. El nivel más bajo al que podemos aspirar a llegar en el control
de un microprocesador es precisamente el del lenguaje de máquina.
Ahora bien, siendo el lenguaje de máquina
un conjunto de números, ¿cómo es capaz el microprocesador de saber cuándo un
número representa una instrucción y cuándo un dato? El secreto de esto reside
en la dirección de inicio de un
programa y en el estado del
microprocesador. La dirección de inicio nos indica en qué localidad de
memoria comienza un programa, y en consecuencia que datos deberemos considerar como instrucciones. El estado del
microprocesador nos permite saber cuándo éste espera una instrucción y cuándo
éste espera un dato.
Obviamente, el lenguaje de máquina de un
microprocesador no puede ser ejecutado
por otro microprocesador de arquitectura distinta, a menos que haya cierto tipo de compatibilidad prevista.
Por ejemplo, un 80486 es capaz de ejecutar lenguaje de máquina propio y soporta
el código generado para microprocesadores anteriores de la misma serie (desde
un 8086 hasta un 80386). Por otra parte, un PowerPC es capaz de ejecutar
instrucciones de los microprocesadores Motorola 68xxx y de los Intel 80xx/80x86.
En ambos casos, el diseño de los microprocesadores se hizo tratando de mantener
cierto nivel de compatibilidad con los desarrollados anteriormente. En el
segundo caso, este nivel de compatibilidad se extendió a los de otra marca. Sin
embargo, un 8088 no puede ejecutar código de un 80186 o superiores, ya que los
procesadores más avanzados poseen juegos de instrucciones y registros nuevos no
contenidos por un 8088. Un caso similar es la serie 68xxx, pero de ninguna
manera podemos esperar que un Intel ejecute código de un Motorola y viceversa.
Y esto no tiene nada que ver con la compañía, ya que Intel desarrolla otros
tipos de microprocesadores como el 80860 y el iWARP, los cuales no pueden
compartir código ni entre ellos ni entre los 80xx/80xxx.
Ahora bien, mientras que con el lenguaje de
máquina, nosotros obtenemos un control total del microprocesador, la
programación en este lenguaje resulta muy difícil y fácil para cometer errores.
No tanto por el hecho de que las instrucciones son sólo números, sino porque se
debe calcular y trabajar con las direcciones de memoria de los datos, los
saltos y las direcciones de llamadas a subrutinas, además de que para poder
hacer ejecutable un programa, se deben enlazar las rutinas de run-time y servicios del sistema
operativo. Este proceso es al que se le denomina ensamblado de código. Para facilitar la elaboración de programas a
este nivel, se desarrollaron los Ensambladores
y el Lenguaje Ensamblador.
Existe una correspondencia 1 a 1 entre las
instrucciones del lenguaje de máquina y las del lenguaje ensamblador. Cada uno
de los valores numéricos del lenguaje de máquina tiene una representación
simbólica de 3 a 5 letras como instrucción del lenguaje ensamblador.
Adicionalmente, este lenguaje proporciona un conjunto de pseudo-operaciones (también
conocidas como directivas del
ensamblador) que sirven para definir datos, rutinas y todo tipo de información
para que el programa ejecutable sea creado de determinada forma y en
determinado lugar.
1.2.- INTERPRETES, COMPILADORES Y ENSAMBLADORES.
Aun
cuando el lenguaje ensamblador fue diseñado para hacer más fácil la
programación de bajo nivel, ésta resulta todavía complicada y muy laboriosa.
Por tal motivo se desarrollaron los lenguajes de alto nivel, para facilitar la
programación de los computadores, minimizando la cantidad de instrucciones a
especificar. Sin embargo, esto no quiere decir que el microprocesador ejecute
dichos lenguajes. Cada una de las instrucciones de un lenguaje de alto nivel o
de un nivel intermedio, equivalen a varias de lenguaje máquina o lenguaje
ensamblador.
La traducción de las instrucciones de nivel
superior a las de bajo nivel la realizan determinados programas. Por una parte
tenemos los interpretes, como DBase,
BASIC, APL, y Lisp. En estos, cada vez
que se encuentra una instrucción, se llama una determinada rutina de lenguaje
de máquina que se encarga de realizar las operaciones asociadas, pero en ningún
momento se genera un código objeto y mucho menos un código ejecutable.
Por otra parte, tenemos los compiladores, como los desarrollados para Fortran, Clipper, COBOL, Pascal o C,
que en vez de llamar y ejecutar una rutina en lenguaje de máquina, éstos juntan
esas rutinas para formar el código objeto que, después de enlazar las rutinas
de run-time y llamadas a otros programas y servicios del sistema operativo, se
transformará en el programa ejecutable.
Finalmente, tenemos los ensambladores— como los descritos en
este trabajo —que son como una versión
reducida y elemental de un compilador (pero que de ninguna manera deben
considerarse como tales), ya que lo único que tienen que hacer es cambiar toda
referencia simbólica por la dirección
correspondiente, calcular los saltos, resolver referencias y llamadas a otros
programas, y realizar el proceso de enlace. Los ensambladores son programas
destinados a realizar el ensamblado de un determinado código.
1.3.- EL PROCESO DE LIGA, LAS RUTINAS DE RUN-TIME Y LOS SERVICIOS DEL SISTEMA OPERATIVO.
Para
crear un programa ejecutable a partir de un código objeto se requiere que se
resuelvan las llamadas a otros programas y a los servicios del sistema
operativo, y agregar las rutinas o información de run-time para que el programa pueda ser cargado a memoria y ejecutado. Este proceso es lo que se conoce
como Link o proceso de liga, y se
realiza a través de un ligador o Linker
que toma de entrada el código objeto
y produce de salida el código ejecutable.
Las rutinas de run-time son necesarias,
puesto que el sistema operativo requiere tener control sobre el programa en
cualquier momento, además de que la asignación de recursos y su acceso
deben hacerse solamente a través
del sistema operativo. Para los computadores personales, esto no es tan
complejo como para otros computadores y sistemas operativos, pero es requerido.
2.- LA ARQUITECTURA DE LAS COMPUTADORAS PERSONALES IBM Y COMPATIBLES.
Los
computadores PC están compuestos físicamente por: monitor, teclado, CPU, floppy
drives, hard disk drives,
periféricos y componentes adicionales. Lógicamente están compuestos por el
BIOS (Basic Input-Output System) y el
sistema operativo MS-DOS (MicroSoft Disk Operating System).
El teclado se encuentra conectado a un
puerto de entrada destinado específicamente para tal propósito. Este tiene
asociado un área de memoria (buffer)
al cual llegan los códigos de la teclas
presionadas en el teclado.
La CPU está compuesta por la memoria RAM,
memoria ROM (donde reside el BIOS), los controladores de disco, la tarjeta de
video, los puertos de entrada-salida y el microprocesador. Los periféricos se
encuentran conectados y asociados por el sistema operativo con un puerto en
particular (una vez que el puerto ha sido abierto).
Las tarjetas controladores de floppy y disco duro se encargan de
intercambiar información entre memoria, procesador y unidades de disco. Para tal propósito se reservan en memoria
áreas específicas, para servir de buffers
en el intercambio de datos del computador con las unidades de disco.
El
monitor y la tarjeta de video están muy relacionados entre sí, ya que si bien
el monitor se encarga de desplegar la información, no es éste quien la
mantiene, sino la memoria designada
a la tarjeta de video. Esta
memoria es como cualquier otra, direccionable y modificable, y es en donde se
guarda la información que aparece en pantalla.
Debido al tipo de microprocesador empleado,
la memoria de la PC se encuentra dividida en una serie de blocks denominados segmentos, de 64KB cada uno. La memoria es
accedida especificando el segmento y el
desplazamiento dentro del segmento (segmento:desplazamiento, para mayor detalle ver el apéndice C). Para las PCs la memoria se clasifica en tres tipos:
-
Convencional.
Es la memoria de tipo básico y que abarca las direcciones de 0 a 640KB. En ésta
es donde se cargan los programas de usuario y el sistema operativo, y es la que
está disponible para equipo XT (8088,8086, 80186 y 80188).
-
Extendida. Esta memoria sólo está disponible para
procesadores 80286 y mayores (equipo AT, 80386 y 80486). Muchos programas que
usan la memoria convencional no pueden usar la memoria extendida porque las
direcciones en memoria extendida están más allá de las que el programa puede
reconocer. Únicamente las direcciones dentro de los 640KB pueden ser
reconocidas por todos los programas. Para reconocer la memoria extendida se
requiere de un manejador de memoria
extendida, como HIMEM.SYS que provee MS-DOS.
-
Expandida.
Esta es la memoria que se agrega al computador a través de una tarjeta de
expansión, y que debe ser administrada por un programa especial, como el
EMM386.EXE. A diferencia de la memoria convencional o extendida, la memoria
expandida es dividida en bloques de 16K llamados páginas (pages) . Cuando
un programa solicita información de
memoria expandida el manejador copia la página correspondiente en un área
denominada page frame para poder ser
accedida en la memoria extendida.
Como podremos ver, el 8088, 8086, 80188 y
80186 son capaces de direccionar hasta 1 MB de memoria. Ya hemos indicado que
la memoria convencional sólo abarca 640KB, así nos quedan 384KB libres. Esta
parte de la memoria es denominada parte
alta, y como no está disponible para muchos programas generalmente se usa
para cargar drivers del sistema
operativo, programas residentes y datos de hardware (ROM y páginas de video).
3.- LA ARQUITECTURA DE LOS MICROPROCESADORES INTEL.
Sin
importar de que microprocesador se trate,
los microprocesadores del 8088 al 80486 usan el modelo de registros del
8086. Los microprocesadores matemáticos 80287 al 80487 utilizan el modelo de registros expandidos del 8087. Para mayor detalle ver los apéndices A y B.
Los microprocesadores matemáticos están
diseñados exclusivamente para efectuar
operaciones aritméticas de una manera más rápida y precisa bajo el control de
otro procesador razón por la cual se
denominan coprocesadores. Estos
también poseen un juego de instrucciones de lenguaje de máquina propio.
La diferencia entre los diversos
microprocesadores de uso general y los coprocesadores reside en el nuevo
conjunto de instrucciones, registros y señalizadores agregados con cada nueva
liberación de un procesador superior. Estas adiciones se hicieron con el fin de
agregar un mayor poder de cómputo sin alterar la estructura básica, para así mantener la compatibilidad con los
desarrollos anteriores, tanto de software como de hardware.
La diferencia entre los 8086 y 8088
con los 80186 y 80188 no es muy grande, ésta radica en un grupo de
instrucciones que fueron agregadas al 80186 y al 80188. La diferencia entre el
8086 y el 8088, lo mismo que entre el 80186 y el 80188, es el modelo de memoria
que usan ambos procesadores. El 8088 y el 80188 están diseñados como
microprocesadores de 8 bits por lo que el modo de acceso a la memoria es
ligeramente distinto pero compatible con el 8086 y el 80186. Esto se verá con
más detalle en un tema posterior.
4.- EL SISTEMA OPERATIVO MS-DOS.
Junto con
todo lo visto, y como se mencionó anteriormente, uno de los componentes que caracterizan los
computadores personales es su sistema operativo. Una PC puede correr varios
sistemas operativos: CP/M, CP/M-86,
XENIX, Windows, PC-DOS, y
MS-DOS. Lo que los define es la forma en que están integrados sus servicios y
la forma en que se accede a ellos. Esto
es precisamente lo que el linker debe
enlazar y resolver.
Aquí nos enfocaremos exclusivamente en el
sistema operativo MS-DOS, y lo que se mencione aquí será valido para las
versiones 3.0 y superiores. Este
sistema operativo está organizado de la
siguiente manera:
Comandos
Internos (reconocidos
y ejecutados por el COMMAND.COM)
Comandos
Externos ( .EXEs y
.COMs )
Utilerías
y drivers
(programas de administración del sistema)
Shell
(Interfaz amigable, sólo versiones 4.0 o mayores)
Servicios
(Interrupciones)
Los servicios, más conocidos como interrupciones o vectores de interrupción, es parte medular de lo que es MS-DOS,
y no son mas que rutinas definidas por
MS-DOS y el BIOS, ubicadas a partir de una localidad de memoria específica. La
manera de acceder a estas rutinas y a los servicios que ofrecen es mediante
una instrucción que permite ejecutar
una interrupción.
MS-DOS proporciona dos módulos: IBMBIO.COM (provee una interfaz de bajo
nivel para el BIOS) e IBMDOS.COM (contiene un manejador de
archivos y servicios para manejo de registros). En equipos compatibles estos archivos
tienen los nombres IO.SYS y MSDOS.SYS, respectivamente. El acceso a
los servicios del computador se realiza de la siguiente manera:
|
Programa de usuario |
DOS |
DOS |
ROM |
EXTERNO |
||||
|
Petición de I/O |
‹—› | IBMDOS.COM | ‹—› | IBMBIO.COM | ‹—› | BIOS | ‹—› | Dispositivo |
5.- ENSAMBLADORES Y MACROENSAMBLADORES.
Existen
varios ensambladores disponibles para ambiente MS-DOS: el IBM Macro Assembler,
el Turbo Assembler de Borland, el Turbo Editassm de Speedware, por citar
algunos. Una breve descripción de cada uno se propociona a continuación.
Macro Ensamblador IBM.-
Está integrado por un ensamblador y un macroensamblador. En gran medida su
funcionamiento y forma de invocarlo es sumamente similar al de Microsoft. Su
forma de uso consiste en generar un archivo fuente en código ASCII, se procede
a generar un programa objeto que es ligado y se genera un programa .EXE.
Opcionalmente puede recurrirse a la utilería EXE2BIN de MS-DOS para
transformarlo a .COM. Es capaz de generar un listado con información del
proceso de ensamble y referencias cruzadas.
Macro Ensamblador de Microsoft.-
Dependiendo de la versión, este ensamblador es capaz de soportar el juego de instrucciones de distintos tipos
de microprocesadores Intel de la serie 80xx/80x86. En su versión 4.0 este
soporta desde el 8086 al 80286 y los coprocesadores 8087 y 80287. Requiere
128KB de memoria y sistema operativo MS-DOS v2.0 o superior. Trabaja con un
archivo de código fuente creado a partir de un editor y grabado en formato
ASCII. Este archivo es usado para el proceso de ensamble y generación de código
objeto. Posteriormente, y con un ligador, es creado el código ejecutable en
formato .EXE.
Turbo Editassm.-
Este es desarrollado por Speddware, Inc., y consiste de un ambiente integrado
que incluye un editor y utilerías para el proceso de ensamble y depuración. Es
capaz de realizar el ensamble línea a línea, conforme se introducen los
mnemónicos, y permite revisar listas de referencias cruzadas y contenido de los
registros. Este ensamblador trabaja con tablas en memoria, por lo que la
generación del código ejecutable no implica la invocación explícita del ligador
por parte del programador. Adicionalmente permite la generación de listados de
mensajes e información de cada etapa del proceso y la capacidad de creación de
archivos de código objeto.
Turbo Assembler.-
De Borland Intl., es muy superior al Turbo Editassm. Trabaja de la misma forma,
pero proporciona una interfaz mucho más fácil de usar y un mayor conjunto de
utilerías y servicios.
En lo que se refiere a las presentes notas,
nos enfocaremos al Microsoft Macro Assembler v4.0. Los programas ejemplo han
sido desarrollados con éste y está garantizado su funcionamiento. Estos mismo
programas posiblemente funcionen con otros ensambladores sin cambios o con
cambios mínimos cuando utilizan directivas o pseudoinstrucciones.
Realmente la diferencia entre los ensambladores radica en la forma de
generar el código y en las directivas con que cuente, aunque estas diferencias
son mínimas. El código ensamblador no cambia puesto que los microprocesadores
con los que se va a trabajar son comunes. Así,
todos los programas que se creen con un ensamblador en particular podrán
ser ensamblados en otro, cambiando las
pseudo-operaciones no reconocidas por el equivalente indicado en el manual de
referencia del paquete empleado.
Los programas que componen el Macro
Ensamblador Microsoft v4.0 son los siguientes:
Programa Descripción
MASM.EXE Microsoft
Macro Assembler
LINK.EXE Microsoft
8086 object linker
SYMDEB.EXE Microsoft Symbolic Debuger
Utility
MAPSYM.EXE Microsoft
Symbol File Generator
CREF.EXE Microsoft
Cross-Reference Utility
LIB.EXE Microsoft
Library Manager
MAKE.EXE Microsoft Program Maintenance Utility
EXEPACK.EXE Microsoft EXE File Compression
Utility
EXEMOD.EXE Microsoft
EXE File Header Utility
COUNT.ASM Sample
source file for SYMDEB session
README.DOC Updated
information obtained after the manual
was
printed.
El
Microsoft Macro Assembler v4.0 crea código ejecutable para procesadores 8086,
8088, 80186, 80188, 80286, 8087 y 80287. Además es capaz de aprovechar las
instrucciones del 80286 en la creación de código protegido y no protegido.
El término macroensamblador es usado para indicar que el ensamblador en
cuestión tiene la capacidad de poder ensamblar programas con facilidad de macro. Una macro es una
pseudo-instrucción que define un conjunto de instrucciones asociadas a un
nombre simbólico. Por cada ocurrencia en el código de esta macro, el
ensamblador se encarga de substituir esa llamada por todas las instrucciones
asociadas y, en caso de existir, se dejan
los parámetros con los que se estaba llamando la macro y no con los
que había sido definida. Es importante señalar que no se deja una llamada,
como a una subrutina o procedimiento,
sino que se incorporan todas las instrucciones que definen a la macro.
| Ultima actualización: . |