por Dario Alejandro Alpern
El 80386 consiste en una unidad central de proceso (CPU), una unidad de manejo de memoria (MMU) y una unidad de interfaz con el bus (BIU).
La CPU está compuesta por la unidad de ejecución y la unidad de instrucciones. La unidad de ejecución contiene los ocho registros de 32 bits de propósito general que se utilizan para el cálculo de direcciones y operaciones con datos y un barrel shifter de 64 bits que se utiliza para acelerar las operaciones de desplazamiento, rotación, multiplicación y división. Al contrario de los microprocesadores previos, la lógica de división y multiplicación utiliza un algoritmo de 1 bit por ciclo de reloj. El algoritmo de multiplicación termina la iteración cuando los bits más significativos del multiplicador son todos cero, lo que permite que las multiplicaciones típicas de 32 bits se realicen en menos de un microsegundo.
La unidad de instrucción decodifica los códigos de operación (opcodes) de las instrucciones que se encuentran en una cola de instrucciones (cuya longitud es de 16 bytes) y los almacena en la cola de instrucciones decodificadas (hay espacio para tres instrucciones).
El sistema de control de la unidad de ejecución es el encargado de decodificar las instrucciones que le envía la cola y enviarle las órdenes a la unidad aritmética y lógica según una tabla que tiene almacenada en ROM llamada CROM (Control Read Only Memory).
La unidad de manejo de memoria (MMU) consiste en una unidad de segmentación (similar a la del 80286) y una unidad de paginado (nuevo en este microprocesador). La segmentación permite el manejo del espacio de direcciones lógicas agregando un componente de direccionamiento extra, que permite que el código y los datos se puedan reubicar fácilmente. El mecanismo de paginado opera por debajo y es transparente al proceso de segmentación, para permitir el manejo del espacio de direcciones físicas. Cada segmento se divide en uno o más páginas de 4 kilobytes. Para implementar un sistema de memoria virtual (aquél donde el programa tiene un tamaño mayor que la memoria física y debe cargarse por partes (páginas) desde el disco rígido), el 80386 permite seguir ejecutando los programas después de haberse detectado fallos de segmentos o de páginas. Si una página determinada no se encuentra en memoria, el 80386 se lo indica al sistema operativo mediante la excepción 14, luego éste carga dicha página desde el disco y finalmente puede seguir ejecutando el programa, como si hubiera estado dicha página todo el tiempo. Como se puede observar, este proceso es transparente para la aplicación, por lo que el programador no debe preocuparse por cargar partes del código desde el disco ya que esto lo hace el sistema operativo con la ayuda del microprocesador.
La memoria se organiza en uno o más segmentos de longitud variable, con tamaño máximo de 4 gigabytes. Estos segmentos, como se vio en la explicación del 80286, tienen atributos asociados, que incluyen su ubicación, tamaño, tipo (pila, código o datos) y características de protección.
La unidad de segmentación provee cuatro niveles de protección para aislar y proteger aplicaciones y el sistema operativo. Este tipo de protección por hardware permite el diseño de sistemas con un alto grado de integridad.
El 80386 tiene dos modos de operación: modo de direccionamiento real (modo real), y modo de direccionamiento virtual protegido (modo protegido). En modo real el 80386 opera como un 8086 muy rápido, con extensiones de 32 bits si se desea. El modo real se requiere primariamente para preparar el procesador para que opere en modo protegido. El modo protegido provee el acceso al sofisticado manejo de memoria y paginado.
Dentro del modo protegido, el software puede realizar un cambio de tarea para entrar en tareas en modo 8086 virtual (V86 mode) (esto es nuevo con este microprocesador). Cada una de estas tareas se comporta como si fuera un 8086 el que lo está ejecutando, lo que permite ejecutar software de 8086 (un programa de aplicación o un sistema operativo). Las tareas en modo 8086 virtual pueden aislarse entre sí y del sistema operativo (que debe utilizar instrucciones del 80386), mediante el uso del paginado y el mapa de bits de permiso de entrada/salida (I/O Permission Bitmap). Finalmente, para facilitar diseños de hardware de alto rendimiento, la interfaz con el bus del 80386 ofrece pipelining de direcciones, tamaño dinámico del ancho del bus de datos (puede tener 16 ó 32 bits según se desee en un determinado ciclo de bus) y señales de habilitación de bytes por cada byte del bus de datos.
La siguiente figura muestra los registros de la arquitectura base del 80386, que incluye los registros de uso general, el puntero de instrucciones y el registro de indicadores. Los contenidos de estos registros y de los selectores del párrafo siguiente son específicos para cada tarea, así que se cargan automáticamente al ocurrir una operación de cambio de tarea. La arquitectura base también incluye seis segmentos direccionables directamente, cada uno de 4 gigabytes de tamaño máximo. Los segmentos se indican mediante valores de selectores puestos en los registros de segmento del 80386. Si se desea se pueden cargar diferentes selectores a medida que corre el programa.
Tipo | Registros | Bits 31-16 | Bits 15-0 | Descripción |
---|---|---|---|---|
Uso general | EAX | EAX31-16 | EAX15-0 = AX | Acumulador |
EBX | EBX31-16 | EBX15-0 = BX | Base | |
ECX | ECX31-16 | ECX15-0 = CX | Contador | |
EDX | EDX31-16 | EDX15-0 = DX | Datos | |
ESI | ESI31-16 | ESI15-0 = SI | Indice Fuente | |
EDI | EDI31-16 | EDI15-0 = DI | Indice Destino | |
EBP | EBP31-16 | EBP15-0 = BP | Puntero Base | |
ESP | ESP31-16 | ESP15-0 = SP | Puntero de Pila | |
De segmento | CS | No aplicable: estos registros son de 16 bits | CS | Segmento de código |
SS | SS | Segmento de pila | ||
DS | DS | Segmento de datos | ||
ES | DS | Segmentos de datos extra | ||
FS | DS | |||
GS | DS | |||
Otros | EIP | EIP31-16 | EIP15-0 = IP | Puntero de instrucciones |
EFlags | EFlags31-16 | EFlags15-0 = Flags | Indicadores |
Los ocho registros de uso general de 32 bits se pueden usar para direccionamiento indirecto. Cualquiera de los ocho registros puede ser la base y cualquiera menos ESP puede ser el índice. El índice se puede multiplicar por 1, 2, 4 u 8.
Ejemplos de direccionamiento indirecto:
Los seis segmentos direccionables en cualquier momento se definen mediante los registros de segmento CS, DS, ES, FS, GS, SS. El selector en CS indica el segmento de código actual, el selector en SS indica el segmento de pila actual y los selectores en los otros registros indican los segmentos actuales de datos.
Registros descriptores de segmento: Estos registros no son visibles para el programador, pero es muy útil conocer su contenido. Dentro del 80386, un registro descriptor (invisible para el programador) está asociado con cada registro de segmento (visible para el programador). Cada descriptor mantiene una dirección base de 32 bits, un límite (tamaño) de 32 bits y otros atributos del segmento.
Cuando un selector se carga en un registro de segmento, el registro descriptor asociado se cambia automáticamente con la información correcta. En modo real, sólo la dirección base se cambia (desplazando el valor del selector cuatro bits hacia la izquierda), ya que el límite y los otros atributos son fijos. En modo protegido, la dirección base, el límite y los otros atributos se cargan con el contenido de una tabla usando el selector como índice.
Siempre que ocurre una referencia a memoria, se utiliza automáticamente el registro descriptor de segmento asociado con el segmento que se está usando. La dirección base de 32 bits se convierte en uno de los componentes para calcular la dirección, el límite de 32 bits se usa para verificar si una referencia no supera dicho límite (no se referencia fuera del segmento) y los atributos se verifican para determinar si hubo alguna violación de protección u otro tipo.
CR0 (Registro de control de la máquina): Contiene seis bits definidos para propósitos de control y estado. Los 16 bits menos significativos de CR0 también se conocen con el nombre de palabra de estado de la máquina (MSW), para la compatibilidad con el modo protegido del 80286. Las instrucciones LMSW y SMSW se toman como casos particulares de carga y almacenamiento de CR0 donde sólo se opera con los 16 bits menos significativos de CR0. Para lograr la compatibilidad con sistemas operativos del 80286 la instrucción LMSW opera en forma idéntica que en el 80286 (ignora los nuevos bits definidos en CR0). Los bits definidos de CR0 son los siguientes:
Un cambio de tareas a través de un TSS que cambie el valor de CR3, o una carga explícita de CR3 con cualquier valor, invalidará todas las entradas en la tabla de páginas que se encuentran en el caché de la unidad de paginación. Si el valor de CR3 no cambia durante el cambio de tareas se considerarán válidos los valores almacenados en el caché.
GDTR e IDTR: Estos registros mantienen la dirección lineal base de 32 bits y el límite de 16 bits de GDT e IDT, respectivamente. Los segmentos GDT e IDT, como son globales para todas las tareas en el sistema, se definen mediante direcciones lineales de 32 bits (sujeto a traducción de página si el paginado está habilitado mediante el bit PG) y límite de 16 bits.
LDTR y TR: Estos registros mantienen los selectores de 16 bits para el descriptor de LDT y de TSS, respectivamente. Los segmentos LDT y TSS, como son específicos para cada tarea, se definen mediante valores de selector almacenado en los registros de segmento del sistema. Nótese que un registro descriptor del segmento (invisible para el programador) está asociado con cada registro de segmento del sistema.
1) El código de operación de punto de parada INT 3 (0CCh).
2) La capacidad de ejecución paso a paso que provee el indicador TF.
Lo nuevo en el 80386 son los registros de depuración. Los seis registros de depuración de 32 bits accesibles al programador, proveen soporte para depuración (debugging) por hardware. Los registros DR0-DR3 especifican los cuatro puntos de parada (breakpoints). Como los puntos de parada se indican mediante registros en el interior del chip, un punto de parada de ejecución de instrucciones se puede ubicar en memoria ROM o en código compartido por varias tareas, lo que no es posible utilizando el código de operación INT 3. El registro de control DR7 se utiliza para poner y habilitar los puntos de parada y el registro de estado DR6 indica el estado actual de los puntos de parada. Después del reset, los puntos de parada están deshabilitados. Los puntos de parada que ocurren debido a los registros de depuración generan una excepción 1.
Registros de direcciones lineales de puntos de parada (DR0 - DR3): Se pueden especificar hasta cuatro direcciones de puntos de parada escribiendo en los registros DR0 a DR3. Las direcciones especificadas son direcciones lineales de 32 bits. El hardware del 80386 continuamente compara las direcciones lineales de los registros con las direcciones lineales que genera el software que se está ejecutando (una dirección lineal es el resultado de computar la dirección efectiva y sumarle la dirección base de 32 bits del segmento). Nótese que si la unidad de paginación del 80386 no está habilitada (mediante el bit PG del registro CR0), la dirección lineal coincide con la física (la que sale por el bus de direcciones), mientras que si está habilitada, la dirección lineal se traduce en la física mediante dicha unidad.
Registro de control de depuración (DR7): La definición de los bits de este registro es la siguiente:
Campo | LEN3 | RW3 | LEN2 | RW2 | LEN1 | RW1 | LEN0 | RW0 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
Campo | Indef | GD | Indef | GE | LE | G3 | L3 | G2 | L2 | G1 | L1 | G0 | L0 | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
LENi = 00 (1 byte): Los 32 bits de DRi se utilizan para especificar el
punto de parada.
LENi = 01 (2 bytes): Se utilizan los bits 31-1 de DRi.
LENi = 10: Indefinido. No debe utilizarse.
LENi = 11 (4 bytes): Se utilizan los bits 31-2 de DRi.
RWi = 00: Ejecución de instrucciones solamente.
RWi = 01: Escritura de datos solamente.
RWi = 10: Indefinido. No debe utilizarse.
RWi = 11: Lectura y/o escritura de datos solamente.
Los puntos de parada (excepción 1) debido a la ejecución de instrucciones ocurren ANTES de la ejecución, mientras que los puntos de parada debido a acceso a datos ocurren DESPUES del acceso.
Uso de LENi y RWi para poner un punto de parada debido a acceso a datos: Esto se realiza cargando la dirección lineal del dato en DRi (i=0-3). RWi puede valer 01 (sólo parar en escritura) o 11 (parar en lectura/escritura). LENi puede ser igual a 00 (un byte), 01 (2 bytes) ó 11 (4 bytes). Si un acceso de datos cae parcial o totalmente dentro del campo definido por DRi y LENi y el punto de parada está habilitado, se producirá la excepción 1.
Uso de LENi y RWi para poner un punto de parada debido a la ejecución de una instrucción: Debe cargarse DRi con la dirección del comienzo (del primer prefijo si hay alguno). RWi y LENi deben valer 00. Si se está por ejecutar la instrucción que comienza en la dirección del punto de parada y dicho punto de parada está habilitado, se producirá una excepción 1 antes de que ocurra la ejecución de la instrucción.
El registro DR6 contiene indicadores para cada uno de los eventos arriba mencionados. Los otros bits son indefinidos. Estos indicadores se ponen a uno por hardware pero nunca puestos a cero por hardware, por lo que el manejador de la excepción 1 deberá poner DR6 a cero.
Condición | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|---|---|
Indicador | BT | BS | BD | B3 | B2 | B1 | B0 |
Bit | 15 | 14 | 13 | 3 | 2 | 1 | 0 |
Registro | Modo Real | Modo Protegido | Modo virtual 8086 | |||
---|---|---|---|---|---|---|
Escr | Lect | Escr | Lect | Escr | Lect | |
Registros generales | Sí | Sí | Sí | Sí | Sí | Sí |
Registros de segmento | Sí | Sí | Sí | Sí | Sí | Sí |
Indicadores | Sí | Sí | Sí | Sí | IOPL | IOPL |
Registros de control | Sí | Sí | CPL=0 | CPL=0 | No | Sí |
GDTR | Sí | Sí | CPL=0 | Sí | No | Sí |
IDTR | Sí | Sí | CPL=0 | Sí | No | Sí |
LDTR | No | No | CPL=0 | Sí | No | No |
TR | No | No | CPL=0 | Sí | No | No |
Registros de depuración | Sí | Sí | CPL=0 | CPL=0 | No | No |
Registros de test | Sí | Sí | CPL=0 | CPL=0 | No | No |
CPL=0: Los registros se pueden acceder sólo si el nivel de privilegio actual es cero.
IOPL: Las instrucciones PUSHF y POPF son sensibles al indicador IOPL en modo 8086 virtual.
1) No depender de los estados de cualquiera de los bits no definidos. Estos deben ser enmascarados (mediante la instrucción AND con el bit a enmascarar a cero) cuando se utilizan los registros.
2) No depender de los estados de cualquiera de los bits no definidos cuando se los almacena en memoria u otro registro.
3) No depender de la habilidad que tiene el procesador de retener información escrita en bits marcados como indefinidos.
4) Cuando se cargan registros siempre se deben poner los bits indefinidos a cero.
5) Los registros que se almacenaron previamente pueden ser recargados sin necesidad de enmascarar los bits indefinidos.
Los programas que no cumplen con estas indicaciones, pueden llegar a no funcionar en 80486, Pentium y siguientes procesadores, donde los bits no definidos del 80386 poseen algún significado en los otros procesadores.
Clase de excepción | Tipo | Instrucción que la causa | El manejador retorna a dicha instrucción |
---|---|---|---|
Error de división | 0 | DIV, IDIV | SI |
Excepción de depuración | 1 | Cualquier instrucción | |
Interrupción NMI | 2 | INT 2 o NMI | NO |
Interrupción de un byte | 3 | INT 3 | NO |
Sobrepasamiento | 4 | INTO | NO |
Fuera de rango | 5 | BOUND | SI |
Código inválido | 6 | Instrucción ilegal | SI |
El dispositivo no existe | 7 | ESC, WAIT | SI |
Doble falta | 8 | Cualquier instrucción que pueda generar una excepción | |
TSS inválido | 10 | JMP, CALL, IRET, INT | SI |
Segmento no presente | 11 | Carga de registro de segmento | SI |
Falta de pila | 12 | Referencia a la pila | SI |
Violación de protección | 13 | Referencia a memoria | SI |
Falta de página | 14 | Acceso a memoria | SI |
Error del coprocesador | 16 | ESC, WAIT | SI |
Reservado | 17-32 | ||
Interrupción de 2 bytes | 0-255 | INT n | NO |
BSF dest, src (Bit Scan Forward): Busca el primer bit puesto a 1 del operando fuente src (comenzando por el bit cero hasta el bit n-1). Si lo encuentra pone el indicador ZF a 1 y carga el destino dest con el índice a dicho bit. En caso contrario pone ZF a 0.
BSR dest, src (Bit Scan Reverse): Busca el primer bit puesto a 1 del operando fuente src (comenzando por el bit n-1 hasta el bit cero). Si lo encuentra pone el indicador ZF a 1 y carga el destino dest con el índice a dicho bit. En caso contrario pone ZF a 0.
BT dest, src (Bit Test): El bit del destino dest indexado por el valor fuente se copia en el indicador CF.
BTC dest, src (Bit Test with Complement): El bit del destino dest indexado por el valor fuente se copia en el indicador CF y luego se complementa dicho bit.
BTR dest, src (Bit Test with Reset): El bit del destino dest indexado por el valor fuente src se copia en el indicador CF y luego pone dicho bit a cero.
BTS dest, src (Bit Test with Set): El bit del destino dest indexado por el valor fuente src se copia en el indicador CF y luego pone dicho bit a uno.
CDQ (Convert Doubleword to Quadword): Convierte el número signado de 4 bytes en EAX en un número signado de 8 bytes en EDX:EAX copiando el bit más significativo (de signo) de EAX en todos los bits de EDX.
CWDE (Convert Word to Extended Doubleword): Convierte una palabra signada en el registro AX en una doble palabra signada en EAX copiando el bit de signo (bit 15) de AX en los bits 31-16 de EAX.
JECXZ label (Jump on ECX Zero): Salta a la etiqueta label si el registro ECX vale cero.
LFS, LGS, LSS dest, src (Load pointer using FS, GS, SS): Carga un puntero de 32 bits de la memoria src al registro de uso general destino dest y FS, GS o SS. El offset se ubica en el registro destino y el segmento en FS, GS o SS. Para usar esta instrucción la palabra en la dirección indicada por src debe contener el offset, y la palabra siguiente debe contener el segmento. Esto simplifica la carga de punteros lejanos (far) de la pila y de la tabla de vectores de interrupción.
MOVSX dest, src (Move with Sign eXtend): Copia el valor del operando fuente src al registro destino dest (que tiene el doble de bits que el operando fuente) extendiendo los bits del resultado con el bit de signo. Se utiliza para aritmética signada (con números positivos y negativos).
MOVZX dest, src (Move with Zero eXtend): Copia el valor del operando fuente src al registro destino dest (que tiene el doble de bits que el operando fuente) extendiendo los bits del resultado con ceros. Se utiliza para aritmética no signada (sin números negativos).
POPAD (Pop All Doubleword Registers): Retira los ocho registros de uso general de 32 bits de la pila en el siguiente orden: EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX. El valor de ESP retirado de la pila se descarta.
POPFD (Pop Doubleword Flags): Retira de la pila los indicadores completos (32 bits).
PUSHAD (Push All Doubleword Registers): Pone los ocho registros de uso general de 32 bits en la pila en el siguiente orden: EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI.
PUSHFD (Push Doubleword Flags): Pone los 32 bits del registro de indicadores en la pila.
SETcc dest (Set Byte on Condition cc): Pone el byte del destino dest a uno si se cumple la condición, en caso contrario lo pone a cero. Las condiciones son las mismas que para los saltos condicionales (se utilizan las mismas letras que van después de la "J").
SHLD dest, src, count (Shift Left Double precision): Desplaza dest a la izquierda count veces y las posiciones abiertas se llenan con los bits más significativos de src.
SHRD dest, src, count (Shift Left Double precision): Desplaza dest a la derecha count veces y las posiciones abiertas se llenan con los bits menos significativos de src.