DISEÑO DE SISTEMAS OPERATIVOS
Curso 2001-02

PRÁCTICA 1: PLANIFICADOR DE TAREAS

PROPÓSITO DE LA PRACTICA

Se trata de simular la ejecución paralela de varias tareas y su planificación mediante la realización de una rutina de tratamiento de interrupciones de reloj en un PC con MS-DOS.
 

GUÍA PARA EL DESARROLLO DE LA PRÁCTICA

Para realizar multitarea es necesario definir una estrategia de planificación de tareas, como por ejemplo round-robin. Esto implica que cada cierto tiempo, el planificador tiene que ser capaz de expulsar la tarea en ejecución y sustituirla por otra que estuviera esperando a ser ejecutada. Para que el planificador pueda entrar a ejecutar puede aprovechar los momentos en que se producen interrupciones de reloj (asumiendo que los procesos o tareas de usuario no tienen la posibilidad de inhibir las interrupciones, que en un sistema operativo normal sería una instruccion privilegiada).

Por tanto, la parte principal de la práctica consiste en la realización de una rutina de tratamiento de interrupciones de reloj, a la que se asocia un planificador de tareas.

La forma de funcionar las interrupciones en el PC es la siguiente. El PC tiene varios elementos de hardware, como el temporizador, el teclado, el puerto de comunicaciones asíncrono, el disco o la impresora, que están conectados al chip controlador de interrupciones 8259A. Cada vez que uno de estos dispositivos envía una señal al 8259A, éste causa una interrupción en el procesador 80x86. Si las interrupciones estan permitidas, el procesador para lo que estuviera haciendo para tratarla.

La interrupción hardware de mayor prioridad es la del temporizador (IRQ0), la interrupción 08. Ésta sucede 18.2 veces por segundo en el PC y cada vez activa una rutina de servicio de interrupción del BIOS que actualiza la hora del PC y  controla el tiempo de funcionamiento del motor de la unidad de disquete. Además, antes de finalizar la interrupción, produce una interrupción software al vector 1C, permitiendo que se pueda ejecutar una rutina de servicio definida por el usuario que será llamada 18.2 veces por segundo independientemente de lo que esté ocurriendo en el sistema.

El vector de tratamiento de interrupción 1C está inicializado al arrancar el sistema a una dirección de memoria donde hay una instrucción IRET, luego no hace nada, en principio. Se puede cambiar este vector de interrupción para que apunte a la rutina que nos interesa. Véase cómo se hace esto en el programa ejemplo reloj.pas.

La conmutación de tareas exige almacenar los valores de todos los registros de la tarea actual en el descriptor de tarea asociado, y cargar los valores de la nueva tarea (a partir de los almacenados en su descriptor de tarea). Este tipo de operaciones se puede realizar en ensamblador. En el manual de TurboPascal se muestra como incluir instrucciones de ensamblador con la sentencia asm, por ejemplo:

asm
push ax
pop ax
mov ax,es:processtable[bx+di]
mov es:processstable[bx+di],ax
...
end;
Al entrar en el procedimiento de tratamiento de la interrupción 1C, en la pila se encuentra la dirección de retorno y la palabra de estado (flags) correspondientes a un punto en la rutina de tratamiento de interrupción 08 (interrupción de reloj del sistema), que es la que llamó a la interrupción 1C. Desafortunadamente, el valor del registro SS es cambiado por la rutina de tratamiento de la interrupción 08 (porque utiliza su propia pila), y por tanto es difícil acceder al valor del CS:IP correspondiente a la tarea en curso. Por ello, en la práctica habrá que cambiar la rutina de tratamiento de interrupción 08 en vez de la 1C (aunque habría sido más interesante hacerlo para la 1C, respetando así el tratamiento que hace el BIOS para las interrupciones de reloj). Hay varias maneras de introducir la nueva rutina de tratamiento de interrupción llamando también a la rutina antigua (se deja como ejercicio de la práctica).

Al ocurrir la interrupción, en la pila se encuentra la información correspondiente a la tarea de usuario interrumpida. La rutina de tratamiento de interrupción, Planificador, debería realizar los siguientes pasos:

1. Salvar todos los registros.
En general todos los compiladores C++ o Pascal disponibles suelen dar alguna facilidad para ello y habrá que consultar el manual correspondiente. En general suele darse la posibilidad de declarar un procedimiento como interrupt para indicar que se trata de una rutina de tratamiento de interrupción. El compilador genera entonces el código apropidado para salvar todos los registros del procesador para facilitar el guardar el contexto del proceso interrumpido (y no sólo los registros Flags, CS e IP que ya se salvan al producirse la interrupción).

