El microprocesador 80286

por Dario Alejandro Alpern

Introducción

Este microprocesador apareció en febrero de 1982. Los avances de integración que permitieron agregar una gran cantidad de componentes periféricos en el interior del 80186/80188, se utilizaron en el 80286 para hacer un microprocesador que soporte nuevas capacidades, como la multitarea (ejecución simultánea de varios programas), lo que requiere que los programas no "choquen" entre sí, alterando uno los datos o las instrucciones de otros programas. El 80286 tiene dos modos de operación: modo real y modo protegido. En el modo real, se comporta igual que un 8086, mientras que en modo protegido, las cosas cambian completamente, como se explica a partir del próximo párrafo. Esto necesitó un nivel de integración mucho mayor. El 80286 contiene 134.000 transistores dentro de su estructura (360% más que el 8086). Externamente está encapsulado en formato PLCC (Plastic Leaded Chip Carrier) con pines en forma de J para montaje superficial, o en formato PGA (Pin Grid Array), en ambos casos con 68 pines.

El microprocesador 80286 ha añadido un nuevo nivel de satisfacción a la arquitectura básica del 8086, incluyendo una gestión de memoria con la extensión natural de las capacidades de direccionamiento del procesador. El 80286 tiene elaboradas facilidades incorporadas de protección de datos. Otras características incluyen todas las características del juego de instrucciones del 80186, así como la extensión del espacio direccionable a 16 MB, utilizando 24 bits para direccionar (224 = 16.777.216).

El 80286 revisa cada acceso a instrucciones o datos para comprobar si puede haber una violación de los derechos de acceso. Este microprocesador está diseñado para usar un sistema operativo con varios niveles de privilegio. En este tipo de sistemas operativos hay un núcleo que, como su nombre indica, es la parte más interna del sistema operativo. El núcleo tiene el máximo privilegio y los programas de aplicaciones el mínimo. Existen cuatro niveles de privilegio. La protección de datos en este tipo de sistemas se lleva a cabo teniendo segmentos de código (que incluye las instrucciones), datos (que incluye la pila aparte de las variables de los programas) y del sistema (que indican los derechos de acceso de los otros segmentos).

Para un usuario normal, los registros de segmentación (CS, DS, ES, SS) parecen tener los 16 bits usuales. Sin embargo, estos registros no apuntan directamente a memoria, como lo hacían en el 8086. En su lugar, apuntan a tablas especiales, llamadas tablas de descriptores, algunas de las cuales tienen que ver con el usuario y otras con el sistema operativo. Actualmente a los 16 bits, cada registro de segmento del 80286 mantiene otros 57 bits invisibles para el usuario. Ocho de estos bits sirven para mantener los derechos de acceso (sólo lectura, sólo escritura y otros), otros bits mantienen la dirección real (24 bits) del principio del segmento y otros mantienen la longitud permitida del segmento (16 bits, para tener la longitud máxima de 64 KB). Por ello, el usuario nunca sabe en qué posición real de memoria está ejecutando o dónde se ubican los datos y siempre se mantiene dentro de ciertas fronteras. Como protección adicional, nunca se permite que el usuario escriba en el segmento de código (en modo real se puede escribir sobre dicho segmento). Ello previene que el usuario modifique su programa para realizar actos ilegales y potencialmente peligrosos. Hay también provisiones para prever que el usuario introduzca en el sistema un "caballo de Troya" que pueda proporcionarle un estado de alto privilegio.

El 80286 tiene cuatro nuevos registros. Tres de ellos apuntan a las tablas de descriptores actualmente en uso. Estas tablas contienen información sobre los objetos protegidos en el sistema. Cualquier cambio de privilegio o de segmento debe realizarse a través de dichas tablas. Adicionalmente hay varios indicadores nuevos.

Existen varias instrucciones nuevas, además de las introducidas con el 80186. Todas estas instrucciones se refieren a la gestión de memoria y protección del sistema haciendo cosas tales como cargar y almacenar el contenido de los indicadores especiales y los punteros a las tablas de descriptores.

Modo protegido

Debido a la complejidad del siguiente texto, para entenderlo es necesario leerlo varias veces. Además es necesaria una práctica de estos temas con una PC para fijar los conocimientos.

Mecanismo de direccionamiento

Como en modo real, en modo protegido se utilizan dos componentes para formar la dirección física: un selector de 16 bits se utiliza para determinar la dirección física inicial del segmento, a la cual se suma una dirección efectiva (offset) de 16 bits.

