jueves, 30 de julio de 2009

Tu la llevas

Pues, hoy voy a ponernos un reto a ver si lograís resolverlo. Bueno, en realidad os voy a poner el enunciado y solución (que yo dí) a una de las prácticas que nos pusieron en la asignatura de Laboratorio de Sistemas Operativos (LabSSOO para los amigos) de 2º de ITIS de la Universidad de Salamanca.
Os animo a tratar de sacar vuestra propia solución y así probaís vuestros conocimientos de programación en C, en este caso para sistemas UNIX.
Pretendo poner 1º una parte general del enunciado y, para que el post no sea muy largo, el resto del enunciado y la solución en la parte de "leer más". Es una nueva opción que he implementado en la plantilla del blog y no se qué tal funcionará, asi que lo siento si no funciona xD. Como siempre, admito comentarios de cualquier tipo, ya sean de la solución, otras soluciones o de la forma de publicar estos "retos". Como por ejemplo, publicar la solución un poco oculta o separada del enunciado para evitar tentativas de mirarla. Y ya no me enrollo más.

Tú la llevas

El programa que hay que presentar constará de un único fichero fuente de nombre lallevas.c. La correcta compilación de dicho programa, producirá un fichero ejecutable, cuyo nombre será obligatoriamente lallevas. Respetad las mayúsculas/minúsculas de los nombres, si las hubiere.

La ejecución del programa creará una serie de procesos que accederán a una zona de exclusión mutua. La regulación del acceso a dicha zona se hará mediante el paso de un testigo.

Los procesos se dispondrán en círculo. Mientras ningún proceso quiera entrar en la sección crítica, el testigo circula por el círculo de procesos. Cuando un proceso quiera entrar, ha de esperar a estar en posesión del testigo. Entrará entonces en la sección crítica. Cuando salga, procederá con la circulación del testigo.

La invocación de la práctica se hará con dos argumentos, el último de ellos opcional:
   lallevas n_procs [debug]
El primero es un número entero comprendido entre 3 y 33. Es el número de procesos que participan en la práctica. De ellos, uno es el padre y el resto son hijos suyos. El modo en que se forma el corro para la circulación del testigo se deja a la libertad de los autores.

Si el segundo argumento es la palabra debug, la práctica se ejecuta en modo de depuración. Las características de este modo se explican más abajo. Si los parámetros introducidos no respetan las reglas anteriores, el programa lo detectará, informará al usuario y acabará.


  1. Funcionamiento de cada proceso

    Durante el período de funcionamiento de la práctica, los procesos estarán en el siguiente bucle infinito:
    1. Si estamos en el modo de depuración, duerme 1 segundo. Si no, no hace nada.
    2. Entra en la sección crítica.
    3. Si estamos en el modo de depuración, imprime en la pantalla: E(pid). Si no, imprime solamente una E. En ningún caso imprimirá un salto de línea.
    4. Si estamos en el modo de depuración, duerme 2 segundo. Si no, no hace nada.
    5. Si estamos en el modo de depuración, imprime en la pantalla: S(pid). Si no, imprime solamente una S. En ningún caso imprimirá un salto de línea.
    6. Sale de la sección crítica.
    Viendo la salida por pantalla, es evidente que para que la práctica funcione es necesario que aparezcan en alternancia perfecta, Ees y eSes.

    El paso del testigo se va a simular mediante el envío de señales. Cuando un proceso en posesión del testigo, desea pasárselo a otro, le enviará la señal SIGUSR1. Un proceso que no tenga el testigo pasa a poseerlo cuando reciba la señal. Si debe reenviarlo, esperará al menos una dé cima de segundo si estamos en el modo de depuración. De no estar en dicho modo, el reenvío será inmediato. Para efectuar esa pausa, se usará la función nanosleep (mirad la página de manual).

    Para hacer que el proceso duerma en el resto de casos, se usará la señal SIGALRM, nunca la función sleep. Dormir hará que el proceso, en modo de depuración, apenas consuma CPU. Lo podéis comprobar con la orden top.

    Para que el buffer intermedio usado por printf no interfiera con la salida de los procesos, es importante usar write para la salida por pantalla en su lugar.

  2. Finalización ordenada

    La práctica acabará cuando el usuario pulse CTRL-C. Los procesos deben morir y el padre, una vez hayan muerto todos imprimirá un salto de línea y la frase: "Programa acabado correctamente".

  3. Restricciones

    • Se deberán usar llamadas al sistema siempre que sea posible, a no ser que se especifique lo contrario.
    • No está permitido usar la función de biblioteca system, salvo indicación explícita en el enunciado de la práctica.
    • No se puede suponer que los PIDs de los procesos de una ristra van a aparecer consecutivos. Puestos en plan exquisito, ni siquiera podemos suponer que estarán ordenados de menor a mayor (puede ocurrir que se agoten los PIDs y se retome la cuenta partiendo de cero).
    • No está permitido el uso de ficheros, tuberías u otro mecanismo externo para transmitir información entre los procesos, salvo que se indique en el enunciado.
    • Supondremos un límite máximo de procesos igual a 33. Este límite os puede servir para no tener que usar memoria dinámica si no lo deseáis.
  4. Solución
#include
#include
#include
#include

#define EXERR -1

int pidPadre, numProc = 0, pidSiguiente;
int entrar = 0, modoD = 0, sonoAlarma = 0;
sigset_t blockAlarm;

void morir(int);
void testigo(int);
void alarma(int);
void error(int);

