ENUNCIADO
La práctica consiste en generar cuatro procesos
que pretenden
acceder a una zona de exclusión mutua en la que
solo pueden
estar dos de ellos a la vez. El acceso a la zona de exclusión
mutua se regulará por un semáforo.
Cada proceso calculará un
número igual a la parte entera de la última
cifra de su PID
dividida por dos.
A ese número le sumará uno y le llamará
somnolencia. El proceso dormirá
tanto como indique su somnolencia,
esperará hasta entrar en la sección crítica,
dormirá dentro
tanto como indique su somnolencia y, vuelta a empezar.
Se puede usar la función sleep
, aunque no
system
, para dormir.
Cuando se pulse CTRL+C, todos los procesos acabarán
y el semáforo será borrado del Sistema Operativo.
Salida por pantalla: para comprobar el correcto comportamiento
del programa, la salida por pantalla será como sigue.
Al principio, el primer proceso limpiará la pantalla con
la función:
system("clear");
Cuando un proceso entre en la sección crítica
escribirá la
última cifra de su PID en la línea de la
pantalla cuyo número
es igual a ella. Cuando vaya a salir de la sección
crítica
borrará (escribirá un espacio encima) en dicha
línea. Así se
podrá observar a simple vista que no hay más de
dos procesos a
la vez en la sección crítica.
Para poner el cursor en la línea
7, columna 3, por ejemplo, se puede usar la función:
system("tput cup 7 3");
Observación: si usáis printf
para escribir en la
pantalla debéis saber que esta función tiene
un búfer intermedio
que hace que lo que se escriba no aparezca inmediatamente en
pantalla. El búfer se vacía en tres circunstancias:
- Se recibe un retorno del carro (
\n
).
- Se llena y no queda más remedio que volcarlo.
- Se ejecuta la función
fflush(stdout);
.
Tenedlo en consideración. Podéis encontrar en
/usuarios/profes/gyermo/SESION6/critica
el
ejecutable de esta práctica.
COMENTARIOS
La práctica con 0.25 es la de IorI:
/* ::::::::Esto tiene que ir antes de los ficheros de cabecera::::::::: */
#ifndef _HPUX_SOURCE
#define _HPUX_SOURCE
#endif
/* ::::::::Bibliotecas::::::::: */
/* ::::::::Ficheros de cabeceras, más bien::::::::: */
#include <stdio.h> /* printf()... */
#include <signal.h> /* sigaction()... */
#include <unistd.h> /* system(), fork(),... */
#include <sys/sem.h> /* semctl(), semop(),... */
#include <sys/wait.h> /* wait() */
/* ::::::::Macros, contantes::::::::: */
/* ::::::::¡Buena idea!::::::::: */
#define SEM_WAIT(x,n)\
x##.sem_num=n;\
x##.sem_op=-1;\
x##.sem_flg=0;
#define SEM_SIGNAL(x,n)\
x##.sem_num=n;\
x##.sem_op=1;\
x##.sem_flg=0;
/* ::::::::Variables Globales::::::::: */
/* ::::::::¡Yu, yu!::::::::: */
int semid;
pid_t pid[3];
/* ::::::::Prototipos::::::::: */
void newsigint(int signal);
void MEZ_algoritm(int semid); /* Mutual Exclusion Zone Algoritm :P */
/* Mutual Exclusion Zone Algorithm ;) */
/* ::::::::MAIN::::::::: */
/* Gracias por la información. */
int main(int argc, char *argv[])
{
int info;
struct sigaction new, old;
sigset_t mask;
if((semid=semget(IPC_PRIVATE, 1, IPC_CREAT | 0600)) == -1)
{
perror("Error en creación de semáforo");
exit(1);
}
semctl(semid,0,SETVAL,2); /* ¡¡¡Comprobación errores!!! */
system("clear");
switch(pid[0]=fork())
{
case -1 :
perror("Error en creación del segundo proceso");
exit(2);
case 0 : /* Segundo proceso */
MEZ_algoritm(semid);
default : /* Primer proceso */
switch(pid[1]=fork())
{
case -1 :
perror("Error en creación del tercer proceso");
exit(3);
case 0 : /* Tercer proceso */
MEZ_algoritm(semid);
default : /* Primer proceso */
switch(pid[2]=fork())
{
case -1 :
perror("Error en creación del cuarto proceso");
exit(4);
case 0 : /* Cuarto proceso */
MEZ_algoritm(semid);
default : /* Primer proceso */
new.sa_handler=newsigint;
sigemptyset(&mask);
new.sa_mask=mask;
new.sa_flags=SA_RESTART | SA_RESETHAND;
/* ¿Para qué sirven aquí estos flags?
Todavía SA_RESTART hace que los semops no fallen cuando se recibe una señal,
pero el otro... */
if(sigaction(SIGINT, &new, &old)==-1)
{
perror("Error en registro de SIGINT");
exit(-5);
}
MEZ_algoritm(semid);
}
}
}
}
/* ::::::::Funciones propias::::::::: */
void newsigint(int signal)
{
int i, data;
for(i=0;i<3;i++)
{
kill(pid[i],SIGINT);
wait(&data);
}
if(semctl(semid,0,IPC_RMID)==-1)
{
perror("Error en borrado de semáforo");
exit(5);
}
system("clear"); /* Bueno para borrar huellas... */
exit(0);
}
void MEZ_algoritm(int semid)
{
/* Poner código antes de haber acabado con las variables es propio de C++ */
int somnolencia=((getpid() % 10) >> 1) + 1;
/* Forma de dividir por dos -----^^^^ */
struct sembuf semact;
char command[20];
while(666)
{
sleep(somnolencia);
SEM_WAIT(semact,0);
if(semop(semid,&semact,1)==-1)
{
perror("Operación WAIT en semáforo");
exit(6);
}
sprintf(command,"tput cup %d 1",getpid() % 10);
system(command);
printf("%d\b",getpid() % 10);
fflush(stdout);
sleep(somnolencia);
printf(" \b");
fflush(stdout);
SEM_SIGNAL(semact,0);
if(semop(semid,&semact,1)==-1)
{
perror("Operación SIGNAL en semáforo");
exit(7);
}
}
}
MENCIONES ESPECIALES
- De Sagolpan y Satan (0.1 ptos.): fueron los primeros en usar
dos semáforos en vez de uno. En sus propias palabras:
/*****************************************************************************
En esta practica uso dos semaforos:
- Uno controla el acceso a la region critica.
- El otro controla la impresion; podria darse el caso (una vez entre un
millon) de que dos procesos accedan a la region critica a la vez, lo
hagan todo a la vez y se posicionen en la pantalla para escribir a la
vez. Entonces, en ellugar en el que se posiciono el ultimo se escribi-
rian los dos numeros.
*****************************************************************************/
Bueno, quizá no se expliquen como un libro abierto...
(Además del
pequeño detalle de "En esta práctica USO
dos semáf...", en lugar
de USAMOS :) ). Lo cierto es que, aunque no se dice en el enunciado,
tenemos un recurso compartido adicional, la salida por pantalla.
Este recurso es accedido simultáneamente por varios procesos.
Como sólo puede usarlo uno de por vez, hay que crear una zona
de exclusión mutua. Así evitamos situaciones del tipo:
- PROCESO 1: "Pongo el cursor en (3,7) y..."
- PROCESO 2: "Pongo el cursor en (3,4) y escribo un 4."
- PROCESO 1: "...escribo un 7" (encima del 4, aunque no
lo sé).
Su código es (cuidado, porque ninguno de los semops
tiene comprobación
de errores):
[...]
for(;;)
{
sleep(somnolencia);
sop.sem_num = 0; /* Wait semáforo principal */
sop.sem_op = -1;
sop.sem_flg = 0;
semop(semaforo, &sop, 1);
/*********REGION CRITICA**********/
sop.sem_num = 1;
semop(semaforo, &sop, 1); /*Este controla la escritura en pantalla*/
system(mandato);
printf("%d", getpid() % 10);
fflush(stdout);
system("tput cup 0 0");
sop.sem_op = 1;
semop(semaforo, &sop, 1); /*Abandono la escritura en pantalla*/
sleep(somnolencia);
sop.sem_op = -1;
semop(semaforo, &sop, 1); /*Vuelvo a escribir en pantalla*/
system(mandato);
printf(" ");
fflush(stdout);
sop.sem_op = 1;
semop(semaforo, &sop, 1); /*Acaba la escritura*/
sop.sem_num = 0;
semop(semaforo, &sop, 1); /* Signal semáforo principal */
/********FIN REGION CRITICA********/
}
- De Goku y Son Gohan (0.1 puntos):
Observad la misma técnica que el punto anterior,
pero programada por ellos:
int somnolencia,resto;
char orden[20],volver[20];
resto=pid%10;
somnolencia=(resto/2)+1;
sprintf(orden,"tput cup %d 3",resto);
sprintf(volver,"tput cup 0 0");
while(1)
{
sleep(somnolencia);
decrementar(SECCION_CRITICA);
decrementar(PANTALLA);
if(system(orden)==-1)
{
perror("pract6:system");
exit(8);
}
printf("%d",resto);
fflush(stdout);
if(system(volver)==-1)
{
perror("pract6:system");
exit(9);
}
incrementar(PANTALLA);
sleep(somnolencia);
decrementar(PANTALLA);
if(system(orden)==-1)
{
perror("pract6:system");
exit(10);
}
printf(" ");
fflush(stdout);
if(system(volver)==-1)
{
perror("pract6:system");
exit(11);
}
incrementar(PANTALLA);
incrementar(SECCION_CRITICA);
}
¿Necesita algún comentario?
- De Master of Puppets (0.1 puntos): Goku y Son Gohan crearon los
cuatro (en realidad ellos crearon cinco, se equivocaron) procesos
así:
for(i=0;i<NUM_PROCESOS;i++)
{
switch(fork())/*primer hijo*/
{
case -1:perror("pract4:fork");
return 5;
case 0:pid_hijo[i]=getpid();
if(pid_hijo[i]==-1)
{
perror("pract6:getpid");
return 6;
}
if(i==0)
{
if(system("clear")==-1)
{
perror("pract6:system");
return 7;
}
}
dormir(pid_hijo[i]);
}
}
Esto está bien, pero Master of Puppets me comentó
que se pueden
crear cuatro procesos con algo bastante más corto:
fork();
fork();
Y si no, pensadlo. Bueeeeno, no se tienen en cuenta errores.
Así que:
if (fork()==-1) {/* Error */}
if (fork()==-1) {/* Error */}
Aunque esta solución también tiene sus pegas. Si falla el
segundo fork()
y no el primero, queda un
proceso por ahí colgando. Venga, a ver si así
quedamos conformes:
res=fork();
if (res==-1) {perror("fork1"); exit(1);}
if (fork()==-1)
{kill(res==0?getppid():res, SIGTERM);
perror("fork2"); exit(2);}
Bastante más corto, ¿no?
LPEs
Los semáforos son difíciles de manejar
por dos circunstancias: primero,
hay que diseñar dónde colocarlos y qué
valores iniciales y operaciones
vamos a realizar sobre ellos. En segundo lugar, hay que saber
programar eso que se ha diseñado.
Me alegro mucho que esta sección
esté en blanco.
© 2000 Guillermo González Talaván.