La diferencia entre los dos modos radica en el cálculo de la dirección inicial del segmento. En modo protegido el selector se utiliza para especificar un índice en una tabla definida por el sistema operativo. La tabla contiene la dirección base de 24 bits de un segmento dado. La dirección física se obtiene sumando la dirección base hallada en la tabla con el offset.

Segmentación

La segmentación es un método de manejo de memoria. La segmentación provee la base para la protección. Los segmentos se utilizan para encapsular regiones de memoria que tienen atributos comunes. Por ejemplo, todo el código de un programa dado podría estar contenido en un segmento, o una tabla del sistema operativo podría estar en un segmento. Toda la información sobre un segmento se almacena en una estructura de ocho bytes llamada descriptor. Todos los descriptores del sistema están en tablas en memoria que reconoce el hardware.

Terminología

Los siguientes términos se utilizan en la discusión de descriptores, niveles de privilegio y protección.

Tablas de descriptores

Estas tablas definen todos los segmentos utilizados en un sistema basado en el 80286. Hay tres tipos de tablas que mantienen descriptores: la tabla de descriptores globales o GDT (Global Descriptor Table), la tabla de descriptores locales o LDT (Local Descriptor Table) y la tabla de descriptores de interrupción o IDT (Interrupt Descriptor Table). Todas las tablas son arrays de longitud variable, que pueden tener entre 8 y 65.536 bytes. Cada tabla puede mantener hasta 8192 descriptores. Los 13 bits más significativos de un selector se usan como un índice dentro de la tabla de descriptores. Las tablas tienen registros asociados que contienen la dirección base de 24 bits y el límite de 16 bits de cada tabla.

Cada una de las tablas tiene un registro asociado. Estos se llaman GDTR, LDTR, IDTR (ver las siglas en inglés que aparecen en el párrafo anterior). Las instrucciones LGDT, LLDT y LIDT cargan (Load) la base y el límite de la tabla de descriptores globales, locales o de interrupción, respectivamente, en el registro apropiado. Las instrucciones SGDT, SLDT y SIDT almacenan (Store) los valores anteriormente mencionados de los registros en memoria. Estas tablas son manipuladas por el sistema operativo, por lo que las instrucciones de carga son instrucciones privilegiadas (sólo se ejecutan si el CPL vale cero).

Tabla de descriptores globales (GDT)

Esta tabla contiene descriptores que están disponibles para todas las tareas del sistema. La GDT puede contener cualquier clase de descriptores de segmento excepto los relacionados con interrupciones. Todos los sistemas basados en el 80286 en modo protegido contienen una GDT. Generalmente la GDT contiene los segmentos de código y datos usados por el sistema operativo y los TSS (cuya explicación aparece más adelante) y los descriptores para las LDT en un sistema.
La primera entrada de la GDT corresponde al selector nulo y no se usa.

Tabla de descriptores locales (LDT)

: Las LDT contienen descriptores asociados con una tarea particular. Generalmente los sistemas operativos se diseñan de forma tal que cada tarea tenga su propia LDT. La LDT sólo puede contener descriptores de código, datos, pila, compuertas de tarea, y compuertas de llamada. Las LDT proveen un mecanismo para aislar los segmentos de código y datos de una tarea dada del sistema operativo y las otras tareas, mientras que la GDT contiene descriptores comunes a todas las tareas. Una tarea no puede acceder un segmento si no existe en la LDT actual o en la GDT. Esto permite el aislamiento y protección de los segmentos de una tarea, mientras que los datos comunes pueden ser compartidos por todas las tareas.

Tabla de descriptores de interrupción

: La tercera tabla necesaria para sistemas 80286 que operan en modo protegido es la IDT. Esta tabla contiene los descriptores que apuntan a la ubicación de hasta 256 rutinas de servicio de interrupción (interrupt handlers). Debe conocerse cuál es el valor máximo del tipo de interrupción que se va a utilizar y poner el límite del IDTR igual a ocho veces ese valor (ya que, como se explicó anteriormente, cada descriptor ocupa ocho bytes). Cada interrupción utilizada por el sistema debe tener una entrada propia en la IDT. Las entradas de la IDT se referencian mediante instrucciones INT, vectores externos de interrupción y excepciones (interrupciones internas del microprocesador).

Registros de direcciones del sistema

Como se indicó anteriormente, existen cuatro registros del 80286 que apuntan a las tablas de descriptores.

GDTR e IDTR: Estos registros mantienen la dirección base de 24 bits y el límite de 16 bits de las tablas GDT e IDT, respectivamente. Estos segmentos, como son globales para todas las tareas en el sistema, se definen mediante direcciones físicas de 24 bits 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. Estos segmentos, como son específicos para cada tarea, se definen mediante valores de selector almacenado en los registros de segmento del sistema. Éste apunta a un descriptor apropiado (de LDT o TSS). Nótese que un registro descriptor del segmento (invisible para el programador) está asociado con cada registro de segmento del sistema.

Descriptores

A continuación se brinda una descripción detallada de los contenidos de los descriptores utilizados en el microprocesador 80286 operando en modo protegido.

Bits de atributo del descriptor

: El objeto apuntado por el selector se llama descriptor. Los descriptores son cantidades de ocho bytes que contienen atributos sobre una región de memoria (es decir, un segmento). Estos atributos incluyen la dirección base de 24 bits, la longitud de 16 bits, el nivel de protección (de 0 a 3), permisos de lectura, escritura o ejecución (esto último para segmentos de código), y el tipo de segmento. A continuación se muestra el formato general de un descriptor.

El byte de derechos de acceso es el que define qué clase de descriptor es. El bit 4 (S) indica si el segmento es de código o datos (S = 1), o si es del sistema (S = 0). Veremos el primer caso.

Si se lee o escribe en un segmento donde no está permitido o se intenta ejecutar en un segmento de datos se genera una excepción 13 (Violación general de protección). Si bien no se puede escribir sobre un segmento de código, éstos se pueden inicializar o modificar mediante un alias. Los alias son segmentos de datos con permiso de escritura (E = 0, W = 1) cuyo rango de direcciones coincide con el segmento de código.

Los segmentos de código cuyo bit C vale 1, pueden ejecutarse y compartirse por programas con diferentes niveles de privilegio (ver la sección sobre protección, más adelante).

A continuación se verá el formato del byte de derechos de acceso para descriptores de segmentos del sistema:

Como se pudo observar hay atributos en común entre los distintos descriptores: P, DPL y S.

Ahora se verá con más detalle los diferentes descriptores del sistema.

- Descriptores de LDT (S = 0, tipo = 2): Contienen información sobre tablas de descriptores locales. Las LDT contienen una tabla de descriptores de segmentos, únicos para cada tarea. Como la instrucción para cargar el registro LDTR sólo está disponible en el nivel de privilegio 0 (nivel más privilegiado), el campo DPL es ignorado. Los descriptores de LDT sólo se permiten en la tabla de descriptores globales (GDT).

- Descriptores de TSS (S = 0, tipos = 1, 3): Un descriptor de segmento de estado de la tarea (TSS: Task State Segment) contiene información sobre la ubicación, el tamaño y el nivel de privilegio de un TSS. Un TSS es un segmento especial con formato fijo que contiene toda la información sobre el estado de una tarea y un campo de enlace para permitir tareas anidadas. El campo de tipo se usa para indicar si la tarea está ocupada (tipo = 3), es decir, en una cadena de tareas activas, o si el TSS está disponible (tipo = 1). El registro de tarea (TR: Task Register) contiene el selector que apunta al TSS actual dentro de la GDT.

- Descriptores de compuertas (tipos = 4 a 7): Las compuertas se utilizan para controlar el acceso a puntos de entrada dentro del segmento de código objetivo. Los distintos tipos de compuerta son: de llamada (call gate), de tarea (task gate), de interrupción (interrupt gate) y de trampa (trap gate). Las compuertas proveen un nivel de indirección entre la fuente y el destino de la transferencia de control. Esta indirección permite al procesador realizar automáticamente verificaciones de protección. También permite a los diseñadores controlar los puntos de entrada a los sistemas operativos. Las compuertas de llamada se utilizan para cambiar niveles de privilegio (ver la sección que trata sobre privilegio, más adelante), las compuertas de tarea se utilizan para hacer cambios de tarea y las de interrupción y de trampa se utilizan para especificar rutinas de servicio de interrupción.

A continuación se muestra el formato de los descriptores de compuertas, que difieren del formato general:

Las compuertas de llamado se utilizan para transferir el control del programa a un nivel más privilegiado. El descriptor correspondiente consiste en tres campos: el byte de derechos de acceso, un puntero largo (selector y offset) y una cantidad de palabras que especifica cuántos parámetros deben copiarse de la pila de la rutina llamadora a la pila de la rutina llamada. Este campo sólo es utilizado por las compuertas de llamada cuando hay un cambio de nivel de privilegio (PL). Los otros tipos de compuertas ignoran el campo de cantidad de palabras.

Las compuertas de interrupción y de trampa utilizan los campos de selector y offset como un puntero al inicio de la rutina que maneja la interrupción o trampa. La diferencia entre ambos tipos de compuertas es que la de interrupción deshabilita las interrupciones (IF <- 0), mientras que la de trampa no altera IF.

Las compuertas de tarea se usan para cambiar tareas. Las compuertas de tarea sólo se pueden referir a un TSS y por lo tanto sólo se utiliza el campo del selector, siendo ignorado el de offset.

Se genera una excepción 13 (Violación general de protección) si un selector no se refiere a un tipo de descriptor correcto (un segmento de código para una compuerta de interrupción, trampa o llamada y un segmento de estado de tarea para la compuerta de tarea).

Caché del descriptor del segmento

: Aparte del valor del selector, cada registro de segmento tiene un registro caché de descriptor del segmento invisible para el programador. Al cargar un registro de segmento con un nuevo selector, el descriptor de ocho bytes asociado con ese selector se carga automáticamente en el chip. Una vez que se hizo esto, todas las referencias a ese segmento utilizan la información almacenada en el caché en vez de volver a acceder el descriptor. Como los cachés de los descriptores sólo varían cuando se carga un registro de segmento, los programas que deben modificar las tablas de descriptores deben volver a cargar los registros de segmento apropiados después de cambiar el valor de un descriptor.

Contenido de los cachés descriptores de segmento: El contenido varía dependiendo del modo en que opera el 80286. En modo protegido, como se explicó más arriba, los cachés se cargan con la información de los descriptores. En modo real, el contenido no se obtiene de la memoria, sino que toma unos valores fijos para lograr compatibilidad con el 8086. La base generada es 16 veces el valor del selector, el límite siempre es FFFFh, el bit P (presente) vale 1, el nivel de privilegio es cero (máximo privilegio), el bit de dirección de expansión indica que los offsets deben ser menores o iguales que el límite y están habilitados los permisos de lectura, escritura y ejecución. Todo esto hace que en modo real no se pueda acceder a los 16 MB de capacidad de direccionamiento del 80286, ya que el valor máximo ocurre cuando el selector vale FFFFh y el offset también, con lo que se logra una dirección máxima de FFFF0h + FFFFh = 10FFEFh (casi 1088 KB).

Protección

El 80286 tiene cuatro niveles de protección que están optimizados para soportar las necesidades de los sistemas operativos multitarea para aislar y proteger los programas de un usuario de otros y del sistema operativo. Los niveles de privilegio controlan el uso de instrucciones privilegiadas, instrucciones de entrada/salida, y el acceso a segmentos y descriptores de segmento. A diferencia de los sistemas tradicionales basados en microprocesadores donde esta protección sólo se logra a través de un hardware externo muy complejo con el correspondiente software, el 80286 provee esta protección como parte de la unidad de manejo de memoria (MMU: Memory Management Unit) incorporada.

El sistema de privilegio jerárquico de cuatro niveles es una extensión de los modos usuario/supervisor que se encuentran comúnmente en minicomputadoras. Los niveles de privilegio (PL: Privilege Level) se numeran de 0 a 3, siendo el 0 el nivel más privilegiado (más confiable).

Ejemplo de los niveles jerárquicos:

Reglas de privilegio

El 80286 controla el acceso a los datos y procedimientos (o subrutinas) entre niveles de una tarea, de acuerdo a las siguientes reglas:

Privilegio de una tarea

En un momento determinado, una tarea en el 80286 se ejecuta en uno de los cuatro niveles de privilegio. Esto está especificado por el nivel de privilegio actual (CPL). El CPL de una tarea sólo puede cambiarse mediante transferencias de control utilizando los descriptores de compuerta a un segmento de código con un nivel de privilegio diferente. Por ejemplo, un programa de aplicación corriendo con PL = 3 puede llamar una rutina del sistema operativo con PL = 1 (mediante una compuerta) que causaría que CPL = 1 hasta que finalice la rutina del sistema operativo.

Nivel de privilegio del selector (RPL)

El nivel de privilegio de un selector se especifica en el campo RPL. El RPL está formado por los dos bits menos significativos del selector. Se utiliza para que un nivel de privilegio menos confiable que el nivel de privilegio actual (CPL) pueda utilizar dicho selector. Este nivel se llama nivel de privilegio efectivo (EPL) de la tarea, que se define como el nivel menos privilegiado (numéricamente mayor) del CPL de la tarea y el RPL del selector. Así, si RPL = 0, entonces CPL siempre especifica el nivel de privilegio para realizar un acceso usando el selector, mientras que si RPL = 3, entonces el selector sólo puede acceder segmentos de nivel 3 independientemente del valor de CPL de la tarea. El RPL se utiliza generalmente para verificar que los punteros pasados a un procedimiento del sistema operativo no accede datos que son de mayor privilegio que el procedimiento que originó el puntero. Como dicho procedimiento puede especificar cualquier valor de RPL, la instrucción de ajuste de RPL (ARPL) se utiliza para forzar los bits de RPL a que sean iguales al CPL de la rutina que generó el selector.

Privilegio de entrada/salida

El nivel de privilegio de entrada/salida (IOPL, que ocupa los bits 13 y 12 del registro de indicadores) define el nivel menos privilegiado para el cual se pueden realizar instrucciones de I/O (IN, OUT, INS, OUTS, REP INS, REP OUTS). Si CPL > IOPL, al ejecutar alguna de estas instrucciones se generará una excepción 13. IOPL también afecta otras instrucciones, como STI, CLI y el prefijo LOCK. Además afecta si IF (indicador de interrupciones) puede cambiarse cargando un valor en el registro de indicadores (mediante POPF). Si CPL es menor o igual que IOPL, entonces IF se puede cambiar. Si CPL > IOPL el valor de IF no varía mediante la ejecución de la instrucción POPF (en este caso no se genera ninguna excepción).

Validación de privilegio

El 80286 provee algunas instrucciones para acelerar la verificación de punteros y mantener la integridad del sistema comprobando que el valor del selector se refiera a un segmento apropiado.
La verificación de punteros previene el problema común de una aplicación con PL = 3 que llama a un rutina del sistema operativo con PL = 0 pasándole un puntero "erróneo" que corrompe una estructura de datos que pertenece al sistema operativo. Si éste utilizara la instrucción ARPL para asegurar que el RPL del selector no tiene mayor privilegio que la rutina llamadora, este problema podría evitarse.

Las instrucciones para verificar punteros son ARPL, VERR, VERW, LSL y LAR. Todas estas instrucciones ponen el indicador de cero a uno si la verificación se pudo realizar; si no se puede realizar pone ZF <- 0, en vez de generar una excepción 13 como hace con otras instrucciones.

Acceso a descriptores

Básicamente hay dos tipos de accesos de segmentos: aquéllos que se refieren a los segmentos de código como las transferencias de control, y aquéllos que se refieren a segmentos de datos. Para determinar si una tarea puede acceder un segmento se necesita conocer el tipo de segmento a acceder, las instrucciones utilizadas, el tipo de descriptor utilizado y los CPL, RPL y DPL, como se describió más arriba.
Cada vez que una instrucción carga los registros de segmentos de datos (DS, ES), el 80286 hace validaciones de protección. Los selectores almacenados en esos registros sólo pueden referirse a segmentos de datos o segmentos de código con habilitación de lectura. Las reglas de accesos de datos se especificaron anteriormente. La única excepción a estas reglas la constituye el segmento de código legible con el bit C = 1 de los derechos de acceso que se puede acceder desde cualquier nivel.
Finalmente se realizan las verificaciones de privilegio. El CPL se compara con EPL y si EPL es más privilegiado que CPL se genera una excepción 13 (Violación general de protección).
Las reglas para el segmento de pila son ligeramente diferentes que aquéllos referidos a los segmentos de datos. Las instrucciones que cargan los selectores en SS deben referirse a descriptores de segmentos de datos con habilitación de escritura (ya que la pila debe poder ser leída y escrita). El DPL y RPL deben ser iguales al CPL. Cualquier otro tipo de descriptor o una violación de privilegio causará una excepción 13. Si el segmento de pila está marcado como no presente (P = 0) se genera una excepción 12. La excepción 11 ocurre en el caso de segmentos de código o datos no presentes.

Transferencias de nivel de privilegio

Las transferencias de control intersegmento ocurren cuando se carga un selector en el registro CS. Para un sistema típico la mayoría de esas transferencias ocurren simplemente como resultado de una llamada o un salto a otra rutina. Hay cinco tipos de control de transferencia que se resumen a continuación. Varias de estas transferencias resultan en un cambio de nivel de privilegio. El cambio de niveles de privilegio sólo se puede efectuar mediante compuertas y cambios de tarea.

Los tipos de descriptores que se usan para transferencia de control son los siguientes:

Tipos de transferencia
de control
Operaciones que
la generan
Descriptor que se referencia Tabla de descriptores
que se utiliza para
realizar la transferencia
Transferencia de control intersegmento dentro del mismo nivel de privilegioJMP
CALL
RET
IRET (sólo si el flag NT vale 0)
Segmento de código GDT
LDT
Transferencia de control intersegmento al mismo o mayor nivel de privilegioCALL Compuerta de llamada GDT
LDT
Transferencia de control intersegmento al mismo o mayor nivel de privilegioINT
excepción
interrupción externa
Compuerta de interrupción o trampa Tabla de descriptores de interrupción
Transferencia de control intersegmento al mismo o menor nivel de privilegioRET
IRET (sólo si el flag NT vale 0)
Segmento de código GDT
LDT
Cambio de tarea a través del segmento de estado de tareaCALL
JMP
Segmento de estado de la tarea Tabla de descriptores globales
Cambio de tarea a través de una compuerta de tarea CALL
JMP
Compuerta de tarea GDT
LDT
Cambio de tarea a través de una compuerta de tarea IRET (sólo si el flag NT vale 1) Compuerta de tarea Tabla de descriptores de interrupción

Las transferencias de control sólo pueden ocurrir si la operación que cargó el selector se refiere al tipo correcto de descriptor. Cualquier violación de estas reglas causará una excepción 13 (por ejemplo, un salto a través de una compuerta de llamado, o un IRET desde una subrutina de interrupción).

Para que el sistema sea aún más seguro, todas las transferencias de control también se sujetan a las reglas de privilegio, que indican lo siguiente:

Todas las transferencias de control que cambian CPL dentro de la misma tarea causan un cambio de pilas como resultado del cambio de privilegio. Los valores iniciales de SS:SP para los niveles de privilegio 0, 1 y 2 se almacenan en el segmento de estado de tarea (ver más abajo el formato de dicho segmento). Durante la ejecución de JMP o CALL, el nuevo puntero de pila se carga en los registros SS y SP y el puntero previo se pone en la nueva pila.

Al retornar al nivel de privilegio original, el uso de la pila menos privilegiada se restaura como parte de la ejecución de la instrucción RET o la IRET. Para subrutinas que pasan parámetros en la pila y cruzan niveles de privilegio, se copia un número fijo de palabras (especificado en el campo de cuenta de palabras de la compuerta) de la pila previa a la actual. La instrucción RET intersegmento con un valor de ajuste del puntero de pila ajusta correctamente el SP al efectuar el retorno.

Compuertas de llamado: Estas compuertas proveen llamadas (CALL) indirectas, en forma protegida. Uno de los usos más importantes de las compuertas es poder tener un método seguro de transferencias de privilegio dentro de una tarea. Como el sistema operativo define todas las compuertas en el sistema, puede asegurar que todas las compuertas permiten el acceso sólo a los procedimientos confiables (como aquéllos que realizan manejo de memoria u operaciones de entrada/salida). Un intento de acceso a otro lugar causará una excepción 13 (Violación general de protección).

Los descriptores de compuertas siguen las mismas reglas de privilegio que los datos, esto es, una tarea puede acceder una compuerta sólo si el EPL (Effective Privilege Level) es igual o más privilegiado que el DPL (Descriptor Privilege Level). Las compuertas siguen las reglas de privilegio de las transferencias de control y por lo tanto sólo pueden transferir el control a un nivel más privilegiado.

Las compuertas de llamada se acceden mediante una instrucción CALL y son sintácticamente idénticas a la llamada a una subrutina normal. Cuando se activa una llamada que cambia el nivel de privilegio, suceden las siguientes acciones:

1) Cargar CS:IP de la compuerta y verificar la validez.
2) Poner el viejo SS en la nueva pila.
3) Poner el viejo SP en la nueva pila.
4) Copiar la cantidad de parámetros de 16 bits como se indica en el campo de cuenta de palabras de la compuerta de la vieja a la nueva pila.
5) Poner la dirección de retorno en la pila.

Las compuertas de interrupción y de trampa trabajan de la misma forma que las compuertas de llamada, excepto que no hay copia de parámetros. La única diferencia entre las compuertas de interrupción y de trampa es que las primeras deshabilitan las interrupciones (IF <- 0), mientras que las otras no alteran el indicador de interrupciones IF.

Cambio de tareas

Un atributo muy importante de cualquier sistema operativo multitarea/multiusuario es su habilidad para cambiar rápidamente entre tareas o procesos. El 80286 soporta esta operación incluyendo una instrucción de cambio de tarea en el hardware. Dicha instrucción salva el estado entero de la máquina (todos los registros, espacio de direcciones y un puntero a la tarea anterior), carga un nuevo estado de ejecución, realiza verificaciones de protección y comienza la ejecución en menos de 20 microsegundos. Al igual que la transferencia de control por compuertas, la operación de cambio de tareas se invoca ejecutando una instrucción JMP o CALL intersegmento que se refiere a un segmento de estado de la tarea (TSS) o un descriptor de compuerta de tarea en el GDT (Global Descriptor Table) o en el LDT (Local Descriptor Table). Una instrucción INT n, una excepción, trampa o interrupción externa puede también invocar la operación de cambio de tarea si hay un descriptor de compuerta de tarea en la entrada correspondiente de la IDT (Interrupt Descriptor Table).

