La práctica consiste en implementar el problema de la Barbería, con el objeto de
ver cómo funciona un problema típico de concurrencia y
uso de semáforos. Aprenderemos a crear procesos concurrentes en
UNIX con el uso de la llamada fork, y
a sincronizarlos construyendo semáforos
generales (siguiendo la implementación de Kearns) a
partir de semáforos binarios que simularemos con ficheros.
Problema de la
Barbería
En una barbería hay num_barberos barberos, cada uno con su
silla reclinable. Además, tenemos una sala de espera con num_sillas sillas normales
en las que se sientan los clientes para esperar su turno. El
funcionamiento de la barbería se rige por las siguientes normas:
void barbero() { while (1) { /* bucle infinito */ P(CLIENTES); /* se duerme si clientes es 0 */ Pb(MUTEX); ... /* decrementar los clientes_espera */ V(BARBEROS); Vb(MUTEX); mostrarMensaje("barbero","afeitando"); sleep(rand() % 4 + 1); /* espera aleatoria entre 1 y 4 segundos */ mostrarMensaje("barbero","termina de afeitar"); } |
donde las funciones P y V
corresponden a semáforos generales mientras que Pb y y Vb a
semáforos binarios. El pseudocódigo de un cliente
sería:
void cliente() { mostrarMensaje("cliente","ha llegado"); P(MUTEX); if (clientes_espera < num_sillas) { ... /* incrementar los clientes_espera */ V(CLIENTES); /* despierta al barbero */ Vb(MUTEX); P(BARBEROS); /* espera si no hay barbero libre */ mostrarMensaje("cliente","siendo afeitado"); } else { Vb(MUTEX); mostrarMensaje("cliente", "no atendido"); } } |
void mostrarMensaje(char *quien, char
*que) { char mensaje[MAX_CADENA]; time_t t=time(NULL); sprintf(mensaje, "%s pid %u: %s %s", quien, getpid(), que, ctime(&t) ); write(STDOUT_FILENO,mensaje,strlen(mensaje)); } |
de modo que, por ejemplo, un barbero afeitando haría la
llamada mostrarMensaje("barbero","afeitando").
La práctica constará de un único programa
llamado barberia
que se encargará de crear todos los procesos barberos y clientes
y controlar su finalización. Este programa recibirá tres
parámetros como
entrada:
$ barberia -b num_barberos -s num_sillas -c num_clientes
donde num_barberos indica
cuántos barberos hay disponibles, num_sillas indica el
número de sillas de espera y num_clientes es el
número de clientes que se van a generar.
Para crear todos los procesos, se utilizará la llamada fork().
Esta llamada crea una réplica o clon (proceso hijo) del proceso actual (proceso padre). Cuidado: la réplica
tiene
exactamente el mismo código y sus variables se inicializan con
el mismo valor que tenían las del padre en el momento de ser
invocada. Por tanto, justo después de llamar a fork(),
el programa no podría
distinguir si es el proceso padre o el
hijo. Para evitar esto, fork() devuelve
0 en el proceso hijo, y un valor >0 en el proceso padre (en
realidad, le devuelve al padre el pid, o identificador de proceso del
hijo que acaba de crear). Si se produce un
error, devuelve -1.
El proceso padre deberá crear inicialmente todos los barberos, guardando sus pid's en un array pid_barberos, para poder matarlos al terminar. Los procesos cliente se irán lanzando tras un retardo aleatorio de entre 1 a 7 segundos, para que su llegada no sea instantánea.
... for (i=0;i<num_barberos; i++) switch ( pid_barberos[i]=fork() ) { case -1 : printf("error fork barbero número %d\n",i); exit(1); case 0 : barbero(); exit(0); } for (i=0;i<num_clientes; i++) switch (fork()) { case -1 : printf("error fork cliente número %d\n",i); exit(1); case 0 : srand(getpid()); sleep(rand() % 7 + 1); /* espera aleatoria de 1 a 7 segs */ cliente(); exit(0); } } /* El padre espera por los hijos (clientes) */ for (i=0;i<num_clientes;i++) { sprintf(mensaje, "terminó el hijo %u\n",wait((int *)NULL)); write(STDOUT_FILENO,mensaje,strlen(mensaje)); } ... |
Como se puede ver, el proceso padre espera (wait) a
que finalicen un número de num_clientes
procesos hijos. Estos procesos que terminan serán sólo
clientes, ya que los barberos ejecutan un bucle infinito. Como
último paso, el proceso padre deberá hacer uso de la
llamada al sistema kill
para ir matando uno a uno los procesos barberos cuyos pid están
almacenados en pid_barberos.
int
clientes_espera; /*
Código
incorrecto !!! */ |
void Pb(char *nombre) { int df; /* intento continuamente crear en modo exclusivo... */ while ( (df=open(nombre, O_CREAT | O_EXCL, 0777)) == -1) ; /* cuando finalmente no falle la llamada, doy "paso" */ close(df); } void Vb(char *nombre) { unlink(nombre); } |