SOLUCIONES A ALGUNOS DE LOS EJERCICIOS DE SINCRONIZACION Y COMUNICACION ENTRE PROCESOS 1. Con las tres llamadas create, suspend, y resume, la estructura del programa sería la siguiente: process recoger; ...... suspend(); suspend(); recoger*; resume(pid2); resume(pid3); ....... process imprimir; ........ suspend(); imprimir*; resume(pid3); .......... process guardar; ....... suspend(); guardar*; resume(pid1); ....... process calcular; ....... suspend(); suspend(); calcular*; resume(pid1); resume(pid4); ........ ........ pid1 = create("recoger"); pid2 = create("guardar"); pid3 = create("calcular"); pid4 = create("imprimir"); resume(pid1); resume(pid1); resume(pid3); Esta solución presupone que las llamadas a resume() siempre tienen un correspondiente suspend() que las espera. En un sistema operativo si se hace una llamada para reanudar un proceso que no está suspendido el efecto es normalmente nulo. Pero si esto tuviera lugar en este programa ocasionaría la pérdida de señalizaciones con los correspondientes fallos de precedencia, y su funcionamiento sería incorrecto. Si suponemos que resume() devuelve un valor TRUE o FALSE dependiendo de si ha reanudado o no efectivamente el proceso que pretendía, podrían suplirse todas las llamadas a resume() por while (! resume(pidi)) { } ; pero aunque de este modo la lógica del problema estaría correctamente resuelta, sería a costa de pérdida de eficacia de la solución al introducir bucles de reanudaciones que consumen tiempo de UCP sin hacer trabajo útil. S1. a) Es segura, sin interbloqueo, y con posible aplazamiento indefinido.. Basta para ello advertir que inicialmente los valores de las variables c (global), d de p1 (local) y e de p2 (local) que intervienen en la negociación del acceso a la sección crítica forman la tripleta (1,0,0) y que las únicas operaciones que se efectúan son intercambios indivisibles sin modificación de valores. Por tanto los valores que se pueden alcanzar son: (1,0,0) ==> p1 y p2 están fuera de sus Secciones Críticas (0,1,0) ==> p1 está en su Sección Crítica, y p2 está fuera de la suya (0,0,1) ==> p1 está fuera de su Sección Crítica y p2 dentro de la suya Luego la exclusión mutua es segura. Para producirse interbloqueo deberían alcanzarse los valores (0,0,0): ambos procesos fuera y sin posibilidad de entrar, lo cual no es posible. El aplazamiento indefinido es posible puesto que no hay ningún mecanismo que impida que los valores estén oscilando entre (1,0,0) y (0,1,0), por ejemplo, sin dar opción a p2 a pasar a su sección crítica. b) La generalización a n procesos es inmediata. Sólo hay que repetir el esquema de cualquiera de los procesos p1 o p2 el número de veces que haga falta, para negociar la exclusión mutua con un (n+1)-tuple de valores de los cuales uno vale 1 y el resto 0. c) Si XCHG no fuera indivisible ya no se aseguraría la existencia invariante de uno y sólo un 1, y ello podría ocasionar fallos de exclusión mutua (ej. (0,1,1)) e interbloqueos (ej.(0,0,0)). 1 S3. a) Definición original con cola de espera WAIT: if (s == 0)) suspender en s.Q; else s = s - 1; Definición alternativa con cola de espera WAIT: s := s - 1; if (s < 0)) suspender en s.Q; SIGNAL: if (! s.Q vacía) reanudar un proceso de s.Q; else s = s + 1; SIGNAL: if (! s.Q vacía) reanudar un proceso de s.Q; {punto de interrupción} s := s + 1; La operación de decremento juntamente con la comprobación del valor de s debe formar un todo indivisible en la operación wait. Para la operación signal basta que sean indivisibles separadamente la operación de reanudación condicional y la de incremento. b) El valor negativo indica Ocupado con operación de espera (wait) en curso y, en el caso de implementación con cola de espera, el numero de procesos que han pasado a esperar desde la última señalización c) Sí afecta a SIGNAL. Hace que el incremento del valor del semáforo se haga siempre y no solo en el caso de que no haya ningún proceso esperando en cola. Otra posibilidad: Siendo Pb(s) y Vb(s) las operaciones de espera y señalización sobre semáforos binarios, es decir, Pb(s) ≡ while (s == 0) { } ; y Vb(s) ≡ s = 1; definimos wait(s) : Pb(mutex) s = s - 1; if (s < 0) { Vb(mutex); Pb(retardo) }; Vb(mutex); signal(s): Pb(mutex) s = s + 1; if (s <= 0) Vb(retardo) else Vb(mutex); S5. Declaraciones globales sem_t sillon, afeitado; mutex_t mutex; int sillas; boolean esperando; const int MAX = 5; Procesos Barbero() Cliente() 2 while (TRUE) { lock(mutex); if (sillas == 0) { esperando = TRUE; unlock(mutex); wait(sillon); unlock(mutex); esperando = FALSE }; sillas = sillas-1; unlock(mutex); Afeitar; post(afeitado); }; lock(mutex); if (sillas < MAX) { sillas = sillas+1; if (esperando) post(sillon); unlock(mutex); wait(afeitado); } else unlock(mutex); Inicialización (programa principal): sillas = 0; esperando = FALSE; sem_init(sillon, 0); sem_init(afeitado, 0); mutex_init(mutex); cobegin { Barbero(); Clientes() //tantas instancias de Cliente como se precise }; S5. Posible segunda solución. - ¿Funciona? Barbero while (TRUE) { wait (sala); lock (mutex); sillas = sillas-1; unlock (mutex); post (afeitando); Afeitar }; Clientes lock (mutex); if (sillas < MAX) { post (sala); sillas = sillas+1; unlock (mutex); wait (afeitando) } else unlock (mutex) Inicialización: sillas = 0; sem_init (afeitando, 1); sem_init (sala, 0); // sala, semáforo general mutex_init (mutex); cobegin { Barbero(); Clientes(), ...; }; S7. (a) El proceso LEER hace usos de dos procedimientos: leetarjeta (car) que lee un tarjeta de 80 columnas y rellena el array car de 80 caracteres put(buf, car) que coloca el carácter car en la posición siguiente del buffer ilimitado buf El proceso MODIFICAR hace uso de dos procedimientos get(buf, car) que toma el siguiente carácter del buffer ilimitado buf y lo asigna a car 3 Si el buffer esta vacío se queda esperando put(buf, car) El proceso IMPRIMIR hace uso de dos procedimientos get(buf, car) imprime(línea) que saca a la impresora el array línea de 125 caracteres Si los buffers los definimos así: typedef struct buffer { int indent, indsal; char elementos[infinito]; sem_t contador; } BUFFER; los procedimientos get() y put() serán: get(BUFFER buf; char car) { wait(buf.contador); car = buf.elementos[buf.indsal] buf.indsal = buf.indsal + 1; }; put(BUFFER buf:; char car) { buf.elementos[indent] = car; buf.indent = buf.indent + 1; post(buf.contador); }; y los procesos quedarían básicamente de la siguiente forma, comunicados a través de dos buffer infinitos buf1 y buf2: Leer: Modificar: Imprimir: i=0; while (! eof) { leetarjeta (car); for (i=0; i<80; i++) put(buf1,car[i]); put(buf1,' ') }; put(buf1,eof); get(buf1, car1); while (! eof) { get(buf1,car2); if ((car1=='*') && (car2=='*')) { ristra = TRUE; put(buf2,'/'); } else if (ristra) ristra = false; else put(buf2, car1); car1=car2; }; if (! ristra) put(buf2,car) i=0; while (! eof) { get(buf2,car); linea[i]=car; i=i+1; if (i == 125) { imprime(linea); i=0; } }; if (i != 0) imprime(linea); S8. (b) // program PCconCE; {productor/consumidor con cuenta de eventos} const int n = 5; contadores producido, consumido; elementos deposito[N]; void PRODUCTOR(int id) { int ip; ip = 0; while (TRUE) { await(consumido, ip-N); deposito[ip % n] := producir(); ip = ip+1; advance(producido); } 4 }; void CONSUMIDOR(int id) { int ic; ic = 0; while (TRUE) { await(producido, ic); consumir(deposito[ic % n]); ic = ic+1; advance(consumido) } }; main() { initial_event(producido,0); initial_event(consumido,0); cobegin { PRODUCTOR(0); CONSUMIDOR(0); }; }. MVC1. Una posible solución a este problema, con semáforos, es la siguiente: // Program Fumadores; sem_t ingrediente[3]; sem_t s[6]; mutex mutex ; sem_t sem; int t; void Agente() { int i, j; while (TRUE) { i = random(2) + 1; j = i; while (j == i) j = random(2) + 1; wait(sem); post(ingrediente[i]); post(ingrediente[j]); end; end; void Ayuda(int id) { const int incr[3] = {1,2,4}; while (TRUE) { wait(ingrediente[id]); lock(mutex); t = t + incr[id]; post(s[t]); unlock(mutex); }; }; }; void Fumador(int id) { const int nec[3] = (6,5,3); while (TRUE) { wait(s[nec[id]]); t = 0; post(sem); --- FUMA --}; 5 main() { t = 0; sem_init(sem, 1); mutex_init(mutex, 1); cobegin { Agente(); Ayuda(0);Ayuda(1); Ayuda(2); Fumador(0);Fumador(1);Fumador(2); } }. mutex se encarga de forzar la exclusión mutua al operar con t sem (Agente espera y Fumador[ ] señala) : permite que Agente saque un nuevo par de ingredientes t acumula los identificadores de hasta 2 ingredientes Resuelto con mutexes y variables condicionales // Program Fumadores; struct ingredientes {boolean tabaco,cerillas,papel;} = {FALSE,FALSE,FALSE}; mutex_t ingr_mutex; cond_t ingr_cond; void Agente() { int i, j; while (TRUE) { i = random(2) + 1; j = i; while (j == i) j = random(2) + 1; lock (ingr_mutex) while (tabaco || cerillas || papel) wait(ingr_cond2, ingr_mutex) tabaco = (i == 0) || (j == 0); cerillas = (i == 1) || (j == 1); papel = (i == 2) || (j == 2); broadcast(ingr_cond1); unlock (ingr_mutex) }; }; void FumadorTC() { while (TRUE) { lock(ingr_mutex); while (!(tabaco && cerillas)) wait(ingr_cond1, ingr_mutex); tabaco = FALSE; cerillas = FALSE; signal(ingr_cond2); unlock(ingr_mutex); --- FUMA --}; }; main() { 6 void FumadorPC() { semejante a Fumador TC pero con papel y cerillas } void FumadorTP { semejante a Fumador TC pero con tabaco y papel } cobegin { Agente(); FumadorTC(); FumadorPC(); FumadorTP(); } } MVC4. // programa ascensor Fichero CONTROL.c const int Npisos = 10; cond_t Petpisos[Npisos], aviso; int NpetPiso[Npisos]; mutex_t ctrlme; int piso_actual=1, piso_nuevo=-1, peticiones=0; void PulsarBoton(int piso) { lock(ctrlme); signalc(aviso); peticiones = peticiones+1; NpetPiso[piso]++; waitc(Petpisos[piso],ctrlme); NpetPiso[piso]--; unlock(ctrlme); }; void EsperarPeticiones() { lock(ctrlme); if (peticiones == 0) waitc(aviso,ctrlme); unlock(ctrlme); }; void ElegirMasCercano(int& dist) { int i; boolean sigue; lock(ctrlme); i= 0; sigue = TRUE; while ((i <= Npisos) && sigue) { if ((piso_actual+i <= Npisos) && (NpetPiso[piso_actual+i]>0)) { piso_nuevo = piso_actual+i; dist = i; sigue = FALSE; } else if ((piso_actual-i > 0) && (NpetPiso[piso_actual+i]>0)) { piso_nuevo = piso_actual -i; dist = -i; sigue = FALSE; }; i = i + 1; }; unlock(ctrlme); }; void SubirBajar() { lock(ctrlme); while (NpetPiso[piso_nuevo])>0) { signal(Petpisos[piso_nuevo]); peticiones = peticiones-1; }; 7 piso_actual = piso_nuevo unlock(ctrlme); }; void pasajero() { int origen, destino; origen = random(Npisos)+1; while (TRUE) { PulsarBoton(origen); do { destino = random(Npisos)+1 } while (destino == origen); PulsarBoton(destino); origen = destino; sleep(random(10)); } } void servidor() { integer dist, while (TRUE) { EsperarPeticiones; ElegirMasCercano(dist); sleep(ABS(dist)); SubirBajar; } } main() { cobegin { servidor; pasajero();pasajero();pasajero(); ...; } } MVC5. La definición de las operaciones P (wait) y V (signal) para semáforos acotados son las siguientes: P(s): while (s <= 0) { }; s := s - 1; V(s): while (s >= smax) { }; s := s + 1; El monitor que fuerza este comportamiento es el siguiente: Fichero (monitor) Sa const int smax = N, sem[NS]; cond_t c, d; mutex_t Saem void P(int s) { lock(Saem); while (sem[s] <= 0) wait(c,Saem); 8 sem[s]--; signal(d); // if (sem[s] < smax) signal(c); unlock(Saem); }; void V(int s); lock(Saem); if (sem[s] >= smax) wait(d, Saem); sem[s]++; signal(c); // if (sem[s] > 0) signal(c); unlock(Saem); }; MVC6. Fichero (monitor) LyE { int nlect=0, Lesp=0, Eesp=0; bool ocupado=FALSE; cond_t condL, condE; mutex_t LyEem; void IniciarL () { void AcabarL() { lock(LyEem); lock(LyEem); if (ocupado||(Eesp > 0)) { nlect = nlect-1; if (nlect == 0) Lesp = Lesp+1; wait(condL,LyEem); signal(condE); unlock(LyEem); Lesp = Lesp-1; }; }; nlect = nlect+1; signalc(condL); unlock(LyEem); }; void IniciarE() { lock(LyEem); if (ocupado||(nlect!=0) { Eesp = Eesp+1; wait(condE,LyEem); Eesp = Eesp-1; }; ocupado = TRUE; unlock(LyEem); }; void AcabarE() { lock(LyEem); ocupado = FALSE; if (Lesp > 0) signal(condL); else signal(condE); unlock(LyEem); }; Otra solución, si se dispone de la función empty(cola) que devuelve un valor lógico true o false: Fichero (monitor) LyE { int nlect=0; boolean ocupado=FALSE; cond_t condL, condE; mutext_t LyEem; 9 void IniciarL() { lock(LyEem); if (ocupado || !empty(condE)) wait(condL, LyEem); nlect = nlect+1; signal(condL); unlock(LyEem); }; void AcabarL() { lock(LyEem); nlect = nlect-1; if (nlect == 0) signal(condE); unlock(LyEem); }; void IniciarE; lock(LyEem); if (ocupado || (nlect != 0)) wait(condE, LyEem); ocupado = TRUE; unlock(LyEem); }; void AcabarE() { lock(LyEem); ocupado = FALSE; if (!empty(condL)) signal(condL); else signal(condE); unlock(LyEem); }; } MVC7. // programa Filosofos; Fichero(monitor) Mpalillos int Palillos[5]={2,2,2,2,2}; cond_t OK_Comer[5]; mutex_t Mpalem; int j; // rango 0..2; void Coger_Palillos (int i){ lock(Mpalem); while (Palillos[i] != 2) wait(OK_Comer[i],Mpalem); Palillos[(i+1) % 5] = Palillos[(i+1) % 5] - 1; Palillos[(i-1) % 5] = Palillos[(i-1) % 5] – 1 unlock(Mpalem); } void Soltar_Palillos (int lock(Mpalem); Palillos[(I+1) % 5] = Palillos[(I-1) % 5] = signal(OK_Comer[(I+1) signal(OK_Comer[(I-1) unlock(Mpalem); } } // MPalillos. i) { Palillos[(I+1) % 5] + 1; Palillos[(I-1) % 5] + 1; % 5]); % 5]); void Filosofo (int i) { while (TRUE) { Pensar; Coger_Palillos(i); Comer; Soltar_Palillos(i); } } 10 main() { cobegin { Filosofo(0); Filosofo(1); Filosofo(2); Filosofo(3); Filosofo(4); } } Demostración de la no existencia de interbloqueo: Si hubiera interbloqueo, nadie estaría comiendo Î SUMA (Palillos[]) = 10 todos están en espera Î SUMA (Palillos[]) <= 5 lo cual, evidentemente, es una contradicción. Puede existir aplazamiento indefinido al no haber una política que controle la actuación de los filósofos, por lo cual dos de ellos inmediatamente no consecutivos pueden cooperar para impedir la entrada a comer del filósofo intermedio. ME1. Basta sustituir wait por receive(mutex,msg) y signal por send(mutex, null) Lector while (TRUE) { receive(mutex, msg); nlect = nlect + 1; if (nlect == 1) receive(escr, msg); send(mutex, null); LEER receive(mutex, msg); nlect = nlect - 1; if (nlect == 0) send(escr, null); send(mutex, null); Otro proceso; }; Escritor: while (TRUE) { receive(escr, msg); ESCRIBIR; send(escr, null); Otro_proceso } inicialización nlect = 0; send(mutex, null); send(escr, null); cobegin { lectores; escritores; }; ME2. a) Mira alternativamente en cada buzón cada cierto tiempo b) Emplea un proceso separado para atender a cada buzón y se comunica con ellos a través de un buzón común P1: while (TRUE) { receive(M1,msg);send(MIyM2, msg); } P2: while (TRUE) { receive(M2,msg);send(MIyM2, msg); } boolean mirar(buzon, &msg) { receive(buzon, m); if (m!=null) {msg=m;return TRUE} else {send(buzon, null); return FALSE;} } while (TRUE) { while (mirar(M1,msg)) procesar(msg); while (mirar(M2,msg)) procesar(msg); sleep(rato); } while (TRUE) { receive(M1yM2, msg); procesar(msg); } 11 Nota: el mensaje “null” enviado en el apartado a) debe tener la menor prioridad posible, para garantizar que siempre es el último del buzón. 12