Prima provetta di Sistemi Operativi 04/11/2011 SOLUZIONI 1. (1pt) Elencare e commentare brevemente i vantaggi/svantaggi dei Thread. I Threads sono una implementazione della concorrenza con condivisione dell’ambiente. Vantaggi: velocitá del context switch, maggiore efficienza dovuta al fatto che un thread bloccante non blocca gli altri, possibilitá di schedulare i thread su piú core, se disponibili, arrivando ad una programmazione parallela. Svantaggi: la condivisione delle risorse tra thread deve essere gestita e puo’ richedere un certo sovraccarico. 2. (3pt) Alla biblioteca tecnico-scientifica dell’Universita’ di Trieste c’e’ un terminale per le consultazioni. Entrando, vi accorgete che c’e’ una persona che sta consultando il terminale e che due persone stanno aspettando in coda. Calcolare la probabilita di dover aspettare pi di 30 minuti (Prob[tempo attesa¿30minuti]) supponendo che il tempo d’attesa sia una variable aleatoria esponenziale con media di 10minuti. Il significato del tempo d’attesa non é specificato. Si propongono due possibili interpretazioni. La prima é semplicemente di considerare il tempo d’attesa complessivo una variabile casuale distribuita esponenzialmente cioé con distribuzione f (t) = λe−λt con media R1/λ=10 (per cui λ = 0.1) della quale si vuole determinare la prob. che P (t > ∞ −3 30) = 30 λe−λt dt = −e−λt |∞ = 0.05. 30 = e Nella seconda interpretazione ogni utente presenta un tempo d’attesa distribuito esponenzialmente con media di 10minuti. Questo vuol dire che l’evento delle fine consultazioni forma un processo di Poisson con media 10minuti.La probabilitá di avere n −λt . Visto che ci sono tre utenti ciascuno dei n fine consultazioni in t é data da λt) n! e quali puó terminare la consultazione, la probabiliá che il nuovo utente aspetti piú di 30 minuti é la probabiliá di non avere né 0 né 1 né 2 fine consultazioni in 30 minuti 1 2 0 −λ30 −λ30 −λ30 cioé (1 − λ30) ) ∗ (1 − λ30) ) ∗ (1 − λ30) ) = 0.63 0! e 1! e 2! e 3. (4pt) Un WebServer puó essere modellato come segue Gli utenti accedono al server con frequenza pari a 50 richieste al secondo, il server recupera le informazioni richieste e le presenta agli utenti. Le informazioni alle quali 1 il server accede sono contenute in due dischi, ciascuno con frequenza di servizio µ2 e µ3 pari a 50. Le probabilitá alle quali vengono richieste le informazioni sui due dischi sono rispettivamente pari a 0.2 e 0.3. Calcolare la frequenza di servizio della CPU tale il numero medio di utenti presenti nel server sia pari a 50. Chiamando λ1, λ2 e λ3 le frequenze di arrivo sulla CPU, sul primo e sul secondo disco rispettivamente, si ha λ1 = λ + λ2 + λ3, e λ2 = 0.2λ1, λ3 = 0.3λ1 per cui λ1 = 2λ = 100, λ2 = 20, λ3 = 30. Quindi ρ1 = 100/µ1, ρ2 = 2/5, ρ3 = 3/5. Il ρ2 ρ3 ρ1 + 1−ρ2 + 1−ρ3 . Se impongo numero complessivo degli utenti nel server é N = 1−ρ1 N=50, e semplificando un pó, si ha 50 = 100/(µ1 − 100) + 2/3 + 3/2 da cui µ1 = 100 + 50/47.83 = 102.09 4. (2pt) Calcolare la frequenza media di arrivo l al Webserver del punto 3 tale che il tempo medio di attesa nella coda CPU sia minore di 10 secondi. ricorda: il numero medio di processi in coda ρ2 /(1 − ρ) Innanzitutto usiamo il valore di µ1 calcolato al punto 3 per semplificare le cose, 2 ρ12 = µ12λ1 cioé µ1 = 102.09. il numero medio di utenti in coda é 1−ρ1 −λ1µ1 . Per Little, il numero medio N di utenti in coda CPU é il prodotto della frequenza di arrivo in λ12 coda per il tempo d’attesa in coda: N = λ1T per cui λ1 = N/T = (µ12 −λ1µ1)T . 2 T µ1 Risolvendo questa equazione rispetto a λ1 si ha: λ1 = (1+T µ1)=101.99 Visto che λ1 = 2λ si ha λ = λ1/2 = 50.99 arrivi al secondo al Server. 5. (4pt) Tracciare il diagramma delle precedenze di un programma concorrente multithread per eseguire il seguenze prodotto scalare: (x*A)*(B*y) dove A e B sono due matrici con dimensione 2x3 e 3x2 rispettivamente, x, y sono due vettori di dimensione 1x2 e 2x1 rispettivamente, e il simbolo * rappresenta il prodotto righe per colonne. Implementare il diagramma usando la programmazione multithread in Java. I passi fondamentali di una possibile semplice implementazione sono illustrati nel seguito (nella classe data ci sono ovviamente le matrici A,B,X,Y,row,col e il valore result). public class XbyA extends Thread{ //prodotto del vettore riga X con la colonna ind di A data d; int ind; float s; int i; public XbyA(data d, int indxi){this.d=d; this.ind=indxi;} public void run(){ s=0; for(i=0;i<2;i++) s+=data.X[i]*data.A[i][ind]; data.row[ind]=s; } public class BbyY extends Thread{ //prodotto della riga ind di B con il vettore colonna Y data d; int ind; float s; int i; public BbyY(data d, int indxi){this.d=d; this.ind=indxi;} public void run(){ s=0; for(i=0;i<2;i++) s+=data.B[ind][i]*data.Y[i]; data.col[ind]=s; } public class RbyC extends Thread{ 2 data d; int i; float s; public RbyC(data d)[this.d=d;} public void run(){ s=0; for(i=0;i<3;i++) s+=data.row[i]*data.col[i]; data.result=s; } } public class principale { private static data d= new data(); public principale() {} public static void main(String[] args) { XbyA t1[]=new XbyA[3]; BbyY t2[]=new BbyY[3]; for(int i=0; i<3; i++){//istanzia i prodotti e fa partire t1[i]= new XbyA(d,i); t1[i].start(); } for(int i=0; i<3; i++){//istanzia i prodotti e fa partire t2[i]= new BbyY(d,i); t2[i].start(); } for(int i=0; i<3; i++){ t1[i].join(); } for(int i=0; i<3; i++){ t2[i].join(); } z=new RbyC(d);//quando sono finiti i prodotti in concorrenza faccio row x col z.start(); } Il diagramma delle precedenze sará allora: 6. (3pt) Scrivere lo pseudocodice di due metodi in Java per realizzare le primitive cobegin(lista dei thread) e coend(lista dei thread) mediante start e join. Uno schema puó essere: public class co{ T t[]=new T[N]; for(i=0;i<N;i++) t[i]= new T(i); 3 cobegin(t,N){ for(int i=0; i<N; i++){t[i].start();} } coend(t,N){ for(int i=0; i<N; i++){t[i].join();} } } dove T é definito come Thread e N é il loro numero. 7. (3pt) Scrivere lo pseudocodice del processo che gestisce il generico accesso al Webserver del punto 3 con il vincolo che non ci possano essere piú di 50 utenti che accedono al server. Realizzare il vincolo mediante un semaforo. Il programma controlla anche l’accesso del Webmaster bloccandolo se c’é almeno un accesso attivo. Si puó usare il problema dei lettori scrittori con un semaforo per bloccare gli utenti se maggiori di 50.Il processo puó essere realizzato come segue: Server() { down(web); down(mutex); n++; if(n==1) down(wm); up(mutex); recupera(); visualizza(); down(mutex); n--; if(n==0) up(wm); up(mutex); up(web); } webmaster() { down(wm); aggiorna(); up(wm); } Il meccanismo funziona cosı́: Server é un thread che viene attivato ad ogni accesso di un utente. ’web’ é un semaforo inizailizzato a 50. wm, mutex sono semafori binari inizalizzati a 1. Se ci sono 50 utenti, ci sono 50 thread attivi e ’web’ é a 0. Il 51-esimo utente si blocca su down(web) e aspetta che un utente finisca per alzare web. Quando tutti gli utenti finiscono web torna a 50. 8. (3pt) Che differenza c’ tra un semaforo spinlock e un semaforo waitlock? 4 Quando conveniente usare l’uno o l’altro? Fare un esempio di implementazione di un semaforo spinlock. Un semaforo spinlock aspetta usando attesa attiva mentre un semaforo waitlock cambia coda d’attesa e non consuma cicli macchina. Peró i semafori waitlock sono piú complessi degli spinlock dovendo essere gestiti dal kernel. É conveniente usare spinlock per sezioni critiche brevi e waitlock per sezioni critiche piú lunghe. Un esempio puó essere la primitiva atomica TSL(b) che restituisce il valore logico di b e lo mette a true. Esempio di utilizzo: lock() { while(TSL(b)) ; } unlock() { b=0; } 9. (4pt) Implementare il seguente grafo usando processi concorrenti realizzati con le chiamate di sistema fork/waitpid. Se fossero thread come si potrebbe implementare il grafo con le primitive cobegin/coend? Innazitutto il grafo viene semplificato portandono alla seguente forma: 5 Questo semplice grafo puó essere implementato con processi concorrenti segcondo il seguente flusso (naturalmente non é l’unico!) Implementando questo flusso mediante processi concorrenti usando fork/waitpid abbiamo: if((t0=fork())==0){exec(T0)} else{ waitpid(t0); if((t1=fork())==0){exec(T1)} else{ if((t3=fork())==0){exec(T3)} else{ if((t4=fork())==0){exec(T4)} else{ if((t2=fork())==0){exec(T2)} else{ waitpid(t3); waitpid(t1); waitpid(t4); waitpid(t2); if((t5=fork())==0){exec(T5)} else{ if((t6=fork())==0){exec(T6)} else{ waitpid(t5); waitpid(t6); if((t7=fork())==0){exec(T7)} } } } } } } 6 } Implementando il grafo con le primitive cobegin/coend abbiamo: T0(); cobegin begin cobegin T1(); T3(); coend; T5(); end; begin cobegin T2(); T4(); coend; T6(); end; coend; T7(); 10. (2pt) Si consideri il seguente pseudocodice relativo al problema dei 5 filosofi con un semaforo per ogni forchetta. class Filosofo extends Thread{ int fid; Semaforo sx, dx; public Filosofo(int i){this.fid=i} public void run(){ while(true) { Pensa(i); Affamato(i); sx.down(); dx.down(); Mangia(i); sx.up(); dx.up(); } } } Discutere questa soluzione e proporre una variante nel caso questa soluzione porti ad uno stallo. In questa soluzione, tutti i filosofi prendono la forchetta di sinistra e di destra rispettivamente, bloccandola. Questa soluzione porta sicuramente ad uno stallo perché se tutti i filosofi concorrentemente prendono le loro forchette di sinistra poi tutti cercano le forchette di destra ma vedono che sono giá impegnate e si bloccano. Una possibile soluzione é di mettere la down di un semaforo inizializzato a 4 prima di prendere le forchette e mettere una up dello stesso dopo averle rilasciate. A questo punto 7 posso avere al piú 4 filosofi concorrenti; il quinto si blocca e non blocca la forchetta. Un’altra possibilitá é di serializzare i filosofi mettendo un down(mutex) dopo il while e un up(mutex) dopo aver rilasciato le forchette. In questo modo non ho stallo ma non ho piú filosofi concorrenti. 11. (3pt) In un certo istante la situazione riguardo le risorse usate da 4 processi concorrenti la seguente: Risorse Allocate R1 R2 R3 Proc.1 2 3 1 Proc.2 4 4 0 Proc.3 2 2 0 Proc.4 4 2 3 Massimo numero di risorse richieste dai processi R4 R1 R2 R3 R4 0 4 4 3 2 0 5 5 5 5 0 4 4 3 2 2 4 4 3 2 mentre le 4 risorse sono disponibili nel seguente numero di istanze: W=[16, 15, 8, 6]. Verificare se questo uno stato sicuro o meno applicando l’Algoritmo del Banchiere. Il problema si puó risolvere costruendo la matrice delle richieste, Q=[matrice del numero massimo di risorse richieste] - [matrice delle risorse allocate]. Nel nostro caso Risorse Allocate R1 R2 R3 Proc.1 2 3 1 Proc.2 4 4 0 Proc.3 2 2 0 Proc.4 4 2 3 R4 0 0 0 2 Risorse Richieste R1 R2 R3 R4 2 1 2 2 1 1 5 5 2 2 3 2 0 2 0 0 risorse disponibili: [4 4 4 4] Quindi, puó essere soddisfatta la richiesta di Proc1 che rilasciando le risorse aumenta le risorse disponibili a [6 7 5 4] poi Proc3 portando le risorse disponibili a [8 9 5 4] poi Proc4 che porta le risorse disponibili a [12 11 8 6] e infine Proc2 che quando rilascia le risorse le riporta a [16 15 8 6]. Quindi lo stato é sicuro. 8