La práctica consiste en implementar el problema de los productores/consumidores utilizando
las llamadas al sistema para manejo de memoria compartida (shmget, shmat,
shmdt, shmctl) y para manejo de semáforos (semget, semctl,
semop).
La práctica constará de un único programa
llamado sem
que se encargará de crear todos los procesos productores y
consumidores. Este programa recibirá tres parámetros como
entrada:
$ sem -p num_productores -c num_consumidores -n num_pasos
donde se indican cuántos productores y consumidores
entrarán en juego, así como el número de
operaciones que realizará cada uno ya sea de inserción,
en el caso de los productores, o de extracción en el caso de los
consumidores.
Para crear todos los procesos, se utilizará la llamada fork().
Esta llamada crea una réplica (proceso hijo) del proceso actual (proceso padre). 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. La parte principal del proceso padre lanza todos
los productores y consumidores del siguiente modo:
... for (i=0;i<num_productores; i++) switch (fork()) { -1 : printf("error fork productor número %d\n",i); exit(1); 0 : productor(); exit(0); } for (i=0;i<num_consumidores; i++) switch (fork()) { -1 : printf("error fork consumidor número %d\n",i); exit(1); 0 : consumidor(); exit(0); } } /* El padre espera por los hijos */ for (i=0;i<num_productores+num_consumidores;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 todos los procesos hijos creados.
El "trabajo" que realizan los productores y consumidores es insertar
y extraer respectivamente una serie de números aleatorios en una
cola circular. Cada productor escribe en la cola un número
aleatorio entre i*100 e i*100+99, donde i es el número de
productor. Por ejemplo, el productor 0 escribe un número
aleatorio en el intervalo [0,99], el productor 1 en el intervalo
[100,199], el 2 en el intervalo [200,299], etc. Los consumidores van
tomando números de la cola e imprimiéndolos en pantalla.
La única salida que ofrecerán estos procesos se
programará usando exclusivamente la siguiente función:
void mostrarDato(char tipo, int i, int
dato) { char mensaje[MAX_CADENA]; time_t t; pid_t p; p=getpid(); t=time(NULL); if (tipo=='P') sprintf(mensaje, "Productor %d (%u): --->[%d] %s",i,p,dato,ctime(&t)); else sprintf(mensaje, "Consumidor %d (%u): [%d]---> %s",i,p,dato,ctime(&t)); write(STDOUT_FILENO,mensaje,strlen(mensaje)); } |
donde tipo
es un carácter igual a 'P' para
productores o a 'C' para
consumidores, i es el
número de productor o consumidor, y dato es
el número que se inserta o se extrae de la cola, dependiendo del
caso. Tanto los productores como los consumidores esperarán
(usando sleep)
un tiempo aleatorio de hasta 3 segundos antes de realizar una nueva
operación.
struct tCola { int numeros[TAM_COLA]; int rpos,wpos,num; }; |
struct tCola
cola; /* Código
incorrecto !!! */ |
pcola=(struct tCola *) shmat(id,0,0); |
#define MUTEXCOLA 0 #define LLENO 1 #define VACIO 2 |