Obsérverse, pues, que para gestionar los procesos hay que trabajar con estas pilas.

2. Incrementar el contador de tics

3. Comprobar si se está ejecutando el planificador (podría haber sido interrumpido mientras realizaba un cambio de contexto). Si es así, volver, lo que requiere restaurar los registros, enviar la señal EndOfInterrupt ($20) al 8259A y hacer un IRET:

MOV AL, $20     ;  envia el EOI ($20) al 8259A (al puerto $20)
OUT $20, AL

4. El compilador incluye el código necesario al volver de la interrupción, para restaurar los registros antes de que se ejecute IRET.

Al hacer IRET se restauran CS:IP y Flags, guardados en la pila. Como en Flags el bit de permiso de interrupciones estaba puesto no hace falta ejecutar la instrucción STI (aunque se podría considerar la conveniencia de hacerlo antes de acabar todo el tratamiento de la interrupción para que el sistema esté el mínimo tiempo posible con las interrupciones inhibidas, y evitar así que se perdiera alguna interrupción).

Este paso también habría que ejecutarlo en los siguientes casos: que se estuviera tratando una llamada del DOS (porque podríamos entrar en conflictos entre las distintas tareas, ya que el DOS no es reentrante), o que todavía no hubiera pasado el tiempo asignado a la tarea (el numero de tics de ejecución de la tarea fuera menor del asignado).

5. En caso de que la tarea actual deba ser cambiada por otra, habría que hacer lo siguiente:

- Poner la tarea actual al final de la cola de listas para ejecutar
- Permitir las interrupciones al 8259A, tal como se ha visto antes
- Realizar el cambio de contexto:
. salvar los registros de la tarea actual
. eligir la nueva tarea a ejecutar
. poner nuevos registros con los valores que tenga la tarea a ejecutar
. poner a 0 el contador de tics del planificador
. Poner en la pila los flags y el CS:IP de la tarea a ejecutar, y todos sus registros

NOTA: En TurboPascal esto se puede hacer cambiando los valores de los parámetros del procedimiento de interrupción, en el que se pasan todos los registros.

- Restaurar registros y volver (IRET), que en el caso de TurboPascal se hace automáticamente
Un aspecto interesante es determinar cuándo una tarea está ejecutando una llamada al DOS. Una posibilidad es ver si el CS:IP de la tarea en ejecucion apunta a un lugar fuera de su segmento de código (CS<>proctable[actual].cseg). Esto es interesante considerarlo para poder permitir que todas las tarea puedan hacer llamadas al DOS (por ejemplo, para que hacer write() y sacar trazas que muestren lo bien que funciona la práctica).

Para la realización de la práctica se pueden hacer varias simplificaciones, como por ejemplo, considerar que las tareas, una vez cargadas en memoria, quedan allí. También se puede simplificar considerando las tareas como funciones o procedimientos del programa global, y no como procesos separados.

La elección de la rodaja de tiempo asignada a cada tarea se determina normalmente por experimentación. Tiene que elegirse lo suficientemente grande como para conseguir que el cambio de contexto no sea muy significativo en comparación al tiempo dedicado a realizar trabajo efectivo. Una buena regla es que el cambio de contexto no suponga mas del 10 por ciento del tiempo de computación. Así, se podría considerar, por ejemplo, entre 5 y 10 tics de reloj.

Es importante recordar que en el código de la rutina de servicio de interrupción no se pueden hacer llamadas a procedimientos dados por TurboPascal tales como read() o write(), ya que éstos llaman al BIOS. Tampoco se pueden usar variables globales si hubiera cambiado el valor del registro DS.
 

PLAZO DE ENTREGA Y EVALUACIÓN

La fecha límite para la entrega de la práctica es el 28 de enero. A partir de entonces, cada semana de retraso resta un punto a la calificación normal de la práctica.

La evaluación de la práctica tendrá en cuenta aspectos de eficiencia de la solución realizada, así como la flexibilidad de uso de los procedimientos desarrollados. Se considerará el diseño orientado a objetos de los componentes de la práctica y la estructuración de los programas. También se valorará la justificación de las decisiones de diseño que se tomen.

La práctica se puede enviar por e-mail a a jpavon@sip.ucm.es, o bien entregar en mano al profesor (en disquete). Para la documentación de la misma basta con comentar adecuadamente los programas, y se puede incluir algún grafo con los componentes desarrollados y sus relaciones.
 



Dudas o comentarios sobre este enunciado: jpavon@sip.ucm.es
Fecha de última actualización: 16 de octubre de 2001