int main(int argc, char * argv[]){

int i = 0, n;
int pid, pidAnterior;
char mensaje[50];
struct sigaction manejadora;
sigset_t bloqueo;

if (sigfillset(&bloqueo) != 0) return EXERR;
if (sigemptyset(&blockAlarm) != 0) return EXERR;
if (sigprocmask(SIG_SETMASK, &bloqueo, NULL) != 0) return EXERR;

manejadora.sa_handler = morir;
if (sigemptyset(&manejadora.sa_mask) != 0) return EXERR;
if (sigaddset(&manejadora.sa_mask, SIGUSR1) != 0) return EXERR;
manejadora.sa_flags = 0;
if (sigaction(SIGINT, &manejadora, NULL) != 0) return EXERR;

manejadora.sa_handler = testigo;
if (sigemptyset(&manejadora.sa_mask) != 0) return EXERR;
if (sigaddset(&manejadora.sa_mask, SIGALRM) != 0) return EXERR;
manejadora.sa_flags = 0;
if (sigaction(SIGUSR1, &manejadora, NULL) != 0) return EXERR;

manejadora.sa_handler = alarma;
if (sigemptyset(&manejadora.sa_mask) != 0) return EXERR;
if (sigaddset(&manejadora.sa_mask, SIGUSR1) != 0) return EXERR;
manejadora.sa_flags = 0;
if (sigaction(SIGALRM, &manejadora, NULL) != 0) return EXERR;

manejadora.sa_handler = error;
manejadora.sa_mask = bloqueo;
if (sigdelset(&manejadora.sa_mask, SIGINT) != 0) return EXERR;
manejadora.sa_flags = 0;
if (sigaction(SIGTERM, &manejadora, NULL) != 0) return EXERR;

pidPadre = getpid();
pidAnterior = pidPadre;

if(argc == 1){
if(sprintf(mensaje, "Faltan argumentos\n") < 0)return EXERR;
if (write(0, mensaje, strlen(mensaje)) == -1)return EXERR;
return EXERR;
}
if(argc == 3){
if(strcmp(argv[2], "debug")== 0){
modoD = 1;
}
else{
if(sprintf(mensaje, "Segundo argumento invalido\n") < 0)return EXERR;
if(write(0, mensaje, strlen(mensaje)) == -1)return EXERR;
return EXERR;
}
}
n = atoi(argv[1]);
if(n <= 0){
if(sprintf(mensaje, "N?? procesos inv??lido, debe ser mayor que 0\n") < 0)return
EXERR;
if(write(0, mensaje, strlen(mensaje)) == -1)return EXERR;
return EXERR;
}

for(i = 0; i < n; i++){
pid = fork();
if(pid == -1){
raise(SIGTERM);
}
else if(pid == 0){
pidSiguiente = pidAnterior;
break;
}
else{
pidAnterior = pid;
numProc++;
}
}
if(getpid() == pidPadre){
pidSiguiente = pidAnterior;
if (kill(pidSiguiente, SIGUSR1) != 0){
kill(0, SIGINT);
for(i = 0; i < numProc; i++){
wait(&n);
}
sprintf(mensaje, "\nEjecucion concluida erroneamente\n");
write(0, mensaje, strlen(mensaje));
_Exit(-1);
}
}

if (sigdelset(&bloqueo, SIGUSR1) != 0) kill(pidPadre, SIGTERM);
if (sigdelset(&bloqueo, SIGTERM) != 0) kill(pidPadre, SIGTERM);
if (sigdelset(&bloqueo, SIGINT) != 0) kill(pidPadre, SIGTERM);
if (sigprocmask(SIG_SETMASK, &bloqueo, NULL) != 0) kill(pidPadre, SIGTERM);

while(1){
entrar = 0;
if(modoD){
sonoAlarma = 0;
alarm(1);
while(!sonoAlarma){
sigsuspend(&blockAlarm);
}
}
entrar = 1;
sigsuspend(&bloqueo);
}
return 0;
}

void morir(int sennal){
int i;
int codigo;
char mensaje[85];
if(getpid() != pidPadre){
_Exit(0);
}
else{
for(i = 0; i < numProc; i++){
wait(&codigo);
}
sprintf(mensaje, "\nEjecucion concluida correctamente\n");
write(0, mensaje, strlen(mensaje));
_Exit(0);
}
}

void testigo(int sennal){
char mensaje[40];
struct timespec duerme;

if(entrar == 1){
entrar = 0;
if(modoD){
sonoAlarma = 0;
if(sprintf(mensaje, "E(%d)", getpid()) < 0) kill(SIGTERM, pidPadre);
if(write(0, mensaje, strlen(mensaje)) == -1) kill(SIGTERM, pidPadre);
alarm(2);
while(!sonoAlarma){
sigsuspend(&blockAlarm);
}
}
else{
if(sprintf(mensaje, "E") < 0) kill(SIGTERM, pidPadre);
if(write(0, mensaje, strlen(mensaje)) == -1) kill(SIGTERM, pidPadre);
}
if(modoD) {
if(sprintf(mensaje, "S(%d)", getpid()) < 0) kill(SIGTERM, pidPadre);
}
else {
if(sprintf(mensaje, "S") < 0) kill(SIGTERM, pidPadre);
}
if(write(0, mensaje, strlen(mensaje)) == -1) kill(SIGTERM, pidPadre);
}
if(modoD){
duerme.tv_sec = 0;
duerme.tv_nsec = 100000;
while(nanosleep(&duerme, &duerme));
}
if (kill(pidSiguiente, SIGUSR1) != 0) kill(pidPadre, SIGTERM);
return;
}

void alarma(int sennal){
sonoAlarma = 1;
return;
};

void error(int sennal){
kill(0, SIGINT);
raise(SIGINT);
}

No hay comentarios: