Enunciado.
Sirva esta práctica como pequeño homenaje a un juego ya clásico,
Transport Tycoon (Deluxe) de Chris Sawyer y su sucesor,
Locomotion que tan buenos ratos nos ha hecho pasar. Vamos
a simular y controlar la circulación
de unos trenes por un circuito cerrado. Cada tren será controlado
por un proceso UNIX y se usarán mecanismos IPC para coordinarlos.
Aunque la práctica requiere de la creación de un buzón de paso
de mensajes y de un conjunto de semáforos, podréis usar también
una zona de memoria compartida si lo deseáis.
Los principales problemas que pueden encontrarse una vez se maneja
la biblioteca bien, son los accidentes y los interbloqueos. Ocurre
un accidente cuando un tren pasa a ocupar una posición ya ocupada
por otro (incluso él mismo). Ocurre un interbloqueo cuando varios
trenes no pueden evolucionar porque, circularmente, se están impidiendo
el paso los unos a los otros. Para que la práctica esté aprobada,
es necesario que no se produzcan accidentes con vuestro diseño
(ni en la teoría, ni en la práctica). Se valorará bastante, no
obstante, si se implementa cualquier mecanismo para que no se
produzcan interbloqueos. Esto es más complicado y sólo se debe intentar
si disponéis de tiempo para ello.
El programa constará de un único fichero fuente,
lomo.c
, cuya adecuada compilación producirá
el ejecutable lomo
. Respetad las
mayúsculas/minúsculas de los nombres.
Para simplificar la realización de la práctica, se os proporciona
una biblioteca estática de funciones (liblomo.a
)
que debéis enlazar con vuestro módulo objeto para generar el
ejecutable. Gracias a ella, algunas de las funciones necesarias
para realizar la práctica no las tendréis
que programar sino que bastará nada más con incluir
la biblioteca cuando compiléis el programa. La línea de
compilación del programa podría ser:
c89 lomo.c liblomo.a -o lomo
Disponéis, además, de un fichero de cabeceras,
lomo.h
, donde se encuentran definidas, entre
otras cosas, las macros que usa la biblioteca y las cabeceras
de las funciones que ofrece.
El proceso inicial se encargará de preparar todas las variables
y recursos IPC de la aplicación y registrar manejadoras para las
señales que necesite. Este proceso, además, debe tomar e interpretar
los argumentos de la línea de órdenes y llamar a la función
LOMO_inicio
con los parámetros adecuados. El proceso
será responsable de crear los trenes que van a circular por
el circuito (un proceso hijo por cada tren) y de controlar que,
si se pulsa CTRL+C la práctica acaba, no dejando procesos en
ejecución ni recursos IPCs sin borrar. La práctica devolverá 0
al sistema operativo si todo fue bien, 1 si se produjo cualquier
error y 2 si hubo cualquier accidente durante su ejecución.
La práctica se invocará especificando dos parámetros exactamente
desde la línea de órdenes. El primer parámetro será un valor entero
mayor o igual que cero.
Si es 1 o mayor, la práctica funcionará tanto más lenta cuanto
mayor sea el parámetro y no deberá consumir CPU apreciablemente.
Si es 0, irá a la máxima velocidad, aunque el consumo de CPU
sí será mayor. Por esta razón y para no penalizar en exceso
la máquina compartida, no debéis dejar excesivo tiempo ejecutando
en el servidor la práctica a máxima velocidad. El segundo
parámetro será el número de trenes que van a circular en esta
ejecución, un número estrictamente mayor que cero y menor que
100.
El programa debe estar preparado para que, si el usuario pulsa
las teclas CTRL-C desde el terminal, la ejecución del programa
termine en ese momento y adecuadamente. Ni en una terminación
como esta, ni en una normal, deben quedar procesos en ejecución
ni mecanismos IPC sin haber sido borrados del sistema. Este es
un aspecto muy importante y se penalizará bastante si la práctica
no lo cumple.
Es probable que necesitéis semáforos para sincronizar
adecuadamente la práctica. Se declarará una array de semáforos
de tamaño adecuado a vuestros requerimientos,
el primero de los cuales se reservará para el funcionamiento
interno de la biblioteca. El resto, podéis usarlos libremente.
Una posición en las vías del tren viene especificada por un
par de coordenadas (x,y). Las coordenadas de los puntos
singulares (origen, curvas y cruces) del trazado se puede
ver en la siguiente figura, junto con las direcciones de
circulación de cada tramo:
Las funciones proporcionadas por la biblioteca
liblomo.a
son las que a continuación aparecen.
De no indicarse nada, las funciones devuelven -1 en caso de
error:
int LOMO_inicio(int ret, int semAforos,
int buzOn)
El primer proceso, después de haber creado los
mecanismos IPC que se necesiten y antes de haber tenido
ningún hijo, debe llamar a esta función, indicando
en ret
la velocidad de presentación
y pasando el identificador del conjunto de
semáforos que se usará y el buzOn que se usará
para comunicarse con la biblioteca.
void LOMO_espera(int y, int yn)
Los trenes funcionan a distinta velocidad cada uno.
Para simular este comportamiento, se ha de llamar
a esta función para que realice una pausa adecuada
antes de que el tren vuelva a avanzar. Para calcular
adecuadamente la duración de la pausa, se debe
proporcionar la coordenada y actual de la cabeza
del tren y la coordenada y de la posición de la
cabeza del tren a la que se va a ir. Véase más
abajo el funcionamiento de los trenes para mayor
información.
int LOMO_fin(void)
Se llama a esta función cuando se desea acabar con
la simulación. Se debe llamar antes de matar a
ningún proceso hijo
y antes de destruir los recursos IPC. Devuelve -1
si se ha producido algún error, 0 si todo acabó
bien y -2 si hubo algún accidente durante la
simulación.
void pon_error(char *mensaje)
Pone un mensaje de error en el recuadro azul de la
parte inferior de la pantalla y espera a que el
usuario pulse "Intro". La podéis usar para depurar.
Estad atentos pues pueden ir saliendo versiones nuevas de la
biblioteca para corregir errores o dotarla de nuevas funciones.
La comunicación de los procesos de los trenes con la
biblioteca se realiza mediante paso de mensajes. El formato
de los mensajes es el de la estructura struct mensaje
que aparece en lomo.h
. Nada más nacer el proceso,
enviará un mensaje al buzón de tipo TIPO_TRENNUEVO
.
La biblioteca responderá con un mensaje de tipo
TIPO_RESPTRENNUEVO
. En el campo tren
de la estructura, la biblioteca mandará el identificador
del tren nuevo (un número entero mayor o igual que cero).
Este número se deberá indicar en cualquier mensaje posterior
que se envíe a la biblioteca. La longitud del tren creado
estará comprendida entre 3 y 19 caracteres.
El proceso que se debe seguir para que un tren avance consiste
en:
- El tren debe enviar a la biblioteca un mensaje de tipo
TIPO_PETAVANCE
. En el campo tren
de la estructura debe ir su identificador.
- La biblioteca le responderá con un mensaje de tipo
TIPO_RESPPETAVANCETREN0+id
, siendo
id el identificador del tren. La biblioteca
devolverá en el campo tren
el identificador
del tren y en los campos x
e y
,
las coordenadas a las que accederá la cabeza del tren en
cuando se dé la orden de ejecutar el avance.
- El proceso debe verificar que la posición a la que se
va a acceder es segura o, en caso de no serlo, esperarse
sin consumo de CPU a que lo sea.
- Una vez haya decidido avanzar, el tren envía a la
biblioteca un mensaje de tipo
TIPO_AVANCE
con su identificador.
- La biblioteca le responderá con un mensaje de tipo
TIPO_RESPAVANCETREN0+id
, de modo
similar al indicado tres puntos más arriba. Una vez
recibido el mensaje, podemos considerar que el avance
se ha producido correctamente. Al avanzar el tren,
la cola del tren puede que libere una posición (esto
ocurrirá siempre, salvo al principio, cuando nace
el tren). Las coordenadas de la posición liberada
vendrán en los campos x
e y
del mensaje. Este es un dato fundamental para diseñar
un esquema en que los trenes no se choquen. Si no
se libera ninguna posición, se devuelve -1 en x
e y
.
- Antes de continuar con el siguiente avance y repetir
los pasos anteriores, el proceso responsable del tren
debe llamar a la función
LOMO_espera
indicando
cuál es la coordenada y anterior y actual después de
realizado el avance.
La biblioteca crea un proceso internamente que es el que se
encarga de atender la cola de mensajes y realizar toda la
simulación. Al llamar a la función LOMO_fin
,
se envía la señal SIGINT
al proceso hijo
y éste, en una manejadora, envía mensajes con el identificador
de tren igual a -1 al buzón. Esto le puede servir a los
procesos que estén esperando por un mensaje, recibir este
y acabar de un modo razonable.
Respecto a la sincronización interna de la biblioteca, se
usa el semáforo reservado para conseguir atomicidad en la
actualización de la pantalla.
Para que las sincronizaciones que de seguro deberéis hacer
en vuestro código estén en sintonía con las de la biblioteca,
os ofrezco ahora un seudocódigo de algunas de
las funciones que realiza la biblioteca y están reguladas por el
semáforo. S es es semáforo interno que se utiliza y la biblioteca
lo inicia a uno.
* LOMO_inicio:
- limpia la pantalla
- S=1
- mensaje de bienvenida
- creación del hijo
- W(S)
- dibujar el circuito
- S(S)
* petición de avance y avance:
- comprobación de condiciones y actualización de variables
- W(S)
- refresco de la pantalla
- S(S)
* LOMO_espera y LOMO_fin:
- No manejan el semáforo
* pon_error:
- W(S)
- imprimir el error
- esperar a la pulsación de la tecla
- S(S)
En esta práctica no se podrán usar ficheros para nada,
salvo que se indique expresamente. Las comunicaciones de PIDs
o similares entre procesos, si hicieran falta, se harán mediante
mecanismos IPC.
Siempre que en el enunciado o LPEs se diga que se puede usar
sleep()
, se refiere a la llamada al sistema,
no a la orden de la línea de órdenes.
Los mecanismos IPC (semáforos, memoria compartida y paso
de mensajes) son recursos muy limitados. Es por ello, que
vuestra práctica sólo podrá usar un conjunto de semáforos,
un buzón de paso de mensajes y una zona de memoria compartida
como máximo. Además, si se produce cualquier error o se
finaliza normalmente, los recursos creados han de ser eliminados.
Una manera fácil de lograrlo es registrar la señal SIGINT
para que lo haga y mandársela uno mismo si se produce un error.
Biblioteca de funciones liblomo.a
Con esta práctica se trata de que aprendáis a sincronizar y
comunicar procesos en UNIX. Su objetivo no es la programación,
aunque es inevitable que tengáis que programar.
Es por ello que se os suministra una biblioteca estática de
funciones ya programadas para tratar de que no debáis
preocuparos por la presentación por pantalla, la gestión de
estructuras de datos (colas, pilas, ...) , etc. También servirá
para que se detecten de un modo automático errores que se
produzcan en vuestro código. Para que vuestro programa funcione,
necesitáis la propia biblioteca liblomo.a
y el fichero de cabecera lomo.h
. La biblioteca
funciona con los códigos de VT100/xterm, por lo que debéis adecuar
vuestros simuladores a este terminal.
Ficheros necesarios: