Make your own free website on Tripod.com

 

 

 

 

 

I.- Definiciones y conceptos preliminares.

                          1.- Conceptos básicos.

                                               1.1.- El lenguaje de maquina y el lenguaje ensamblador.

                                               1.2.- Interpretes, compiladores y ensambladores.

                                               1.3.- El proceso de link, las rutinas de Run-time

                            2.- La arquitectura   de   las   computadoras   personales   IBM   y compatibles

                            3.- La arquitectura de los microprocesadores Intel.

                            4.- El sistema operativo MS-DOS.

                            5.- Ensambladores y Macroensambladores.

                            6. Sistemas numéricos

                            7. Operaciones con bytes

7.1. AND

7.2. OR

7.3. XOR

7.4. NOT

                            8. El juego de registros

 

I.- DEFINICIONES Y CONCEPTOS PRELIMINARES.

 1.- CONCEPTOS BASICOS.

 

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  (tambien 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.

 Aún cuando el lenguaje ensamblador fue diseñado para hacer más fácil la programación de bajo nivel, esta 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 require que se resulevan 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 sólamente  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 éspecificas, 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 accesada especificando el segmento  y el desplazamiento dentro del segmento (segmento:desplazamiento, para mayor detalle ver el apendice C). Para las PC´s 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 alla 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 accesada en la memoria extendida.

Como se puede observar, 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 anteriormente, y como se mencionó anteriomente,  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 accesa 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                     DOS                             DOS                           ROM                 EXTERNO

de usuario                Alto nivel                        Bajo nivel

petición de     ‹—›    IBMDOS.COM    ‹—›    IBMBIO.COM      ‹—›     BIOS     ‹—›   Dispositivo I/O

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 recurirse 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

SYMDEB.EXE

Microsoft Symbolic Debuger Utility

LINK.EXE

Microsoft 8086 object linker

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.1

 6. SISTEMAS NUMÉRICOS

 Comencemos por los sistemas de numeración que más utilizaremos al programar. El básico va a ser el sistema hexadecimal, aunque debemos de explicar antes el binario, el sistema de numeración que utiliza el ordenador. Los números que conocemos están escritos en base 10. Esto significa que tenemos, desde el 0 hasta el 9, diez símbolos para representar cada cifra. Es decir, cada cifra ir  de 0 a 9, y al superar el valor "9", cambiar  a 0 y sumar  uno a su cifra de la izquierda:  9+1: 10.

 El sistema binario utiliza tan sólo dos símbolos, el "0" y el "1". Imaginemos que tenemos el número binario "0". Al sumarle una unidad, este número binario cambiar  a "1". Sin embargo, si volvemos a añadirle otra unidad, a este número en formato binario ser  el "10" ( aumenta la cifra a la izquierda, que era 0, y la anterior toma el valor mínimo ).

 Sumemos ahora otra unidad: el aspecto del número ser  "11" ( tres en  decimal ). Y podríamos seguir:

 Binario  : 0 ; 1 ; 10 ; 11 ; 100 ; 101 ; 110; 111 ; 1000 ; 1001 ; 1010,...

Decimal : 0  1    2     3      4       5       6      7         8        9        10

Esto nos permite establecer un sistema bastante sencillo de conversión del binario al decimal;He aquí los valores siendo n el valor de la cifra:

    Cifra menos significativa:

           n*2^0 =    1 si n=1 o 0 si n=0

    Segunda cifra:

           n*2^1 =    2 si n=1 o 0 si n=0

    Tercera cifra:

           n*2^2 =    4 si n=1 o 0 si n=0

    Cuarta cifra:

           n*2^3 =    8 si n=1 o 0 si n=0

    Etc,...

 Y así continuaríamos, aumentando el número al que se eleva 2. Traduzcamos entonces el número binario '10110111'

 2^7+ 0 +2^5+2^4+ 0 +2^2+2^1+2^0  = 128 + 0 + 32 + 16 + 4 + 2 + 1 = 183

 1     0     1      1      0     1     1     1

 De todos modos, esta transformación se le ha puesto simplemente para que se comprenda con mas claridad cómo funcionan los números binarios. Es mucho más aconsejable el uso de una calculadora científica que permita realizar conversiones entre decimales, hexadecimales y binarios. Se hace su uso ya casi imprescindible al programar. La razón del uso de los números binarios es sencilla. Es lo que entiende el ordenador, ya que interpreta diferencias de voltaje como activado ( 1 ) o desactivado ( 0 ), aunque no detallaré esto. Cada byte de información está compuesto por ocho dígitos binarios, y a cada cifra se le llama bit. El número utilizado en el ejemplo, el 10110111, sería un byte, y cada una de sus ocho cifras, un bit. Y a partir de ahora, cuando escriba un número binario, lo haré con la notación usual, con una "b" al final del número ( ej: 10010101b )

 La conversión a hexadecimal es muy utilizada en ensamblador, se trata de un sistema de numeración en base dieciseis. Por tanto, hay dieciseis símbolos para cada cifra, y en vez de inventarse para ello nuevos símbolos, se decidió adoptar las primeras letras del abecedario. Por lo tanto, tendremos ahora:

   Hex   Dec

     1 -->  1

2 -->  2

3 -->  3

4 -->  4

5 -->  5

6 -->  6

7 -->  7

8 -->  8

9 -->  9

A --> 10

B --> 11

C --> 12

D --> 13

E --> 14

F --> 15

10 --> 16

11 --> 17

 Etc,...

 Como vemos, este sistema nos planteas bastantes problemas para la conversión, una calculadora científica será casi imprescindible para realizar esta operación.

 Por que utilizar este sistema ? Bien sencillo. Volvamos al byte, y traduzcamos su valor más alto, "11111111". Resulta ser 256. Ahora pasemos esta cifra al sistema hexadecimal, y nos resultará "FF". Obtenemos un número más comprensible que el binario ( difícil de recordar ), y ante todo mucho más compacto, en el que dos cifras nos representaran cada byte. Podremos además traducir fácilmente el binario a hexadecimal con esta tabla; cada cuatro cifras binarias pueden traducirse al hexadecimal:

Binario

Hexadecimal

0000

0

0001

1

0010

2

0011

3

0100

4

0101

5

0110

6

0111

7

1000

8

1001

9

1010

A

1011

B

1100

C

1101

D

1110

E

1111

F

Por ejemplo, el número binario:

            1111 0011 1010 1110

En hexadecimal sería:

            1111  0011  1010  1110

               F        3        A       E

Para referirnos a un número hexadecimal sin especificarlo, usaremos la notación que se suele usar al programar, con un 0 al principio ( necesario cuando hay letras ) y una h al final, por ejemplo, el número anterior sería 0F3AEh

7. OPERACIONES CON BYTES

 Hay cuatro operaciones básicas que se pueden realizar con un número binario, y coinciden con operaciones de la lógica matemática, con lo que cualquiera que la haya estudiado tendrá cierta ventaja para entenderla. Para explicarlas, llamar‚ al valor 0 resultado "falso", y al valor 1 "verdadero". Las operaciones son AND, OR, XOR y NOT

 7.1. AND

               Es un 'y' lógico. Se realiza entre dos cifras binarias confrontando cada cifra con su correspondiente, y el resultado será  "1" si las dos son verdaderas ( si las dos valen "1" ), y "0" ( falso ) en el resto de los casos.

 

AND

 

1er. numero

2do. numero

Resultado

1

1

1

1

0

0

0

1

0

0

0

0

 Vuelvo a la lógica para explicarlo más claramente: Imaginemos la frase: "El hombre es un mamífero y camina erguido". El hecho de que el hombre sea un mamífero es cierto ( 1 ), y el de que camine erguido, otro ( 1 ). Por lo tanto, al unirlos mediante una conjunción ( 'y' o 'AND' ), resulta que ya que se dan las dos, la oración es verdadera.

 Pongamos un ejemplo más complejos, queremos realizar un AND lógico entre dos bytes:

                11011000 AND 01101001

 Observemos lo que sucede:

      11011000                                        216                           AND                      01101001     

En sistema decimal sería:  AND    105  (aunque en sistema decimal 01001000 es mas valioso)  72

 Cuando coinciden dos valores de "verdad", el resultado es "verdad", si uno es falso, el resultado es "falso" ( no es verdad que "El hombre es un mamífero y respira debajo del agua" ), y si los dos son falsos, el resultado es falso ( no es cierto que "El hombre es un ave y respira debajo del agua" )

7.2. OR

             El "o" lógico. El resultado es "verdadero" cuando al menos uno de los factores es verdadero. O sea, es "1" cuando al menos uno de los dos factores es "1". Sería como la frase "Voy a buscar el peine o la caja de bombones", donde que uno sea cierto no significa que el otro no lo sea; es cierta la frase, es verdadera mientras uno de los térrminos sean verdaderos.

 Operemos con los números "10100110" y "01101100":

 10100110                     OR                   01101100          --------    11101110

 Como hemos visto, el valor 1 ( verdadero ) queda en las cifras de las que, confrontadas, al menos una es verdadera. Sólo resulta 0 ( falso ) si los dos números enfrentados son 0 ( falsos ).

 7.3. XOR

             "Or" exclusivo. Se trata de una orden parecida al OR, tan sólo que la verdad de una excluye la de la otra. El resultado, por tanto, es "1" ( verdad ) cuando uno y sólo uno de los dos números es verdadero ( y el otro falso, claro ). Sería como la oración "O vivo o estoy muerto", para que sea cierta se tiene que dar una de las dos, pero nunca las dos o ninguna.

 10111001                     XOR     01011101       --------      11100100

 La orden XOR va a ser bastante útil en encriptación, pero eso ya es otra historia,...

 7.4. NOT

             Esto se aplica sobre un sólo número, y en términos de lógica sería la negación de una oración, o sea, si el número al que se aplica es 1 el resultado es 0, y viceversa. En términos de lógica matemática aplicándolo a una oración, sería por ejemplo, " No es verdad que tenga ganas de estudiar y de no beber ", negando las otras dos que en caso contrario serían verdad:

 NOT  11100110                        --------                00011001

 Bytes, bits y demás

             Tan sólo, por si alguien no lo conoce, se quiere detallar el modo de almacenamiento del ordenador, incluyendo lo más temido por el iniciado en Ensamblador, y más engorroso para el programador, segments y offsets.

 La unidad mínima de información es el bit. Su estado, como vimos anteriormente, puede ser 1 o 0. Un conjunto de ocho bits, forman un byte. De ellos, el de la derecha es el menos significativo ( su valor es menor ), y el de m s a la izquierda el más significativo. Un Kbyte es un conjunto de 1024 ( que no 1000 ) bytes. Igualmente, un MegaByte serán 1024 kbytes, o 1024*1024=1048576 bytes. Otro térrmino que se utilizará a menudo, es palabra, o "word". Una "palabra", es un conjunto de dos bytes, y se utiliza por que a menudo se opera con ellas en lugar de bytes.

 Segments y offsets:

 Hubo un tiempo, cuando los dinosaurios dominaban la tierra, en el que a "alguien" se le ocurrió que con 640K debería de bastarnos para hacerlo todo. Y por aquí comienzan los problemas: El ancho de bus de direcciones, para localizar un punto en memoria, es de 20 bits. Por lo tanto, el número máximo de direcciones de memoria a las que podremos acceder ser  1 Mb. Pero como veremos, 20 bits no son ni 2 bytes ni 3, sino así como 2 y medio %-). El problema es ordenarlos para que el procesador conozca la dirección de memoria, y aquí llegan las cosillas,.. se necesitará para conocer una posición de memoria de cuatro bytes combinados de una curiosa manera.

 Hay que imaginar los dos bytes inferiores. Su mayor valor puede ser 0FFFFh (poner un cero delante es una convención, para que lo entiendan los ensambladores, al igual que la h al final indicando que es un número hexadecimal). Esto nos da acceso a 64Kb de memoria, que se considera un bloque. También, a partir de ahora, se llamará Offset a la dirección indicada por estos dos bytes.

 Ahora se desea más memoria que 64 Kb, y para esto se poseen los otros dos bytes. Para formar la dirección completa, se toman los 16 bits del registro de segmento y se situan en los 16 bits superiores de la dirección de 20 bits, dejando los otros cuatro a cero. Vamos, como si se añade cuatro ceros a la derecha, y se suma a este valor de 20 bits, el Offset, da el resultado de la dirección real de memoria

 A continuación se presentará una demostración gráfica para el mejor entendimiento:

 Sea el valor de Segmento 0Ah (o decir, 10 decimal o 1010b, en binario ). Y el del Offset que va a valer (en binario) 01011111 00001010.

 La suma para obtener la dirección de memoria sería tal que así:

            0000 0000 0000 1010 0000        (segmento multiplicado*16, con 4 ceros mas )

