Enunciado.
En esta práctica vamos a simular, mediante procesos de UNIX,
las solicitudes y concesión (o no) de cambios de grupo en
esta asignatura.
El programa constará de un único fichero fuente,
cambios.c
, cuya adecuada compilación producirá
el ejecutable cambios
. 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 (libcambios.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 cambios.c libcambios.a -o cambios
Disponéis, además, de un fichero de cabeceras,
cambios.h
, donde se encuentran definidas, entre
otras cosas, las macros que usa la biblioteca.
El proceso inicial se encargará de preparar todas las variables
y recursos IPC de la aplicación. Este proceso creará 32 procesos
hijos. Cada proceso hijo, representa a una persona. Los nombres
de las personas, junto con las abreviaturas usadas para referirse
a ellas, se muestran en la siguiente tabla:
(A) Ana | (I) Ignacio |
(a) Alberto | (i) Ildefonsa |
(B) Benito | (J) Juan |
(b) Bonifacia | (j) Josefa |
(C) Carla | (L) Laura |
(c) Conrado | (l) Luis |
(D) Daniela | (M) Manuel |
(d) David | (m) María |
(E) Emilio | (N) Nicanor |
(e) Eulalia | (n) Natalia |
(F) Flor | (O) Olvido |
(f) Federico | (o) Olegario |
(G) Gonzalo | (P) Pedro |
(g) Gracia | (p) Pilar |
(H) Honoria | (R) Rosa |
(h) Hilario | (r) Ramón |
En la asignatura hay cuatro grupos (I, II, III y IV) y se ha
establecido inicialmente que los grupos se formarán en atención
a la inicial del nombre de la persona. La partición se ha
efectuado siguiendo el criterio: A-D, E-H, I-M, N-R, para los
grupos I, II, III y IV, respectivamente. Cada proceso, según
nace, ha de ir al grupo que le corresponde.
La vida de los procesos transcurre plácida y tranquila. Duermen
un tiempo y, pasado este, deciden que quieren cambiar de grupo.
Se lo solicitan al proceso padre, que por cierto se llama
Zacarías(Z). Z, viendo las solicitudes que hay en cada momento,
decide conceder el cambio inmediatamente o hacerlo más tarde.
El criterio que se mantiene es que cualquier cambio no debe
implicar variación en el número de alumnos por grupo. Por
ejemplo, si hay un alumno que quiere ir del grupo I al II y
otro que quiere ir del II al I, es un cambio correcto.
Si hay un alumno que quiere ir del I al II, otro del II al
III y otro del I al IV, es un cambio incorrecto.
La práctica se mantiene en ejecución hasta que pasan 15 segundos
(en el caso de que funcione a máxima velocidad) o 30 segundos
en otro caso. Al finalizar la práctica, deben morir los hijos y
Z debe esperar por su muerte.
Cuantos más cambios de grupo haya sido capaz de gestionar Z,
mejor solucionada estará la práctica.
La pertenencia de un alumno a un grupo determinado se establece
en una zona de memoria compartida. Se trata de 80 caracteres
consecutivos asociados en parejas. Cada pareja puede estar
en uno de dos estados:
- El primer carácter es un espacio (ASCII 32) y el
segundo es cualquiera. En este caso, ahí no hay ninguna
persona
- El primer carácter es una letra de entre
"ABCDEFGHIJLMNOPRabcdefghijlmnopr" y el segundo tiene
un código ASCII comprendido entre 1 y 4, ambos incluidos.
Si esto es así, significa que ahí está la persona que
se especifica en el primer carácter y que quiere ir
al grupo que se corresponde con el segundo carácter.
Si dicho grupo coincide con su grupo actual, significa
que no quiere cambiarse.
Las parejas de la primera a la décima, corresponden al grupo I.
Las siguientes decenas corresponden al II, al III y al IV,
respectivamente.
Es evidente que, una vez situadas todas las personas inicialmente
en sus puestos, no puede desaparecer una persona de la zona
de memoria compartida ni puede estar en dos sitios a la vez.
Cada vez que una persona logra satisfactoriamente cambiar de
grupo, debe incrementar un contador. Este contador, de tipo
entero, está situado en la zona de memoria compartida a
continuación de donde se encuentran las parejas del párrafo
anterior, es decir, con un desplazamiento de
84 bytes respecto
al principio de la zona. También debe la persona que logra
cambiarse de grupo invocar la correspondiente función de la
biblioteca para incrementar el contador interno de verificación.
El objetivo de esto es que, al final, deben, como
es obvio, casar las veces que ha contado la biblioteca con el
valor de los cambios de la variable en la zona de memoria
compartida.
Para que los procesos hagan una solicitud y para recibir la
aceptación de Z, se usará un único buzón de paso de mensajes.
El formato de los mensajes enviados se deja libre.
La práctica se invocará especificando un parámetros exactamente
desde la línea de órdenes. El 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.
El tiempo de ejecución en este caso será de 30 segundos.
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, el tiempo de ejecución será de 15 segundos.
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.
Las funciones proporcionadas por la biblioteca
libcambios.a
son las que a continuación aparecen.
De no indicarse nada, las funciones devuelven -1 en caso de
error:
int inicioCambios(int ret, int semAforos,
char *z)
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 puntero a la zona de memoria
compartida recién creados.
int aQuEGrupo(int grupoActual)
Un proceso que esté en un grupo debe invocar esta
función que realizará dos acciones: esperar un
tiempo indeterminado (que simula el tiempo que
el alumno está a gusto en su grupo) y, pasado este,
devuelve el grupo al que quiere cambiarse el
proceso.
int refrescar(void)
Cada vez que un proceso cambia el estado del sistema,
debe llamar a esta función para que el cambio se
refleje en la pantalla. Los procesos aparecen por
su inicial en la fila del grupo en el que están.
El color del carácter indica el grupo al que desean
ir.
void incrementarCuenta(void)
Una vez al proceso se le ha concedido el cambio y,
después de haberse cambiado, debe llamar a esta función.
int finCambios(void)
Se llama a esta función después de muertos los hijos
y haber esperado por ellos
y antes de destruir los recursos IPC. Si las cuentas
coinciden, devolverá cero. Si no son iguales, retornará
con -1, después de haber puesto un mensaje de error
en la pantalla.
void pon_error(char *mensaje)
Pone un mensaje de error en el recuadro verde 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.
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.
* inicioCambios:
- limpia la pantalla
- S=1
- mensaje de bienvenida
- refrescar
* finCambios:
- refrescar
- comprobaciones varias antes de acabar
* refrescar:
- comprobación de valores de letras y grupos
- W(S)
- refrescar la presentación
- S(S)
- comprobación de la consistencia
* 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 libcambios.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 libcambios.a
y el fichero de cabecera cambios.h
. La biblioteca
funciona con los códigos de VT100/xterm, por lo que debéis adecuar
vuestros simuladores a este terminal.
Ficheros necesarios:
NOTA: en el caso de los ficheros de Solaris,
casi es preferible
que hagáis un enlace simbólico en lugar de copiarlos o bajarlos
desde el servidor:
ln -s /home/labssoo/public_html/CAMBIOS/libcambios.a libcambios.a
ln -s /home/labssoo/public_html/CAMBIOS/cambios.h cambios.h
Si así lo hacéis, además de ahorrar espacio de almacenamiento
en el disco del servidor, como ya sabéis, las posibles
actualizaciones de la biblioteca son automáticas y no tenéis
que bajarlas de nuevo cuando se produzcan.