DMT (Discrete Multitone Technology)

Ensamblador. Excepciones. Coprocesador. Puertos VGA (Video Graphics Array). DMA (Direct Memory Access). Mecanismo de modulación estándar en redes

  • Enviado por: Ahucha
  • Idioma: castellano
  • País: España España
  • 40 páginas
publicidad

desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina de tratamiento de excepciones general, donde se produce la finalización de la tarea que generó esta excepción.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 9: Segmento del Coprocesador sobrepasado

Esta excepción se produce en el modo protegido cuando el 80386 detecta una violación de la página o el segmento mientras se transfiere la parte de operandos del coprocesador al NPX. Cualquier tarea que produzca esta excepción será eliminada de la memoria.

Los atributos de este descriptor son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina de tratamiento de excepciones general, donde se produce la finalización de la tarea que generó esta excepción.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 10: TSS inválido

Esta excepción se produce cuando se encuentra un descriptor de TSS inválido. Esta excepción sólo la podrá producir la tarea 0 o código de DMT, ya que es la única que referencia a los descriptores TSS para realizar conmutaciones de tarea.

Los atributos de este descriptor son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que imprime un mensaje por pantalla indicando “TSS inválido”.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 11: Segmento no presente

Esta excepción se produce cuando se referencia a un descriptor que tiene el bit presente a cero. Esta excepción sólo la podrá producir la tarea 0 o código de DMT, ya que es la única que referencia a los descriptores de la GDT.

Los atributos de este descriptor son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que imprime un mensaje por pantalla indicando “Segmento no presente”.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 12: Excepción de Pila

Esta excepción se produce cuando se intenta violar los límites establecidos para la pila. Cuando se produce esta excepción es muy difícil llevar al sistema a un estado estable, ya que antes de producirse seguramente se han realizado otras operaciones inválidas que no han sido detectadas por DMT. Una excepción de pila por parte de una tarea provocará no sólo que la tarea que se estaba ejecutando termine, sino que también producirá la terminación de DMT y la vuelta al modo real. Aunque se ha intentado eliminar únicamente la tarea que provocó la excepción, sin finalizar las otras tareas, se ha observado que tras producirse esta excepción se produce un bloqueo de todo el sistema al poco tiempo de eliminar la tarea “maligna” de la memoria. Por tanto, hemos optado por finalizar todas las tareas de forma abrupta antes de que se produzca el reset del ordenador.

Los atributos de este descriptor son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que imprime un mensaje por pantalla indicando “Excepción de pila” y que termina la ejecución de DMT.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 13: Excepción de Protección General

Esta excepción es la que más se produce en el sistema. Por ejemplo, cada vez que una tarea solicita un servicio del MS-DOS a través de la instrucción int 21h, se produce una excepción de protección general. Esta excepción es tratada por el monitor de V86, del que hablamos anteriormente, que detectará la instrucción que provocó a excepción y llevará a cabo una acción específica. El monitor de V86 será expuesto detalladamente mas adelante.

Los atributos de este descriptor son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que contiene el código correspondiente al monitor de V86, que se encargará de gestionar la excepción para que la tarea siga adelante

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 14: Falta de página

Esta excepción se produce cuando una tarea en modo protegido (tarea 0), accede a una página que tiene el bit presente a cero.

Los atributos de este descriptor son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que imprime un mensaje por pantalla indicando “Falta de página”.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 15: Excepción Reservada

Descriptor 16: Error del Coprocesador

Esta excepción se produce cuando el 80386 detecta una señal mandada por el 80287 o del 80387 en el pin ERROR.

Los atributos de este descriptor son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina de tratamiento de excepciones general, donde se produce la finalización de la tarea que generó esta excepción.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 17: Chequeo de Interrupción

Descriptor 18-31:Excepciones Reservadas por el 80386

Descriptores 32: Interrupción enmascarable 0, Temporizador

Esta interrupción se produce en el 80386 unas 18,2 veces por segundo. Esta interrupción es utilizada por el 80386 para refrescar la memoria y realizar otras operaciones críticas. Cada una de estas 18,2 interrupciones por segundo se les suele llamar ticks.

Bajo DMT se intentó utilizar esta interrupción para despachar el tiempo de atención de la CPU a cada tarea, es decir, cada vez que se producía esta interrupción se producía una conmutación de tarea. Se observó que dar a cada una de las tareas un ticks del reloj era demasiado tiempo, y se observaba la ejecución de cada tarea de forma intermitente. Por ejemplo, si teníamos dos tareas ejecutándose y una de ellas ejecutaba el comando dir del MS-DOS, los archivos del directorio se mostraban en bloques de forma discontinua, no logrando que todos los archivos se mostraran por pantalla de una forma suave y continua. Por tanto necesitábamos algún componente hardware que produjera más interrupciones por segundo y ofrecer a cada tarea un tiempo menor de atención de la CPU. Tras mucho investigar encontré que todos los PCs modernos poseían un reloj de tiempo real que mantenía la hora en el sistema y que éste se podía programar por el usuario para que produjera una interrupción tantas veces por segundo como deseaba el usuario. El descriptor 40 maneja las interrupciones provocadas por el reloj de tiempo real.

Los atributos del descriptor 32 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que realiza una llamada a la rutina original de esta interrupción antes de cargar DMT en memoria.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 33: Interrupción enmascarable 1, Teclado

Cada vez que se pulsa y libera una tecla del teclado, el 80386 recibe esta interrupción.

En DMT se ha redireccionado esta interrupción con el fin de que la tecla que se pulse o libere vaya a parar a la tarea que está en primer plano. En el módulo NewIRQ.asm se describe detalladamente como se ha realizado esto.

Los atributos del descriptor 33 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que se encarga de llevar la pulsación o liberación de la tecla que provocó la interrupción a la tarea que está en primer plano.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 34: Interrupción enmascarable 2, 2º controlador de interrupciones

El AT posee dos controladores de interrupciones para controlar todos los periféricos que se pueden conectar al ordenador. Las interrupciones que se producen en el segundo controlador de interrupciones, provoca una interrupción 2 en el primer controlador de interrupciones, así tenemos ambos controladores conectados en cascada. Cada vez que se produce una interrupción 2 en el primer controlador de interrupciones, se ha de mirar el segundo controlador de interrupciones para saber cual de fue la patilla que provocó tal interrupción en el anterior controlador.

Las interrupciones enmascarables del segundo controlador se mapean a partir del descriptor 40 de la IDT.

Descriptor 35: Interrupción enmascarable 3, Puerto serie 2 (COM2)

Esta interrupción es producida en el 80386 por el primer puerto serie. Los ratones del COM2 producen una interrupción de este tipo cada vez que se produce una acción sobre ellos.

En DMT se ha redireccionado la interrupción del puerto serie para que cada vez que se produzca una interrupción del puerto serie, se llame a la rutina de tratamiento de la tarea que está en primer plano.

Los atributos del descriptor 35 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que se encarga de llamar a la rutina de tratamiento de las interrupciones del COM2 para la tarea que está en primer plano.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 36: Interrupción enmascarable 4, Puerto serie 1 (COM1)

Esta interrupción es producida en el 80386 por el primer puerto serie. Los ratones del COM1 producen una interrupción de este tipo cada vez que se produce una acción sobre ellos.

En DMT se ha redireccionado la interrupción del puerto serie para que cada vez que se produzca una interrupción del puerto serie, se llame a la rutina de tratamiento de la tarea que está en primer plano.

Los atributos del descriptor 36 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que se encarga de llamar a la rutina de tratamiento de las interrupciones del COM1 para la tarea que está en primer plano.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 37: Interrupción enmascarable 5, Puerto paralelo 2

Bajo el puerto paralelo se puede conectar un dispositivo como puede ser una impresora. En DMT no se ha tenido en cuenta que varias tareas accedan al mismo tiempo a la impresora, por lo que no se asegura que la impresión sea la correcta cuando varias tareas imprimen al mismo tiempo. La interrupción 5 del primer controlador de interrupciones no ha sido modificada por DMT y simplemente realiza una llamada al controlador antiguo cuando se produce esta interrupción.

Los atributos del descriptor 37 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que se simplemente llama a la rutina original de esta interrupción.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 38: Interrupción enmascarable 6, Disquete

Cada vez que se produce un acceso al disquete se produce una interrupción de este tipo. DMT no se hace cargo de implementar una rutina de 32 bits en modo protegido para accesos a disquetes. Por tanto se realiza una llamada al controlador de interrupciones antiguo en modo real cada vez que se produce un acceso al disquete.

Los atributos del descriptor 38 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que se simplemente llama a la rutina original de esta interrupción.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 39: Interrupción enmascarable 7, Puerto paralelo 1

Bajo el puerto paralelo se puede conectar un dispositivo como puede ser una impresora. En DMT no se ha tenido en cuenta que varias tareas accedan al mismo tiempo a la impresora, por lo que no se asegura que la impresión sea la correcta cuando varias tareas imprimen al mismo tiempo. La interrupción 7 del primer controlador de interrupciones no ha sido modificada por DMT y simplemente realiza una llamada al controlador antiguo cuando se produce esta interrupción.

Los atributos del descriptor 39 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que se simplemente llama a la rutina original de esta interrupción.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 40: Interrupción enmascarable 8, Reloj de Tiempo Real

El reloj de tiempo real puede ser programado por el usuario para que se produzca una interrupción de este tipo tantas veces como se desea por segundo. Al disponer de esta característica, el reloj de tiempo real en DMT se ha reprogramado para que se produzcan 1024 interrupciones por segundo. Ahora ya tenemos una buena forma de contar el tiempo CPU que se cede a cada tarea entre conmutación y conmutación de tareas.

Al tiempo que se le cede a una tarea para ser despachada por la CPU se llama quantum. El quantum para cada tarea bajo DMT corresponden a 4 interrupciones dadas por el reloj de tiempo real, es decir, de esas 1024 interrupciones que produce el reloj por segundo, cada grupo de 4 interrupciones forman un quantum.

Cada vez que se produce una interrupción del reloj de tiempo real, se llama a un procedimiento que se llama despachador de tareas y que se encarga de ver si el quantum de tiempo para la tarea que estaba ejecutándose ha terminado. El despachador también se encarga de ver si una tarea puede ser conmutada o no, ya sea porque se esté ejecutando sola o porque esté realizando una operación de disco u otra acción crítica. El despachador se explica detalladamente en el módulo despacha.asm.

Los atributos del descriptor 40 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que se encarga despachar el tiempo de solicitud de CPU a cada tarea.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptores 41-45: Libres para tarjeta de expansión

El segundo controlador de interrupciones tiene ciertas patillas libres en el que se pueden conectar diversos dispositivos como pueden ser el ratón del tipo PS/2, Streamer, CD-ROM, etc.

En DMT sólo se ha implementado una rutina para el tratamiento de los ratones del tipo PS/2 que se anclan en el pin 4 del segundo controlador de interrupciones. Para el resto de interrupciones se llaman al controlador que había por defecto antes de ejecutar DMT.

Los atributos de estos descriptores son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de estas excepciones apuntan a una rutina que se encarga de llamar al controlador antiguo de interrupciones.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Descriptor 46: Interrupción enmascarable 14, Disco Duro

Cada vez que se produce un acceso al disco duro se produce una interrupción de este tipo.

Para evitar el problema que surgía al conmutar una tarea que estaba realizando un acceso a disco, se intentó redireccionar la interrupción 14 del segundo controlador de interrupciones, pero el problema continuaba ya que se producían varias interrupciones de este tipo para realizar una transferencia de disco. Así se optó por redireccionar la interrupción de disco de la BIOS que realizaba la transferencia completa de datos a disco.

Los atributos del descriptor 46 son los siguientes:

  • Selector: SelCode32. Cada vez que se produzca esta excepción, el procesador ejecutará el código que se encuentra en el segmento de código protegido de 32 bits, referenciado por SelCode32. El desplazamiento específico dentro del segmento de este segmento de código viene dado por el atributo siguiente. Si la excepción se produce bajo el modo V86 se prepara la pila para poder realizar la llamada de la interrupción original bajo este modo.

  • Desplazamiento: El desplazamiento de esta excepción apunta a una rutina que llama al controlador original de disco duro para realizar las transferencias disco-memoria.

  • Nivel de privilegios: Cero. Este descriptor sólo puede ser utilizado por un usuario que tenga el nivel de privilegios cero. En DMT el único “usuario” que posee el nivel de privilegios cero es la tarea cero.

  • Bit presente activado. La rutina de tratamiento de la excepción se encuentra cargada en memoria.

Estos son todos los descriptores que DMT maneja para realizar su trabajo. Todos los atributos de los descriptores son rellenados, así como el valor base y desplazamiento para que apunten a su rutina de tratamiento de excepción.

  • Paso 7: Inicializar el Directorio y Tablas de páginas

  • El directorio de páginas y las tablas de páginas componen el mecanismo de paginación en el 80386. A través del mecanismo de paginación hacemos corresponder las direcciones lineales, de la segmentación, con direcciones físicas de memoria.

    Toda tarea V86, y tareas en modo protegido, accederá a la memoria indicando una dirección lineal. Parte de esta dirección lineal se utilizará para acceder al directorio de páginas y obtener de allí una tabla de páginas. Otra parte de la dirección lineal se utilizará para especificar una página física dentro de la tabla de páginas obtenida anteriormente. Por último otra parte de la dirección lineal se utiliza para indicar un desplazamiento dentro de la página física obtenida anteriormente. Así podemos decir que una dirección lógica tiene la estructura que se muestra en la figura 8.3.

    Figura 8.3. Formato de una dirección lineal

    El 80386 sólo puede ver las direcciones físicas que son mapeadas por el directorio y las tablas de páginas. Así si tenemos un directorio con un conjunto de tablas de páginas que sólo mapean el primer Mbyte físico, no podremos acceder a ninguna dirección de memoria más allá del primer Mbyte y esto es cierto para todas las tareas del sistema, incluso aquellas que poseen el nivel de privilegios cero. ¿Cual es la forma que tenemos para dar a cada tarea un espacio de direcciones diferentes para que no haya interacciones entre ellas? La respuesta nos la da la misma definición de directorio y tablas de páginas. Para tener espacios de direcciones diferentes debemos de tener un directorio con sus tablas de páginas diferentes para cada tarea y cada uno de estos directorios y tablas de páginas han de mapear direcciones físicas de memoria diferentes.

    En nuestro Multitasker, DMT, cada vez que se crea una tarea V86 debemos de crear al mismo tiempo un directorio de páginas y varias tablas de páginas para mapear el espacio de direcciones físicas de la nueva tarea. Si tenemos cuatro tareas V86 ejecutándose concurrentemente bajo DMT debemos de tener cuatro directorios de páginas con sus tablas de páginas, uno para cada tarea. Los directorios mapearán un espacio de direcciones físicas diferentes para cada tarea, de este modo no habrá interacciones entre ellas ya que el procesador no puede “ver” mas allá de las direcciones mapeadas por un directorio de páginas.

    Vamos a poner un ejemplo de lo que podría ser una instancia de DMT, es decir, vamos a suponer que un usuario va a poner en marcha varias tareas V86 bajo DMT. Supongamos que el usuario tiene cargada dos tareas en memoria, tarea A y tarea B. Como no queremos que las tareas se “machaquen” unas a otras debemos de tener dos directorios de páginas, uno para cada tarea, de forma que cada directorio de páginas mapee un espacio de direcciones físicas diferentes para cada tarea. Supongamos que la tarea A se ejecuta en el segundo Mbyte de memoria física, mientras la tarea B se ejecuta en el tercer Mbyte de memoria física. El directorio de páginas de la tarea A ha de mapear las direcciones físicas que van desde el segundo Mbyte al tercer Mbyte de memoria. El directorio de la tarea B mapeará las direcciones que van desde el tercer Mbyte hasta el cuarto Mbyte. De esta forma cada vez que la CPU conmuta a la tarea A, cambiará el directorio de páginas actual por el directorio de la tarea A, con lo que la tarea A no tendrá ocasión de acceder a una dirección física que no sea mapeada por su directorio y de esta forma se dice que el resto de las tareas que están en memoria están protegidas.

    Otro aspecto importante referente a la paginación es que debemos de hacer creer a cada tarea V86 que se está ejecutando en el primer Mbyte de memoria física, aunque a través del directorio de páginas se mapee en una dirección mas allá del primer Mbyte. Esto es debido a que todas las tareas V86, que son programas de MS-DOS, siempre han sido ejecutados y creados para ejecutarse en el primer Mbyte de memoria física ya que el MS-DOS es la única zona de memoria donde ejecuta sus programas. Para conseguir esto, las tareas generarán como siempre sus direcciones en el rango de cero a un Mbyte. A través del directorio de páginas y las tablas de páginas, mapearemos las direcciones lineales correspondientes al rango anterior, de 0 a un Mbyte, a direcciones físicas totalmente diferentes por encima del primer Mbyte. Con este mecanismo de paginación, las tareas no son conscientes de ello y creen que se están ejecutando en el primer Mbyte de memoria física, aunque realmente se estén ejecutando en el quinto Mbyte de memoria por ejemplo. En la figura 8.4 mostramos como una tarea V86 genera direcciones lineales y estas son mapeadas a direcciones físicas mas allá del primer Mbyte.

    Como se comentó en el capítulo “El modo protegido”, cada entrada del directorio de página apunta a una tabla de páginas que es capaz de direccionar 4 Mbytes. Por tanto, con una sola tabla de páginas podemos mapear la zona de memoria física correspondiente a una tarea V86, ya que cada tarea V86 sólo necesita un Mbyte físico para ejecutarse. Si miramos el código fuente de DMT, observaremos que cada directorio de páginas, para cada tarea V86, consta de varias tablas de páginas y ¿por qué varias tablas de páginas si con una tenemos de sobra para mapear la memoria de una tarea?. La primera tabla de páginas se utiliza para mapear la memoria física correspondiente a una tarea. El resto de las tablas de páginas se incluye en todos los directorios de páginas de cada una de las tareas. Estas tablas de páginas mapean toda la memoria física del ordenador y son utilizadas por el código monitor de DMT, para poder realizar sus tareas. Pero si cada tarea V86 tiene en su directorio tablas de páginas que mapean toda la memoria ¿podrá acceder cada tarea a cualquier dirección de memoria? La respuesta es no. Las tareas V86 pueden generar sólo direcciones de cero a un Mbyte a través del mecanismo segmento:desplazamiento. El directorio mapeará su primera entrada, correspondiente a las direcciones lógicas de cero a cuatro Mbytes, a una tabla de páginas que mapea la dirección física de cada tarea. La tarea V86 no podrá generar direcciones mayores de un Mbyte y por tanto no podrá acceder a la segunda entrada del directorio y siguientes, con lo que no podrá acceder a las direcciones de memoria de otra tarea V86 o al código monitor de DMT.

    figura 8.4. Ejemplo de traducción lineal a físico en tarea V86

    La segunda entrada del directorio de páginas mapea los primeros cuatro Mbytes físicos de memoria. En el primer Mbyte físico se encuentra el código de DMT y por ello ha de estar presente en todos los directorios de páginas. Cuando se produce una excepción en una tarea V86, buscamos la dirección de la rutina de tratamiento para esa excepción en la IDT. Todas las entradas de la IDT mapean su rutina de tratamiento de excepción a partir de la dirección base 400000h, que corresponde con el valor de 4 Mbytes, es decir, que se mapea a partir de la segunda entrada del directorio. Con esto, DMT podrá ejecutar el código de las rutinas para cada una de las excepciones desde la zona de direcciones de cada tarea V86. En la figura 8.5 puede verse el mapeado de memoria que realiza cada uno de los directorios de cada tarea V86.

    Una vez que hemos explicado cómo es la estructura del directorio de páginas y de las tablas de páginas bajo DMT, el siguiente paso en el código fuente es la de rellenar las entradas del directorio y de las tablas de páginas para la tarea 0, es decir, para la tarea que va a iniciar el modo protegido.

    Cada tabla de páginas posee una longitud de 4 Kb, estos cuatro Kbytes se obtienen de multiplicar los 4 bytes que forman cada entrada de una tabla de páginas con las 1024 entradas que posee cada tabla de páginas. La dirección de comienzo de una tabla de páginas ha de ser múltiplo de 4 Kbytes, es decir, solo podemos crear tablas de páginas en la dirección 0, 4096, 8192, etc. El directorio de páginas también ha de comenzar en una dirección que sea múltiplo de cuatro Kbytes, aunque los manuales no dicen que el directorio tenga que comenzar en una dirección múltiplo de 4 Kbytes.

    rafaasd

    asd

    Figura 8.5. Estructura del directorio y tablas de páginas en DMT

    Dependiendo de la memoria que haya instalada en el ordenador, DMT creará más o menos tablas de páginas para poder mapear toda la memoria física disponible. La tabla de páginas que se corresponde con la primera entrada del directorio (para direcciones lineales de 0 a 4 Mbytes) posee los atributos de lectura/escritura y usuario. Los primeros 4 Mbytes lineales tienen el atributo usuario activo para que cuando se conmute a V86 para realizar un servicio del MS-DOS o BIOS solicitado por DMT, no se produzcan excepciones de páginas por acceder una tarea de privilegios 3 a una página de memoria. El resto de las tablas de páginas, que mapean el primer Mbyte de memoria físico y el resto de memoria disponible en el ordenador, sólo podrán ser usadas por tareas que tengan el nivel de privilegios supervisor. Como sólo la tarea 0, que es el código de DMT, posee el nivel de privilegios supervisor podrá acceder a toda el espacio de direcciones de memoria física.

    El rellenado de todo el directorio de páginas y las tablas de páginas es una tarea bastante fácil, incluso desde el ensamblador. Lo único que debemos de hacer es copiar los atributos de las entradas de las tablas de páginas en cada una de las tablas de páginas que conforman el directorio de páginas.

  • Paso 8: Inicializar TSS de DMT

  • Antes de entrar al modo protegido, necesitamos crear el TSS de la primera tarea. Esta tarea no es más que la que inicializa el modo protegido y tiene por defecto el nivel de privilegios cero. Esta tarea será el código de DMT que se encargará de preparar el resto de las estructuras para poder cumplir su objetivo, lograr la multitarea bajo MS-DOS.

    No es necesario rellenar todos los campos del TSS para la tarea 0, únicamente vamos a rellenar aquellos que son absolutamente necesarios para entrar al modo protegido. Los campos que inicializamos del TSS son los siguientes:

    • Mapa de permisos de accesos a puertos de E/S: Como la tarea 0 ha de poseer el máximo nivel de privilegios, debemos de darle permiso de acceso a todos los puertos de E/S. Para ello debemos de apuntar el campo de permisos de E/S del TSS a una zona de memoria inicializada con 8 Kb a ceros.

    • Directorio de páginas (CR3): Debemos de rellenar en el TSS el campo CR3, que indica la dirección del directorio de páginas para la tarea 0. Debemos de saber la dirección donde hemos creado el directorio de páginas para poder rellenar este campo.

    • Campo SS y ESP: Debemos de indicar la dirección lineal de la pila para la tarea 0. Estos campos han de apuntar a una zona de memoria que esté libre para poder usarla como pila.

    No es necesario rellenar más campos para la tarea 0, ya que cuando se realice la primera conmutación de tareas, la tarea 1, el 80386 rellenará el resto de los campos del TSS de la tarea 0.

    Hasta este punto hemos preparado todas las estructuras necesarias para conmutar el procesador al modo protegido, así que vamos a ello.

  • Paso 9: Entrar a modo protegido de 16 bits

  • Antes de entrar en modo protegido de 32 bits, vamos a hacerlo entrando primero en modo protegido de 16 bits y desde allí vamos a activar la paginación para entrar luego en modo protegido de 32 bits y ejecutar entonces DMT.

    Para entrar a modo protegido debemos de realizar las siguientes acciones:

    • Cargar la dirección de comienzo de la IDT en el registro IDTR: Como comentamos antes, la IDT es de vital importancia para la manipulación de excepciones desde el modo protegido, por tanto debemos de cargar el registro IDTR con la dirección de la IDT que creamos antes. Para cargar el registro IDTR debemos de usar la instrucción privilegiada lidt direccionIDT.

    • Deshabilitar las interrupciones enmascarables: Para que no se produzcan interrupciones cuando se vaya a conmutar a modo protegido, debemos de deshabilitar las interrupciones enmascarables para evitar posibles anomalías que se puedan producir antes de entrar a modo protegido. Para deshabilitar estas interrupciones, debemos de utilizar la instrucción cli desde el ensamblador.

    • Cargar la dirección de la GDT en el registro GDTR: Para poder acceder a la memoria desde el modo protegido el 80386 referencia continuamente el contenido de la GDT, por tanto el 80386 necesita tener en un registro la dirección de comienzo de la GDT. El registro donde debemos de guardar la dirección de la GDT es el GDTR. Para cargar el registro GDTR debemos de utilizar la instrucción privilegiada lgdt direccionGDT.

    • Habilitar el bit PE del registro CR0: Cuando activamos el bit PE del registro CR0 el procesador conmuta inmediatamente a modo protegido. Llegados a este punto, es el momento de activar el bit PE y entrar a modo protegido sin ningún problema.

    • Limpiar la cola de prebúsqueda de instrucción: Cuando entramos en modo protegido, el procesador seguramente haya descodificado la siguiente instrucción desde el modo real. Como la instrucción se ha descodificado desde el modo real y va a ser ejecutada en modo protegido se puede producir una ejecución errónea de la instrucción. Para solucionar este problema debemos de realizar un salto inmediatamente después de entrar en modo protegido, de esta forma la cola de prebúsqueda quedará limpia. Debemos de realizar el salto dentro de un segmento de 16 bits para conmutar a modo protegido de 16 bits.

    En la tabla 8.6 podemos ver la pequeña rutina de DMT que conmuta el procesador a modo protegido.

    figura 8.6. Rutina para entrar a modo protegido

  • Paso 10: Entrar a modo protegido de 32 bits

  • Ahora que estamos en modo protegido de 16 bits, debemos de acceder a la memoria a través del mecanismo de selectores de la GDT. Para conmutar el procesador a modo protegido de 32 bits con la paginación habilitada realizamos los siguientes pasos:

    • Cargar el registro CR3 con la dirección de comienzo del directorio de páginas con el que deseamos que comience la paginación.

    • Activar el bit PG del registro CR0: Una vez que hemos cargado el registro CR3 debemos de poner el bit PG a 1 y con ello la paginación ya está activa. Hay que mencionar que para que no se produzca una caída del sistema justo al activar la paginación, necesitamos que la direcciones lógicas se mapeen igual que las direcciones físicas justo en el punto que se activa la paginación, es decir, el código de inicialización de la paginación ha de poseer la misma dirección lógica que física.

    • Limpiar cola de prebúsqueda de instrucción.

    • Cargar los registros de segmentos con valores correctos de selectores.

    • Cargar el registro TR con el selector del TSS de la tarea 0.

    • Realizar un salto hacia un segmento de código de 32 bits.

    Tras esto ya tenemos en nuestras manos una máquina de 32 bits capaz de realizar multitarea y de direccionar hasta 4 Gbytes de memoria.

  • Paso 11: Últimos retoques antes de ejecutar la 1ª tarea

  • Una vez que entramos en modo protegido de 32 bits para ejecutar la primera tarea debemos de tener correctamente instalado los diversos manejadores de excepciones así como el código monitor de DMT. En las siguientes secciones se comentarán estos manejadores de excepción y el monitor de V86 de DMT. En esta sección supondremos que todo lo anterior está preparado, así que nos lanzamos para poner en funcionamiento la primera tarea y a partir de ella se podrán ejecutar el resto de las tareas.

    Los nombres de las tareas V86

    En DMT toda las tareas que se están ejecutando tienen un nombre. El nombre de la tarea coincide con el nombre del programa DOS que está ejecutando. Por ejemplo, si tenemos la tarea A y la tarea B ejecutándose y la tarea A ejecuta el programa Scandisk.exe del MS-DOS y la tarea B llama al programa Debug.exe, la tarea A se llamará “SCANDISK.EXE” y la tarea B se llamará “DEBUG.EXE”.

    Al iniciar cada tarea, lo que DMT hace es crear un nuevo shell a través del programa Command.com, todas las tareas se llamarán inicialmente “COMMAND.COM”. Cada tarea tiene un área de memoria reservada dentro del código de DMT para guardar su nombre de tarea. DMT inicializa este área de memoria con el nombre “COMMAND.COM” para cada tarea.

    Los registros de segmento virtuales de cada tarea

    Como se verá posteriormente, cada tarea V86 es inicialmente una tarea en modo protegido que conmutará a tarea V86 antes de crear un nuevo shell a través de command.com. Toda tarea V86 necesita tener unos registro de segmento virtuales, antes de conmutar a modo V86, que referencian segmentos de memoria para el modo V86.

    Cuando se crea una nueva tarea, ésta es una tarea en modo protegido, que necesitará realizar llamadas al MS-DOS o a la BIOS antes de conmutar a modo V86. Para poder realizar estas llamadas la tarea ha de poseer unos registros de segmento correspondientes al modo real para que el servicio solicitado pueda tener una finalización correcta. Como se ha comentado hasta ahora, los programas en modo protegido hacen referencias a la memoria a través de la GDT. Como los programas en modo protegido necesitan llamar a servicios del DOS o de la BIOS que fueron creados en modo real, necesitamos un conjunto de registros que se correspondan con direcciones de segmento del modo real y no referencien selectores del modo protegido. Estos registros son los registros virtuales de segmento.

    En DMT antes de crear la primera tarea se inicializarán el contenido de todos los registros virtuales correspondientes a cada tarea V86.

    Habilitar el reloj de tiempo real y poner en marcha el “Despachador de Tareas”

    Para que el tiempo de CPU se pueda repartir entre las diversas tareas que se están ejecutando, necesitamos algo que nos mida el tiempo que cada tarea tiene posesión de la CPU y así saber si su tiempo de CPU ha concluido.

    A través del reloj de tiempo real podemos conseguir que se produzca una interrupción tantas veces como queramos por segundo y poder medir el tiempo de posesión de CPU que lleva una tarea.

    El reloj de tiempo real, por defecto, no genera ninguna interrupción por segundo. El programador ha de reprogramar el reloj de tiempo real para que se produzcan tantas interrupciones como desea por segundo. Para realizar tal operación debemos de activar un bit del puerto de estado B del reloj de tiempo real. Una vez que activamos dicho bit se producirán por defecto 1024 interrupciones por segundo, que son suficientes para poder “despachar” el tiempo de CPU entre las diversas tareas. El tiempo de despacho de la CPU o el tamaño del quantum para cada tarea es de cuatro interrupciones de CPU. En la figura 8.7 se muestra un ejemplo de cómo DMT despacha tres tareas en memoria.

    Figura 8.7. Ejemplo de “despacho” de la CPU en DMT

    Inicializar mapa de Memoria Extendida

    Aunque DMT no soporte un sistema de memoria virtual, intercambiando páginas de memoria a disco, si posee muchas estructuras que están orientadas a un sistema de memoria virtual. Ya que inicialmente se pensó instalar uno de estos sistemas dentro de DMT, se conservan en el programa final muchas de estas estructuras orientadas a la memoria virtual. Por falta de tiempo, y sobre todo por falta de documentación, no se ha podido implementar un sistema de memoria virtual para lograr la ejecución simultánea de muchas mas tareas bajo DMT.

    El mapa de memoria extendida consta de un vector que guarda información acerca de bloques de memoria extendida. Los atributos de cada uno de los elementos del vector son, por ejemplo, número de la tarea que posee el bloque, privilegio del bloque, byte de accedido, etc. Este vector sirve para que el sistema de memoria virtual pueda saber que bloque puede pasar a disco en caso de falta de memoria.

    En este punto del programa nuestra misión será únicamente rellenar este vector con valores nulos para indicar que todos los bloques de memoria extendida están libres.

    Crear la primera tarea

    Por último ya sólo tenemos que crear la primera tarea y a partir de ella el usuario podrá crear el resto de las tareas que le sean necesarias.

    Para crear una tarea debemos de crear su directorio de páginas y tablas de páginas, que mapearán una zona de memoria que no esté utilizada por ninguna tarea y de este modo evitaremos conflictos entre las diversas tareas.

    Una vez creado el directorio y las tablas de páginas, debemos de rellenar el descriptor del TSS correspondiente a la tarea que se va a crear. El TSS contendrá los valores de los registros de segmento inicializados, así como el punto de entrada del programa una vez que se le ceda el control de la CPU.

    En DMT se ha definido un procedimiento que se llama CrearTarea que realiza las dos labores anteriores. Este procedimiento será descrito posteriormente.

    A partir de la creación de la primera tarea, el usuario podrá crear nuevas tareas a través del despachador de tareas.

  • Paso 12: De vuelta al modo real. Terminar ejecución de DMT

  • En esta sección queremos comentar como se ha de finalizar DMT. Una vez que el usuario ha disfrutado de los servicios de DMT y quiere terminar su ejecución, se ha de conmutar el procesador al modo real y restaurar todo aquello que DMT ha modificado.

    DMT puede terminar su ejecución por orden explícita del usuario o porque se ha producido una excepción de pila dentro de una tarea V86. En ambos casos se produce la misma salida del programa, excepto que en caso de terminar por una excepción de pila, se mostrará un mensaje al usuario indicando “Excepción 12: Falta de Pila”.

    Para salir de DMT, primero tenemos que poner el procesador en modo protegido de 16 bits. Para ello debemos de realizar un salto hacia un segmento de código que tenga como atributos segmento de 16 bits. Una vez hecho esto, debemos de descargar el registro IDTR para que se utilicen las interrupciones habituales del modo real.

    El próximo paso será deshabilitar la paginación. Para ello pondremos el bit PG del registro CR0 a cero. Hay que recordar que en estos momentos las direcciones lógicas han de ser iguales a las direcciones físicas.

    Una vez hecho esto, podemos poner el procesador en modo real a través del bit PE del registro CR0. Debemos de limpiar la cola de prebúsqueda de instrucción a través de un salto para que no se produzca un error al ejecutar la primera instrucción desde el modo real.

    Como los registros de segmento contienen valores de selectores del modo protegido, debemos de cargarlos con valores correctos del modo real. Estos valores correctos corresponden con el segmento de código del segmento de 16 bits de DMT.

    El siguiente paso consiste en restaurar todas las interrupciones modificadas. A través de la reprogramación del PIC hacemos que se produzcan las interrupciones enmascarables que habían por defecto antes de entrar al modo protegido. Las interrupciones 15h, 13h y 33h que fueron modificadas para la ejecución correcta de DMT, han de ser restauradas y redirigirlas a su rutina de interrupción original.

    Por último debemos de liberar toda la memoria extendida alojada por DMT y acto seguido solicitamos el servicio “terminar programa” del DOS.

  • Emular un servicio del DOS desde el modo protegido

  • Uno de los procedimientos más importantes en DMT es el de emulación de un servicio del DOS o la BIOS desde el modo protegido. En DMT dicho procedimiento se llama INT_16 y su funcionamiento se describe a continuación.

    Como se sabe, para solicitar un servicio del DOS o la BIOS debemos de ejecutar la instrucción en ensamblador int n, donde n identifica el tipo de servicio. Si desde el modo protegido se ejecuta la anterior instrucción se ejecutará la rutina, en caso de que haya, correspondiente a la entrada n de la IDT. Por tanto si queremos que desde el modo protegido se soliciten los servicios igual que desde el modo real, debemos de crear una entrada en la IDT para cada número de interrupción existente en el DOS y la BIOS, lo que resulta un arduo trabajo. Una solución para remediar el anterior problema, consiste en realizar un procedimiento en modo protegido que conmuta a modo V86 ejecutando la rutina correspondiente a la interrupción en el modo real, es decir, si queremos cargar por ejemplo un fichero desde el modo protegido, llamaremos al procedimiento anterior y se conmutará el procesador al modo V86, donde se ejecutarán allí todas las instrucciones correspondientes al servicio del DOS solicitado.

    Debido a la importancia de tal procedimiento, vamos a describir como se ha implementado exactamente para poder comprender así como funciona realmente tal procedimiento.

    • El procedimiento es llamado desde el modo protegido y ha de poseer en la pila el número de la interrupción que se quiere emular antes de llamar al procedimiento.

    • Se han de salvar todos los registros de segmentos, registros de propósito general y todos aquellos otros registros que vayan a ser modificados en el procedimiento. Los registros que realmente se modifican son EFLAGS, gs, fs, ds, es, eax, ebx. En ensamblador quedaría del siguiente modo:

    • Luego tenemos que recargar algunos registros de segmentos para poder referenciar los datos necesarios que serán solicitados desde el procedimiento. El registro ds se cargará con el selector SelData32 para poder referenciar los datos de DMT y el registro gs se cargará con el selector SelZero16 para acceder al primer Mbyte de memoria física.

    • El siguiente paso es el de guardar en la pila el valor de SS y ESP que hay en el TSS, para que una vez que se haya terminado el servicio se recarguen los registros de pila correctamente. También se ha de poner en los campos SS y ESP el valor de la pila actual para que cuando se produzca una excepción desde el modo V86 se utilice la pila de actual.

    • El siguiente paso consiste en meter en la pila el valor de todos los registros de segmentos que tendrán una vez que se esté ejecutando la tarea en modo V86.

    • Una vez que haya terminado el servicio solicitado, hacemos que se ejecute la instrucción INT 0 para que se produzca una excepción desde V86. El código monitor se encargará de detectar que esa instrucción INT 0 corresponde a la finalización de un servicio y ejecutará una rutina para sacar todos los registros que se han metido anteriormente y seguir ejecutando la tarea en el punto posterior a la solicitud del servicio. Debemos de insertar en la pila V86 la dirección de la instrucción INT 0 para que una vez que se ejecute el IRET correspondiente a la finalización del servicio, se produzca una excepción desde el modo V86.

    • Por último debemos de colocar en la pila el contenido de los registros CS e EIP para que apunten a la rutina del servicio solicitado cuando se conmute a modo V86. Además de meter en la pila los dos registros anteriores, debemos de meter una copia del registro EFLAGS pero con el bit VM a 1 para que cuando se ejecute la instrucción IRETD se saque de la pila el registro EFLAGS y se produzca la conmutación a modo V86.

    mov eax, ss:esp[11*4] ; recuperamos el valor de EAX y EBX al

    mov ebx, ss:esp[12*4] ; entrar en este procedimiento

    iretd ; ¡conmutamos a Virtual 8086 Mode!

  • El monitor de V86

  • El monitor de V86 se va a encargar de tratar todas las excepciones que se produzcan por falta de protección general, ya sea desde el modo protegido o desde el modo V86. El monitor de V86 tiene como misión principal la de ejecutar un servicio del DOS o la BIOS desde el modo V86.

    Cuando una tarea en modo protegido conmuta a modo V86 para realizar un servicio, seguramente este servicio solicitado llame a otros servicios para poder ejecutarse. La llamada de estos últimos servicios provocarán excepciones desde la tarea V86, ya que la ejecución de la instrucción INT n desde el modo V86 provocará una falta de protección general. El monitor de V86 ha de detectar que tal excepción se produjo por intentar solicitar un servicio desde el modo V86 y deberá de realizar diversas operaciones para que el servicio pueda ser llevado a cabo y la tarea V86 termine su ejecución felizmente.

    Las tareas de las que se encarga el monitor de V86 en DMT son las siguientes:

    • Determinar si la excepción de protección general se provocó debido a una solicitud de un servicio desde el modo V86.

    • Terminar una tarea en modo protegido si provoca una excepción de protección general.

    • Terminar una tarea en modo V86 si intenta ejecutar una instrucción privilegiada.

    • Terminar una tarea en modo V86 si intenta ejecutar una instrucción inválida.

    • Terminar un servicio solicitado por una tarea en modo protegido al ejecutar el procedimiento, descrito anteriormente, INT_16.

    El código del monitor de V86 se ha anclado en la entrada 13 de la IDT “Falta de protección general”. Cada vez que el procesador emita una falta de protección general el monitor de V86 se pondrá en marcha para intentar “remediar” tal excepción. Al igual que hicimos con el procedimiento INT_16, vamos a describir detalladamente la implementación del monitor de V86 para comprender plenamente todas las posibilidades que ofrece.

    • Lo primero que hace el monitor de V86 es detectar si la excepción se produjo en modo V86 o en modo protegido, ya que el contenido de la pila varía si la excepción se produjo en un modo u otro. Si la excepción se produjo en modo protegido se debe a que el código de DMT es erróneo, por lo que deberemos finalizar el programa y corregir dicho error. Si por el contrario se produjo en modo V86, debemos de examinar la tarea V86 para ver que es lo que provocó tal excepción, esto se comenta en el paso siguiente.

    • Si la excepción se ha producido en modo V86, vamos a examinar en el segmento de código de la tarea V86 la instrucción que provocó tal excepción. Como el 80386 introduce en la pila el contenido del registro CS e IP de la tarea V86 cuando se produjo la excepción, podemos calcular a través de esos dos registros la dirección de la instrucción que provocó la excepción y leer de allí su código de instrucción. Una vez que leemos la dirección de la instrucción que provocó la excepción debemos de comprobar si esa dirección corresponde con la de la instrucción INT 0 que introdujo el procedimiento INT_16 en la pila para indicar que el servicio se ha completado. La rutina que se ejecuta cuando coincida con la dirección de la instrucción INT 0 que introdujo el procedimiento INT_16 se describirá posteriormente. Por ahora, suponemos que la excepción se ha producido por otra causa diferente en V86.

    V86_cont:

    shl ebx, 4 ; pasamos CS a lineal

    add ebx, ss:esp[2*4] ; EBX = dirección lineal CS:IP

    inc dword ptr ss:esp[2*4] ; apuntamos IP a la siguiente ; instrucción

    • El siguiente paso consiste en leer el código de la instrucción que provocó la excepción. Una vez que leemos el código de operación realizamos las siguientes comprobaciones:

  • Comprobar si la excepción se produjo al ejecutar la instrucción INT 3.

  • Comprobar si la excepción se produjo al ejecutar la instrucción INTO.

  • Comprobar si la excepción se produjo al ejecutar una instrucción de la forma INT n.

  • Si no es ninguno de los caso anteriores, se llamará a un procedimiento que se encarga de detectar si la excepción se produjo por acceder a un puerto de E/S que estaba bloqueado. Este procedimiento se comentará posteriormente.

  • Todas estas comprobaciones se han implementado del siguiente modo:

    • Supongamos que la excepción se ha producido al solicitar un servicio desde el modo V86. Las comprobaciones que hacemos ahora son las siguientes:

  • Ver si el servicio solicitado es ejecutar un nuevo programa (Función 4Bh de la interrupción 21h). Si es así se llamará a un procedimiento que se encarga de capturar la cadena ASCII del nombre del fichero y asignárselo a la tarea V86 que se está ejecutando. De esta forma la tarea V86 cambiará de nombre. Este procedimiento será descrito en el módulo INTemul.asm.

  • Comprobar si se ha solicitado un servicio para cambiar el modo de vídeo. En este caso, se llamará a un procedimiento que comprueba si el modo de vídeo corresponde con algún modo gráfico y en tal caso la tarea finaliza. Este procedimiento se describe en el módulo INTemul.asm.

  • Comprobar si se ha solicitado el servicio 87h de la interrupción 15h “Mover bloque de datos hacia/desde la memoria extendida”. Como este servicio conmuta el procesador al modo protegido, se producirá una excepción en la tarea V86 al solicitar este servicio. Por tanto este servicio ha de ser emulado para que una tarea V86 pueda mover datos desde la memoria extendida.

    • La rutina en ensamblador que realiza los pasos anteriores se muestra a continuación:

    • El siguiente paso consiste en preparar el procesador para que conmute nuevamente a modo V86 y ejecute la rutina del servicio solicitado. Una vez que haya concluido el servicio se ha de ejecutar la instrucción siguiente a la que provocó la excepción desde la tarea V86. Debemos de preparar por tanto el punto de entrada para la ejecución del servicio, así como la pila para que cuando se termine el servicio se vuelva al punto siguiente de la excepción. A continuación mostramos la rutina en ensamblador que realiza todo esto.

    • Hasta aquí el servicio ha sido emulado. En este punto vamos a comentar que es lo que hay que hacer cuando la excepción se ha producido por la ejecución de la instrucción INT 0 que introdujo el procedimiento INT_16 en la pila. Primero debemos de sacar los registros eax y ebx de la pila, que fueron introducidos por el código monitor. Tras realizar esto, los registros eax y ebx contendrán los valores que fueron devueltos por el servicio solicitado. Luego tenemos que sacar de la pila el registro CS e EIP ya que no van a ser utilizados. A continuación restauramos los registros virtuales de segmento de la tarea V86, ya que pueden haber sido modificados por el servicio. Por último restauramos el valor de los campos SS y ESP del TSS para que contengan valores correctos cuando se produzca otra excepción.

  • El módulo Excep07.asm

  • Este módulo contiene dos procedimientos que se encargan del manejo de la excepción 7 “Coprocesador no disponible” y de la excepción 1 “Excepción de depurado”.


  • Función TratarExc07

  • Función: TratarExc07.

    Descripción: Rutina para el manejo de la excepción 07.

    Entrada: Nada.

    Salida: Nada.

    Cuando se entra en modo protegido, cualquier intento de ejecutar una instrucción de inicialización del coprocesador sobre un ordenador sin coprocesador provocará una excepción de coprocesador. Cuando en modo real se intenta ejecutar una de estas instrucciones no se realiza ninguna acción y los registros de la CPU permanecen inalterados.

    Como muchos programas detectan la presencia de un coprocesador a través de la ejecución de una instrucción de inicialización del copro, estos programas provocarán una falta de coprocesador si se ejecutan en un ordenador sin coprocesador. Para remediar esta falta en modo protegido, debemos de instalar una rutina de manejo de la excepción 7 que se encargue de no hacer nada cuando se ejecuta una de estas instrucciones y permita la ejecución del programa que provocó la excepción de coprocesador.

    El procedimiento TratarExc07 se encarga de gestionar estas excepciones por falta de coprocesador. Cuando se ejecuta una instrucción de inicialización del coprocesador, como FINIT, FNINIT, FSTCW, etc., se producirá una excepción y este procedimiento se encargará de saltar la instrucción anterior de modo que no se realice ninguna acción en el procesador. Cuando nos saltamos la instrucción de inicialización del coprocesador, el 80386 mantendrá los registros tal y como estaban antes de saltarse la instrucción de inicialización de coprocesador, por tanto, el programa que ejecutó esa instrucción observará que los registros del 80386 no se han modificado, por lo que detectará que no hay ningún coprocesador instalado en el ordenador.

    El procedimiento TratarExc07 posee un mecanismo muy simple, tan solo comprueba cada una de las instrucciones de inicialización del coprocesador con la instrucción en curso y se salta el número de bytes que ocupa la instrucción. En el siguiente cuadro podemos ver una pequeña parte de este procedimiento.

  • Procedimiento TratarExc01

  • Procedimiento: TratarExc01.

    Descripción: Se encarga de gestionar la excepción 1.

    Entrada: Nada.

    Salida: Nada.

    Cuando la tarea que se está ejecutando es un depurador, esta excepción aparecerá cada vez que se ejecute una instrucción de ejecución paso a paso en el depurador. El 80386 provocará una excepción de depurado que ha de ser gestionada por DMT. Como no queremos preocuparnos de la rutina que hay que realizar para remediar esta excepción, se ha optado por llamar a la rutina de excepción de depurado que instala el depurador.

    Cada vez que se produce una excepción de depurado, DMT prepara el procesador para volver a modo V86 y desde allí ejecutar el código correspondiente a la interrupción 1 que ha colocado el depurador. Aunque esto es lo que se ha realizado en DMT se han presentado diversos problemas que no permiten el depurado del programa en curso.

    Cuando se está depurando un programa que tiene la tabla de símbolos completa, la depuración de tal programa se lleva a cabo normalmente. El problema aparece cuando se depura un programa que no posee tabla de símbolos y se realiza la ejecución paso a paso. Cuando mandamos al depurador que realice la ejecución paso a paso, el programa desemboca en una instrucción int 0 y se queda en un bucle infinito ejecutando la anterior instrucción. Si desea comprobar tal problema, ejecute DMT y cargue el depurador Turbo Debugger, por ejemplo, y depure un programa paso a paso. Creo que la intención de DMT de llamar a la rutina para el manejo de la interrupción 01 que instala el depurador es buena pero... no funciona. Por tanto, bajo DMT no se permite la depuración de un programa paso a paso, sin tabla de símbolos, en un depurador.

  • El módulo V86excep.asm

  • Este módulo contiene un único procedimiento que se encarga de gestionar las faltas de protección general que se producen cuando una tarea intenta acceder a un puerto de entrada/salida cuyo acceso a prohibido DMT.

  • Función V86_exc

  • Función: V86_exc.

    Descripción: Trata las excepciones en acceso de puertos de E/S en el modo V86.

    Entrada: Nada.

    Salida: Nada.

    Para que DMT ofrezca una convivencia pacífica de todas las tareas, ha de poseer mecanismos que detecten accesos a los puertos de E/S por las tareas V86 y gestionen tal acceso de tal modo que no interfiera en las demás tareas que se están ejecutando en memoria.

    Supongamos que tenemos dos tareas que se están ejecutando, tarea A y tarea B, la tarea A está en primer plano y la tarea B en segundo plano. Como la tarea A tiene el control total de la pantalla, no queremos que un intento de la tarea B de cambiar la resolución de la pantalla, por ejemplo, afecte a la visualización de la tarea A. Para solucionar el anterior problema debemos de bloquear todos los puertos de E/S pertenecientes a la pantalla, con el fin de detectar cuando una tarea en segundo plano intenta acceder a uno de estos puertos y realizar una modificación sobre ellos. Si el usuario quiere pasar a primer plano la tarea B, se deberá de inicializar la pantalla tal y como lo ha hecho la tarea B hasta ese momento. En el ejemplo de antes, si la tarea B cambia el monitor a la resolución de 320x200 en segundo plano, no queremos que interfiera en la pantalla si está en segundo plano. Cuando el usuario pasa a primer plano la tarea B, se ha de cambiar la resolución de la pantalla a 320x200 para que la tarea B se visualice tal y como fue programada.

    Los puertos que DMT bloquea son los correspondientes al DMA y a la tarjeta de vídeo. A continuación describimos cada uno de los dos tipos de bloqueo anteriores.

    Bloqueo de los puertos del DMA

    Las tareas V86 poseen un espacio de direcciones lineales diferentes a las direcciones físicas. Esto es posible a través del mecanismo de paginado y gracias a esto, es posible la ejecución de varias tareas V86 en memoria. El problema surge cuando intentamos utilizar el chip DMA para realizar una transferencia en una tarea V86. Ya que el chip DMA no nota la presencia del mecanismo de paginado activo, utiliza las direcciones lógicas dadas por la tarea V86 como direcciones físicas con lo que se produce un caos en el sistema.

    Supongamos que tenemos una tarea que se está ejecutando en el quinto Mbyte de memoria física. Esta tarea generará direcciones lineales que irán desde 0 hasta 1 Mbyte. Si la tarea V86 pasa una de estas direcciones lineales al chip DMA se producirá una transferencia de datos en el primer Mbyte y no en el quinto Mbyte, que sería lo correcto.

    Para que una tarea V86 que utiliza el DMA pueda ejecutarse correctamente, se ha de detectar cualquier acceso a este chip y convertir la dirección lineal en una dirección física correcta para la tarea V86.

    Las tareas V86 utilizan el DMA para realizar las transferencias de datos hacia/desde disquetes. En el programa fuente se puede ver como se detecta el acceso al chip DMA y cómo se convierte la dirección lineal a la dirección física correspondiente.

    Bloqueo de los puertos de la VGA

    Para permitir la visualización correcta de la tarea que está en primer plano, se ha de bloquear todos los puertos correspondientes a la VGA con el fin de que una tarea en segundo plano modifique el aspecto de la pantalla.

    Una tarea en primer plano puede realizar cualquier operación sobre los puertos de la VGA sin ningún impedimento de DMT. Las tareas en segundo plano pueden realizar también cualquier operación sobre los puertos de la VGA, pero estas operaciones no podrán ser visualizadas hasta que no se pasen a primer plano. Si tenemos una tarea en segundo plano que realiza una modificación sobre cualquier puerto de la VGA, DMT se encargará de guardar el acceso a dicho puerto en una variable con el fin de poder realizar la operación sobre el puerto anterior cuando se pase a primer plano la tarea.

    La tarjeta de vídeo VGA está formada por diversos registros que definen el aspecto de la imagen sobre la pantalla. Cada tarea V86 ha de poseer una variable para cada registro de la VGA, con el fin de guardar el acceso a los registros de la VGA en cada una de estas variables cuando están en segundo plano. Cuando se pasa una tarea a primer plano, DMT se encarga de rellenar cada registro de la VGA con el contenido de estas variables de esa tarea.

    El objetivo de la función V86_exc es la de guardar cualquier acceso a los registros de la VGA en una variable correspondiente para ese registro en cada tarea V86. Así cada tarea tendrá un conjunto de variables que indican el estado de cada uno de los registros de la VGA para su pantalla virtual. Cuando una tarea en segundo plano quiere leer el contenido de un registro de la VGA, leerá el valor de su variable correspondiente a dicho registro de la VGA.

    Como cada tarea V86 puede acceder a los registros de la VGA con instrucciones diferentes como OUT DX, AL, OUT DX,AX, etc., se ha de detectar el tipo de instrucción que provocó la excepción para poder guardar el acceso al registro de la VGA de la forma correcta. A continuación mostramos un pequeño fragmento de cómo se gestionan las excepciones debido al acceso de un puerto de la VGA.

    Así se ha implementado DMT 87

    Directorio Página Desplazamiento

    direcciones

    de 0 - 1Mbyte

    Tarea A

    Primer

    Mbyte

    Directorio

    Tablas Paginas

    Segundo

    Mbyte

    Memoria física

    Tarea en Ejecución

    de 8 - 12M

    de 4 - 8Mb

    de 0 - 4Mb

    Tablas de páginas

    Directorio de páginas

    Zona de

    memoria

    de la

    tarea V86

    Memoria física

    lidt qword ptr ds:IDT32dir ; cargamos registro IDTR

    lgdt qword ptr ds:GDT32dir ; cargamos registro GDTR

    mov eax, cr0 ; leemos la MSW

    or al, 21h ; habilitamos modo protegido

    mov cr0, eax ; ponemos la máquina en ¡PMode!

    jmp ActivarPG ; limpiamos cola de prebúsqueda

    Tarea B

    Tarea A

    Tarea C

    Tiempo del quantum 4 “ticks” del reloj

    4 ticks

    4 ticks

    4 ticks

    pushfd ; salvamos EFLGS en la pila

    push gs fs es ebx eax ; y todos los registros que van a ser modificados

    mov ax, SelData32 ; obtenemos selector del segmento de datos

    mov ds, ax ; referencia a los datos a través de DS

    mov ax, SelZero16 ; obtenemos selector del primer Mbyte físico

    mov gs, ax ; acceso al primer Mbyte a través de GS

    mov ebx, [Tactiva] ; EBX = numero de la tarea activa

    shl ebx, 2

    mov eax, [TSSXesp0+ebx] ; EAX = direccion del campo ESP0 del ; TSS

    push dword ptr gs:eax[4] ; salvamos el valor de SS

    push dword ptr gs:eax[0] ; salvamos el valor de ESP

    mov gs:eax[0], esp ; ponemos en el TSS el nuevo valor de

    mov gs:eax[4], ss ; ESP y SS

    push ds:[V86_gs+ebx] ds:[V86_fs+ebx] ds:[V86_ds+ebx] ds:[V86_es+ebx]

    mov eax, ds:[V86_sp+ebx] ; EAX = offset dentro de la pila V86

    mov ebx, ds:[V86_ss+ebx] ; guardamos el valor de SS (V86)

    push ebx

    shl ebx, 4 ; EBX = dirección física de la pila V86

    sub eax, 6 ; dejamos espacio en la pila para poder

    ; guardar EFlags, CS e IP al retornar

    ; de la instrucción INT desde V86

    push eax

    add ebx, eax ; EBX = dirección fisica de SS:SP en V86

    push ax

    mov ax, SelFlat32

    mov gs, ax ; GS apuntando a la direccion lineal 0

    pop ax

    mov word ptr gs:ebx[0], offset int_0 ; valor de IP al regresar ; de V86

    mov word ptr gs:ebx[2], codigo32 ; valor de CS al regresar ; de V86

    mov eax, ss:esp[14*4] ; EAX = EFlags del PMODE

    and ah, NOT 42h ; desactivamos bits IF y NT

    push eax

    popfd ; y lo ponemos en EFLAGS

    mov gs:ebx[4], ax ; guardamos en la pila V86 los flags

    or eax, 23000h ; ponemos el IOPL = 3 y bit VM activado

    push eax

    mov ebx, ss:esp[17*4] ; EBX = número de interrupción * 4

    movzx eax, word ptr gs:ebx[2] ; EAX = segmento del servicio ; solicitado

    push eax ; valor de CS al conmutar a V86

    mov ax,word ptr gs:ebx[0] ; EAX = offset del servicio solicitado

    push eax ; valor de IP al conmutar a V86

    mov eax, ss:esp[11*4] ; recuperamos el valor de EAX y EBX al

    mov ebx, ss:esp[12*4] ; entrar en este procedimiento

    iretd ; ¡conmutamos a Virtual 8086 Mode!

    test byte ptr ss:esp[3*4+2],2 ; ¿procesador en V86 Mode?

    jz exc13 ; No, es una verdadera #GP

    add esp, 4 ; Si, sacamos codigo de error de la pila

    push ebx eax ; guardamos registros a modificar

    mov ax, SelFlat32

    mov ds, ax ; DS apuntando a la dirección física 0

    movzx ebx, word ptr ss:esp[3*4] ; BX = segmento de código ; interrumpido

    contInt:

    cmp bx, codigo32 ; ¿Se ha terminado el servicio ofrecido?

    jne V86_cont ; No, continuamos en modo V86

    cmp word ptr ss:esp[2*4], offset int_0

    je V86_exit

    mov ah, ds:ebx[0] ; AH = código de instrucción

    mov al, 3 ; interrupción 3

    cmp ah, 0cch ; ¿Era la instrucción INT 3?

    je short V86_int ; Si, ejecutar la interrupción 3

    inc eax ; miramos para interrupción 4

    cmp ah, 0ceh ; ¿Era la instrucción INTO?

    je short V86_int ; SI, ejecutar INTO

    cmp ah, 0cdh ; ¿Era de la forma INT n?

    je V86_cont2 ; No, se ha producido una #GP

    jmp V86_exc

    V86_cont2:

    inc dword ptr ss:esp[2*4] ; incrementamos de nuevo IP para ; apuntar despues de la instrucción INT n

    mov al, ds:ebx[1] ; AL = número de interrupción

    cmp al, 21h ; ¿es la interrupción 21h?

    jne cmpInt10 ; No, comprobar si es la int. 10h

    cmp byte ptr ss:esp[1], 4bh ; ¿función 4Bh?

    jne cmpInt10 ; No, comprobar la int. 10h

    call emul_21_4b ; cogemos nombre del fichero a ejecutar

    jmp V86_int ; y ejecutamos el servicio 4Bh de la int 21h

    jmp V86_int ; ejecutamos verdadera llamada a ; interrupción

    cmpInt15:

    cmp al, 15h ; ¿Interrupción 15h?

    jne short V86_int ; No, es otra interrupción

    cmp byte ptr ss:esp[1], 87h ; ¿Función 87h?

    je emul_15_87 ; Si, emulamos la interrupción 15, ; servicio 87h

    cmpInt10:

    cmp al, 10h ; ¿interrupcion de de video 10h?

    jne cmpInt15 ; No, comprobar si es la int. 15h

    cmp byte ptr ss:esp[1], 0 ; ¿función "establecer modo de video"?

    jne cmpInt15 ; No, comprobar si es la int. 15h

    call emul_10_00 ; emulamos interrupcion de video

    cmpInt15:

    cmp al, 15h ; ¿Interrupción 15h?

    jne short V86_int ; No, es otra interrupción

    cmp byte ptr ss:esp[1], 87h ; ¿Función 87h?

    je emul_15_87 ; Si, emulamos la interrupción 15, ; servicio 87h

    V86_int:

    push ecx ; guardamos registro a modificar en este procedimiento

    movzx ebx, al ; EBX = número de interrupción

    shl ebx, 2 ; EBX = numero de int * 4

    mov ecx, ds:ebx[0] ; ECX = offset de la interrupción

    movzx ebx, word ptr ss:esp[7*4] ; EBX = SS de la tarea V86

    shl ebx, 4 ; convertimos a lineal

    sub word ptr ss:esp[6*4], 6 ; SP = SP - 6 en la tarea V86

    mov ds:ebx[2], ax ; ponemos en la pila V86 el valor de CS

    mov ax, ss:esp[3*4] ; AX = IP

    mov ds:ebx[0], ax ; ponemos en la pila V86 el valor de IP

    mov ss:esp[3*4], cx ; offset de la interrupción

    shr ecx, 16 ; cogemos el segmento

    mov ss:esp[4*4], cx ; segmento de la interrupción

    pop ecx eax ebx ; restauramos los registros modificados

    iretd ; ejecutamos la interrupción V86

    add ebx, ss:esp[6*4] ; EBX = dirección lineal de SS:SP en V86

    mov ax, ss:esp[5*4] ; AX = FLAGS

    mov ds:ebx[4], ax ; salvamos las banderas

    and ah, NOT 3 ; ponemos en los flags los bits TF=0 y IF=0

    mov ss:esp[5*4], ax ; retocamos FLAGS de la pila

    mov ax, ss:esp[4*4] ; AX = CS

    shr ecx, 16 ; cogemos el segmento

    mov ss:esp[4*4], cx ; segmento de la interrupción

    pop ecx eax ebx ; restauramos los registros modificados

    iretd ; ejecutamos la interrupción V86

    V86_exit:

    mov ax, SelData32 ; selector de datos

    mov ds,ax ; apuntamos DS al segmento de datos protegido

    pop eax ebx ; restauramos valores originales de EAX y EX

    mov ss:esp[11*4], eax

    mov ss:esp[12*4], ebx

    add esp,8 ; sacamos IP y CS de la pila

    pop eax ; sacamos EFLAGS

    mov eax, [Tactiva] ; numero de la tarea activa

    shl eax, 2

    mov ebx, [TSSXesp0+eax]

    mov ax, SelZero16

    mov ds, ax

    pop dword ptr ds:ebx[0]

    pop dword ptr ds:ebx[4]

    pop eax ebx es ds fs gs

    popfd

    ret 4 ; cargamos EIP con el valor siguiente a la

    ; instrucción de la int16 y eliminamos 4

    ; bytes de la pila al mismo tiempo

    mov byte ptr ss:esp[14*4], al ; parte baja de EFLAGS

    mov eax, [Tactiva] ; numero de la tarea activa

    cont5:

    shl eax, 2 ; obtenemos indice

    pop ds:[V86_sp+eax] ds:[V86_ss+eax] ; sacamos registros V86 de ; la pila

    pop ds:[V86_es+eax] ds:[V86_ds+eax] ds:[V86_fs+eax] ds:[V86_gs+eax]

    mov eax, [Tactiva] ; numero de la tarea activa

    shl eax, 2

    mov ebx, [TSSXesp0+eax]

    mov ax, SelZero16

    mov ds, ax

    pop dword ptr ds:ebx[0]

    pop dword ptr ds:ebx[4]

    pop eax ebx es ds fs gs

    popfd

    ret 4 ; cargamos EIP con el valor siguiente a la instrucción de ; int16 y eliminamos 4 bytes de la pila al mismo tiempo

    add eax, ss:esp[2*4] ; obtenemos IP de la excepción

    mov eax, ds:[eax] ; obtenemos direccion de la instrucción

    cmp ax, 0e3dbh ; ¿era la instrucción FINIT/FNINIT?

    je saltar2b ; Si, la saltamos

    cmp ax, 3fd9h ; ¿era la instrucción FSTCW [BX]?

    je saltar2b ; Si, la saltamos

    cmp ax, 3fddh ; ¿era la instrucción FSTSW [BX]?

    je saltar2b ; Si, la saltamos

    cmp ax, 3dddh ; ¿era la instrucción FSTSW [DI]?

    je saltar2b ; Si, la saltamos

    cmp ax, 3dd9h ; ¿era la instrucción FSTCW [DI]?

    je saltar2b ; Si, la saltamos

    cmp ax, 3cd9h ; ¿era la instrucción FSTCW [SI]?

    je saltar2b ; Si, la saltamos

    cmp ax, 3cddh ; ¿era la instrucción FSTSW [SDI]?

    je saltar2b ; Si, la saltamos

    cmp ax, 7eddh ; ¿era la instrucción FSTSW [BP-byte]?

    je saltar3b ; Si, la saltamos

    cmp ax, 0beddh ; ¿era la instrucción FSTSW [BP-word]?

    je saltar4b ; Si, la saltamos

    ; PUERTO 3D4h (indice) y 3D5h (datos) del CRT controller

    cmp dx, 3d4h ; ¿controlador CRT?

    jne cmp3d5 ; No, comprobamos si es registro de datos

    mov ax, SelData32 ; selector de datos

    mov ds, ax ; acceso a los datos mediante DS

    mov eax, [Tactiva] ; obtenemos tarea que provoco la excep.

    cmp eax, [TPrimerPlano] ; ¿tarea activa en primer plano?

    je emulOutDxAl ; emulamos instruccion "Out Dx, Al"

    mov bl, ss:esp ; valor de AL antes de la excepcion

    mov ds:[regCRT+eax], bl ; guardamos valor del CRT

    pop eax ebx ; sacamos registros de la pila

    iretd ; volvemos a la tarea actual

    cmp3d5:

    cmp dx, 3d5h ; ¿registro de datos del CRT?

    jne cmp3c4 ; no, comprobamos si es el Sequencer

    mov ax, SelData32 ; selector de datos

    mov ds, ax ; acceso a los datos mediante DS

    mov eax, [Tactiva] ; tarea ejecutandose actualmente

    cmp eax, [TPrimerPlano] ; ¿tarea activa en primer plano?

    je emulOutDxAl ; si, emulamos instruccion OUT

    mov bl, 19h ; numero de registros del CRTC

    mul bl ; AX=indice a registros de la t. activa

    mov ebx, [Tactiva] ; tarea activa

    mov bl, [regCRT+ebx] ; valor del registro indice

    add eax, ebx ; desplazamiento al registro CRT

    mov bl, ss:esp ; valor de AL antes de la excepcion

    mov [VideoRegCRTC+eax], bl ; guardamos reg. CRTC de la T. activa

    pop eax ebx ; sacamos registros de la pila

    iretd ; volvemos a la tarea activa