+      0101 1111 0000 1010        (el offset)

--------------------------------------

0000 0101 1111 1010 1010

Y esta sería la dirección *real* de memoria ( 05FAAh o 24490 Dec ). Como podreis observar, y como curiosidad final, distintos segments y offsets especifican direcciones de memoria distintas; por ejemplo, los pares 0040h:0000 ( donde el primero es el Segment y el segundo el Offset, así lo tomaremos a partir de ahora ), son iguales que 0000:0400h, y los dos se referirían a la misma posición de memoria física, la 0400h o 1024d. Espero que haya quedado claro, aunque sea símplemente tener una ligera idea. Lo próximo serán los registros, y ( y ahora me pongo como los del Pcmanía cuando hablan de Windoze95 ) podremos empezar en serio con nuestro lenguaje favorito X-)

8. Juego de registros

Quizá  alguno de vosotros se está preguntando a estas alturas: ¨ Y eso del Segment y Offset, dónde se guarda, que indica al ordenador esos sitios en memoria, que indica al ordenador en que punto de la memoria est  y que tiene que ejecutar ? Pues bien, para esto y mucho mas sirven los registros.

Se trata de una serie de "variables", que contienen información que puede ser cambiada. Comenzaré, al contrario que todos los libros, por los de segmento y offset actual: CS e IP.