Segmento de estado de la tarea

: El descriptor TSS apunta a un segmento que contiene el estado de la ejecución de 80286 mientras que un descriptor de compuerta de tarea contiene un selector de TSS.
El límite (cantidad de bytes que ocupa) del segmento debe ser mayor o igual a 2Ch = 44 bytes. Si es mayor, en el espacio adicional del segmento TSS, el sistema operativo puede almacenar información adicional acerca de las razones por la que la tarea está inactiva, el tiempo que la tarea estuvo corriendo, los archivos abiertos que pertenecen a esa tarea, etc.

El formato del TSS es como sigue:

00: Puntero selector del TSS
02: SP para CPL = 0
04: SS para CPL = 0
06: SP para CPL = 1
08: SS para CPL = 1
0A: SP para CPL = 2
0C: SS para CPL = 2
0E: IP (Punto de entrada)
10: Flags
12: Registro AX
14: Registro CX
16: Registro DX
18: Registro BX
1A: Registro SP
1C: Registro BP
1E: Registro SI
20: Registro DI
22: Selector ES
24: Selector CS
26: Selector SS
28: Selector DS
2A: Selector LDT de la tarea
2C: Disponible

Cada tarea debe tener un TSS asociado. El TSS actual se identifica mediante un registro especial en el 80286 llamado TR (Task State Segment Register). Este registro contiene un selector que se refiere al descriptor de TSS de la tarea. Un registro de base y límite del segmento (invisible para el programador) asociado con TR se carga cada vez que TR se carga con un nuevo selector. El retorno de una tarea se realiza mediante la instrucción IRET. Cuando se ejecuta IRET, el control retorna a la tarea que había sido interrumpida. El estado de la tarea que se está ejecutando se almacena en el TSS y el estado de la vieja tarea se restaura de su propio TSS. Algunos bits en el registro de indicadores y la palabra de estado de la máquina (MSW) dan información sobre el estado de una tarea que son útiles para el sistema operativo. El bit 14 del registro de indicadores (NT = Nested Task) controla la función de la instrucción IRET. Si NT = 0, dicha instrucción realiza un retorno normal, pero si NT = 1, IRET realiza una operación de cambio de tarea para volver a ejecutar la tarea anterior. El bit NT se pone a cero o uno de la siguiente manera: cuando una instrucción CALL o INT inicia un cambio de tarea, el nuevo TSS se marca como ocupado y el puntero del nuevo TSS se carga con el selector del viejo TSS. El bit NT de la nueva tarea se pone entonces a 1. Una interrupción que no causa un cambio de tareas pondrá a cero el bit NT (el bit NT será restaurado a su valor anterior luego de la ejecución del manejador de interrupciones). El bit NT también puede ser afectado por las instrucciones POPF o IRET.

El segmento de estado de la tarea (TSS) se marca como ocupado cambiando el campo de tipo de descriptor de tipo 1 a tipo 3. El uso de un selector que referencia un TSS ocupado resultará en una excepción 13 (Violación general de protección).

El estado del coprocesador no se salva automáticamente cuando ocurre un cambio de tareas, porque la nueva tarea puede no utilizar instrucciones del coprocesador. El bit 3 del MSW llamado TS (Task Switched) ayuda en esta situación. Cuando el microprocesador realiza un cambio de tarea, pone a uno el flag TS. El 80286 detecta el primer uso de una instrucción de coprocesador después del cambio de tarea y provoca una excepción 7 (Coprocesador inexistente). El manejador de la excepción puede entonces decidir si debe salvar el estado del coprocesador. Dicha excepción ocurre cuando se trata de ejecutar una instrucción ESC o WAIT (es decir, alguna referida al coprocesador) y los bits Task Switched y Monitor coprocessor extension están ambos a uno (es decir, TS = MP = 1).

Inicialización y transición a modo protegido

Como el 80286 comienza la ejecución (después de activar el pin RESET) en modo real, es necesario inicializar las tablas del sistema y los registros con los valores apropiados. Los registros GDTR e IDTR deben referirse a tablas de descriptores globales y de interrupción (respectivamente) que sean válidas.

