El código C necesario para ejecutar la simulación se
divide en 6 ficheros, de los cuales se proporcionan 5:
-
Ficheros CPU.c
y
CPU.h .
Representa la CPU de
nuestro ordenador. Nuestra CPU contiene 3 datos que deben ser
guardadas y restauradas en cada cambio de contexto:
-
El contador de programa. Su valor puede ser
obtenido por la función int getCPUpc() , y puede ser
modificado por la función void setCPUpc(int) .
-
El puntero de pila. Su valor puede ser obtenido
por la función int getCPUsp() , y puede ser modificado
por la función void setCPUsp(int) .
-
16 registros de propósito general. Sus valores
pueden ser obtenidos por la función int* getCPUreg() ,
y pueden ser modificados por la función void
setCPUreg(int*) . Tanto el resultado de
getCPUreg como el argumento de setCPUreg
son un vector de 16 enteros almacenados en memoria creada
dinámicamente. Nótese que getCPUreg reserva una nueva
porción de memoria cada vez que es llamada y que tanto
getCPUreg como setCPUreg no liberan
memoria.
Existe además otra función en el fichero
CPU.c . Su prototipo es void step() ,
y simula el comportamiento de un proceso durante un período de
ejecución en la CPU. Para ello, modifica aleatoriamente el contenido
del contador de programa, el puntero de pila y los registros de
propósito general.
-
Fichero process.c
Este fichero
contiene las variables y funciones necesarias para llevar cuenta de
los procesos actualmente disponibles en el sistema. Debe también
llevar cuenta de cuál es el proceso que está en cierto momento
ocupando la CPU. Es la única parte que se debe implementar en la
práctica, ya que los demás ficheros se proporcionan. El fichero
process.c deberá implementar las siguientes
funciones:
-
void Init() . Inicializa la estructura
de datos: cola de PCBs y le añade un proceso cuyo PID deberá ser igual a 0.
Puede ser ejecutado, pero no terminará hasta el final de la
simulación. El valor inicial del contador de programa, puntero de
pila y los registros de propósito general para este proceso ha de
ser 0. Después de una llamada a esta función (al principio de la
simulación) se supone que ningún proceso está ejecutando. Por este
motivo, en la función main , explicada más adelante,
la llamada a Init , va seguida inmediatamente de una
llamada a ContextSwitch , para hacer que el proceso
creado ocupe la CPU.
-
int Clone() . Crea un nuevo proceso.
El nuevo proceso tendrá inicialmente los mismos valores para el
contador de programa, puntero de pila y registros de propósito
general que el proceso que está ejecutándose en la CPU en ese
momento. Clone asigna un PID al nuevo proceso y lo
añade a la lista de procesos en el sistema. Se debe tener en
cuenta que no puede ocurrir que 2 procesos tengan un mismo PID y
que el rango de valores de PID permitido es entre 0 y 100.
Teniendo en cuenta que el PID 0 está reservado al proceso creado
inicialmente, el rango de valores de PIDs de procesos creados con
Clone debe estar entre 1 y 100. No es necesario
considerar el caso en el que se produce una llamada a
Clone y no hay ningún PID disponible, ya que esa
situación no se produce durante la simulación. Clone
devuelve el PID del proceso creado. Como resultado de una llamada
a Clone no cambia el proceso que está ocupando la
CPU.
-
void Terminate() . Termina el proceso
que está en este momento ocupando la CPU, liberando todos los
recursos dedicados a dicho proceso en la estructura de datos. Como
resultado de una llamada a Terminate , la CPU quedará
libre. Debido a esto, una llamada a Terminate vendrá
seguida siempre por una llamada a ContextSwitch para
hacer que otro proceso pase a ocupar la CPU, excepto después de
que todos los procesos del sistema hayan terminado, al final de la
simulación.
-
void ContextSwitch(int pid) . Si había
algún proceso ejecutando, guarda su contexto (contador de
programa, puntero de pila y registros de propósito general). En
cualquier caso, hace que el proceso cuyo PID es pid
pase a ocupar la CPU. Para ello, el contexto del nuevo proceso
debe ser restaurado en la CPU.
Es obligatorio almacenar la
estructura de datos con los procesos del sistema utilizando memoria
dinámica.
-
Ficheros process.h . y procVal.h Contienen la definición
de los prototipos (tipo del resultado, nombre y tipo de los
parámetros) de las funciones descritas en el apartado anterior. Deben
ser incluido al comienzo del fichero
process.c .
-
Fichero main.c . Ejecuta la
simulación. Comienza con una llamada a Init , seguida de
una llamada a ContextSwitch , para hacer que el proceso
con PID 0 ejecute en la CPU. A continuación se realizan una serie de
llamadas a Clone() , Terminate() y
ContextSwitch() . Al final de la simulación, termina todos
los procesos que estuviesen en el sistema e imprime un mensaje
indicando que la simulación se ha realizado con éxito.
-
Fichero procVal.a . Implementa
las funciones void CloneVal(int) , void
TerminateVal() , void InitVal() , void
CPUCheck() , y int getRandomPID() . Estas funciones
se usan para comprobar que el código realizado por el alumno es
correcto. La única cosa que necesitamos conocer sobre ellas es que
getRandomPID selecciona aleatoriamente uno de los
procesos disponibles en el sistema y devuelve su PID.
La figura 1 muestra
la interacción de los cuatro ficheros anteriores. En el fichero
main.c se realizan una serie de pruebas del código
desarrollado en el fichero process.c que a su vez ha
de utilizar las funciones contenidas en CPU.c . En la
función main se comprueban los resultados obtenidos con los
producidos por el código contenido en
procVal.a .
Para compilar la práctica, se invocará el siguiente
comando:
gcc -I ../LibAO -Wall -o process main.c CPU.c process.c
procVal.a ../LibAO/libao.a
Al ejecutar el programa, si no hay errores, se produce
exclusivamente el mensaje:
Execution with no errors
En caso de que se produzca un error, aparecerá un mensaje
explicativo del error producido empezando por la palabra
ERROR . En cualquier caso aparecerán los
mensajes habituales relativos a gestión de memoria dinámica.
La aplicación debe ser desarrollada en Linux.
|