El registro CS es una variable de un tamaño de dos bytes. Contiene el Segmento actual en que se encuentra el programa. IP, es la variable, de dos bytes también, que contiene el Offset actual. Esto significa, el ordenador va interpretando las secuencias de bytes, pero necesita "algo" que le indique donde tiene que leer. La combinación CS:IP ( tal y como me referí antes en lo de segments & Offsets ) contiene la dirección en la que el ordenador est  interpretando información *en el momento*. O sea, indica la dirección de la próxima instrucción que se va a ejecutar.

El registro DS y el registro ES también sirven para guardar direcciones de Segmentos, y tambien son variables de dos bytes, ser n utilizados para por ejemplo mover datos en memoria, imprimir cadenas, bueno, un etcétera larguísimo. Digamos que son "punteros", que apuntan a cierta zona de memoria ( siempre combinado con otro que haga de Offset, claro ).

El registro SS apunta a la pila, y el SP es el que contiene el offset de la pila, pero esto lo explicaré mas adelante. Luego tenemos una serie de registros que utilizaremos mas comúnmente: AX, BX, CX y DX.

 Todas ocupan dos bytes, y se pueden utilizar divididas en dos partes de longitud un byte, cambiando de nombre. AX se divide en AH y AL, BX en BH y BL, CX en CH y CL y DX en DH y DL. La 'H' se refiere a High en inglés, alto ( de mayor valor ), y la 'l' a  Low ( de menor valor ). Lo ilustro un poquillo:

                                                                AX

|-----------------------------|

11010110    10111000

 AH                AL

 Las funciones de estos cuatro registros son diferentes:

 AX se suele utilizar como propósito general, indica función a las interrupciones, etc., y es el mas flexible, ya que ser  el único que permita multiplicaciones y divisiones. Se denomina a veces acumulador.

 BX nos servirá  mucho como "handler", para abrir/cerrar archivos, etc, y como registro de propósito general al igual que AX, CX y DX. CX se suele usar como contador. DX suele ser el puntero, señalando haciendo el papel de Offset lugares en memoria ( suele combinarse con DS en la forma DS:DX ).

 Y nos quedan ya sólo tres registros, BP, SI y DI, que son también punteros. SI y DI los utilizaremos a menudo para copiar bytes de un lado a otro, etc. Ni que decir que, como el resto de registros, contienen dos bytes. Igual sucede con BP, de otros dos bytes de tamaño.