Para entrar en modo protegido debe ponerse el bit PE (bit 0 de MSW) a 1 utilizando la instrucción LMSW. Después de entrar en modo protegido, la siguiente instrucción deberá ser un JMP intersegmento para cargar el registro CS y liberar la cola de instrucciones. El paso final es cargar todos los registros de segmentos de datos con los valores iniciales de los selectores.

Una forma alternativa de entrar en modo protegido que es especialmente apropiada en sistemas operativos multitarea consiste en realizar un cambio de tarea para cargar todos los registros. En este caso la GDT contendría dos descriptores de TSS además de los descriptores de código y datos necesarios para la primera tarea. La primera instrucción JMP en modo protegido saltaría al TSS causando un cambio de tarea y cargando todos los registros con los valores almacenados en el TSS. El registro TR (Task State Segment Register) deberá apuntar un descriptor de TSS válido ya que un cambio de tarea almacena el estado de la tarea actual en el TSS apuntado por TR.

Nuevas instrucciones del 80286

Aparte de las instrucciones del 8086/8088 y las nuevas del 80186, el 80286 posee nuevas instrucciones. Éstas corresponden todas al modo protegido y son las siguientes:

ARPL dest, src (Adjust Requested Privilege Level of selector): Compara los bits RPL de dest contra src. Si el RPL de dest es menor que el RPL de src, los bits RPL del destino se cargan con los bits RPL de src y el indicador ZF se pone a uno. En caso contrario ZF se pone a cero. Ver nota 1.

CLTS (Clear Task Switched Flag): Pone a cero el indicador TS (bit 3 de la palabra de control de la máquina MSW). Ver nota 2.

LAR dest, src (Load Access Rights): El byte más alto del registro destino se carga con el byte de derechos de acceso del segmento indicado por el selector almacenado en src. Pone ZF a uno si se puede realizar la carga. Ver notas 1 y 3.

LGDT mem64 (Load Global Table register): Carga el valor del operando en el registro GDTR. Antes de ejecutar esta instrucción la tabla debe estar en memoria. Ver nota 2.

LIDT mem64 (Load Interrupt Table register): Carga el valor del operando en el registro IDTR. Antes de ejecutar esta instrucción la tabla debe estar en memoria. Ver nota 2.

LLDT {reg16|mem16} (Load Local Descriptor Table Register): Carga el selector indicado por el operando en el registro LDTR. Antes de ejecutar esta instrucción la tabla deberá estar en memoria. Ver notas 1 y 2.

LMSW {reg16|mem16} (Load Machine Status Word): Carga el valor del operando en la palabra de estado de la máquina MSW. El bit PE (bit 0) no puede ser puesto a cero por esta instrucción, por lo que una vez que se cambió a modo protegido, la única manera de volver a modo real es mediante un RESET del microprocesador. Ver nota 2.

LSL dest, src (Load Segment Limit): Carga el límite del segmento de un selector especificado en src en el registro destino si el selector es válido y visible en el nivel de privilegio actual. Si ocurre lo anterior el indicador ZF se pone a uno, en caso contrario, se pone a cero. Ver notas 1 y 3.

LTR {reg16|mem16} (Load Task Register): Carga el selector indicado por el operando en el registro TR. El TSS (Task State Segment) apuntado por el nuevo TR deberá ser válido. Ver notas 1 y 2.

SGDT mem64 (Store Global Descriptor Table register): Almacena el contenido del registro GDTR en el operando especificado.

SIDT mem64 (Store Interrupt Descriptor Table register): Almacena el contenido del registro IDTR en el operando especificado.

SLDT {reg16|mem16} (Store Global Descriptor Table register): Almacena el contenido del registro LDTR (que es un selector a la tabla de descriptores globales) en el operando especificado. Ver nota 1.

SMSW {reg16|mem16} (Store Machine Status Word): Almacena la palabra de estado de la máquina MSW en el operando especificado. Ver nota 2.

STR {reg16|mem16} (Store Task Register): Almacena el registro de tarea actual (selector a la tabla de descriptores globales) en el operando especificado. Ver nota 1.

VERR/VERW {reg16|mem16} (Verify Read/Write): Verifica si el selector de segmento especificado en el operando es válido y se puede leer/escribir en el nivel de privilegio actual. En este caso se pone ZF a uno, en caso contrario se pone ZF a cero. Ver notas 1 y 3.

Notas:

1) Si se ejecuta en modo real ocurre una excepción 6 (Código de operación inválido).

2) Si se ejecuta en modo protegido en alguno de los anillos 1-3 ocurre una excepción 13 (Violación general de protección).

3) Cualquier violación de privilegio del selector indicado en el operando no causa una excepción 13, en vez de ello, el indicador ZF se pone a cero.

Nedstat Counter