UNICAL - Esercitazioni di Sistemi Operativi - A.A. 2002-2003

Transcript

UNICAL - Esercitazioni di Sistemi Operativi - A.A. 2002-2003
Università degli Studi della Calabria
Corso di Laurea in Ingegneria Informatica
Lucidi delle esercitazioni di
Sistemi Operativi (Corsi A e B)
A.A. 2002/2003
1
Concetto di processo
‰ Nei primi sistemi di calcolo era consentita l’esecuzione di un solo
programma alla volta. Tale programma aveva il completo controllo del
sistema e accesso a tutte le sue risorse.
‰ Gli attuali sistemi di calcolo consentono, invece, che più programmi siano
caricati in memoria ed eseguiti concorrentemente. Essi sono detti
sistemi time-sharing o multitasking. In tali sistemi più programmi
vengono eseguiti dalla CPU, che commuta la loro esecuzione con una
frequenza tale da permettere agli utenti di interagire con ciascun
programma durante la sua esecuzione.
‰ L’unità di lavoro dei moderni sistemi time-sharing è il processo.
Sistemi Operativi
2
P.Trunfio
1
Definizione di processo
‰ Un processo può essere definito come un programma in
esecuzione. Un processo esegue le proprie istruzioni in modo
sequenziale, ovvero in qualsiasi momento viene eseguita al massimo
un’istruzione del processo.
‰ Un programma di per sé non è un processo: un programma è
un’entità passiva, come il contenuto di un file memorizzato su disco,
mentre un processo è una entità attiva, con un program counter che
specifica l’attività attuale (ovvero, quale sia la prossima l’istruzione da
eseguire) ed un insieme di risorse associate.
‰ Due processi possono essere associati allo stesso programma, ma sono
da considerare due sequenze di esecuzione distinte.
Sistemi Operativi
3
P.Trunfio
Processi indipendenti e cooperanti
I processi in esecuzione nel sistema operativo possono essere
indipendenti o cooperanti:
‰ Un processo è indipendente se non può influire su altri processi nel
sistema o subirne l’influsso. Un processo che non condivida dati
temporanei o permanenti con altri processi è indipendente.
‰ Un processo è cooperante se influenza o può essere influenzato da altri
processi in esecuzione nel sistema. Ovviamente, qualsiasi processo che
condivida dati con altri processi è un processo cooperante.
Sistemi Operativi
4
P.Trunfio
2
Cooperazione tra processi
Ci sono diversi motivi per fornire un ambiente che consenta la
cooperazione tra processi:
‰ Condivisione di informazioni. Consente a più utenti di condividere le
stesse informazioni (ad esempio un file).
‰ Accelerazione del calcolo. Si divide un problema in sottoproblemi che
possono essere eseguiti in parallelo. Un’accelerazione di questo tipo è
ottenibile solo se il computer dispone di più elementi di elaborazione
(come più CPU o canali di I/O)
‰ Modularità. Consente la realizzazione di un sistema modulare che divide
le funzioni in processi distinti.
Sistemi Operativi
5
P.Trunfio
Thread
‰ Un thread, anche detto lightweight process, è un flusso sequenziale di
esecuzione di istruzioni all’interno di un programma/processo.
‰ In un programma si possono far partire più thread che sono eseguiti
concorrentemente. Nei computer a singola CPU la concorrenza viene
simulata con una politica di scheduling che alterna l'esecuzione dei
singoli thread.
‰ Tutti i thread eseguono all’interno del contesto di esecuzione di un solo
processo, ovvero condividono le stesse variabili del programma.
‰ Ciascun processo tradizionale, o heavyweight, ha invece il proprio
contesto di esecuzione.
Sistemi Operativi
6
P.Trunfio
3
Thread in Java
‰ Tutti i programmi Java comprendono almeno un thread. Anche un
programma costituito solo dal metodo main viene eseguito come un
singolo thread. Inoltre, Java fornisce strumenti che consentono di creare
e manipolare thread aggiuntivi nel programma
Esistono due modi per implementare thread in Java:
‰ Definire una sottoclasse della classe Thread.
‰ Definire una classe che implementa l’interfaccia Runnable. Questa
modalità è più flessibile, in quanto consente di definire un thread che è
sottoclasse di una classe diversa dalla classe Thread.
Sistemi Operativi
7
P.Trunfio
Definire una sottoclasse della classe Thread
1. Si definisce una nuova classe che estende la classe Thread. La nuova
classe deve ridefinire il metodo run() della classe Thread.
2. Si crea un’istanza della sottoclasse tramite new.
3. Si chiama il metodo start() sull’istanza creata. Questo determina
l’invocazione del metodo run() dell’oggetto, e manda in esecuzione il
thread associato.
Sistemi Operativi
8
P.Trunfio
4
Esempio di sottoclasse di Thread
class Saluti extends Thread {
public Saluti(String nome) {
super(nome);
}
public void run() {
for (int i = 0; i < 10; i++)
System.out.println("Ciao da "+getName());
}
}
public class ThreadTest {
public static void main(String args[]) {
Saluti t = new Saluti("Primo Thread");
t.start();
}
}
Sistemi Operativi
9
P.Trunfio
Definire una classe che implementa Runnable
1. Si definisce una nuova classe che implementa l’interfaccia Runnable. La
nuova classe deve implementare il metodo run() dell’interfaccia
Runnable.
2. Si crea un’istanza della classe tramite new.
3. Si crea un’istanza della classe Thread, passando al suo costruttore un
riferimento all’istanza della nuova classe definita.
4. Si chiama il metodo start() sull’istanza della classe Thread creata,
determinando l’invocazione del metodo run() dell’oggetto Runnable
associato.
Sistemi Operativi
10
P.Trunfio
5
Esempio di classe che implementa Runnable
class Saluti implements Runnable {
private String nome;
public Saluti(String nome) {
this.nome = nome;
}
public void run() {
for (int i = 0; i < 10; i++)
System.out.println("Ciao da "+nome);
}
}
public class RunnableTest {
public static void main(String args[]) {
Saluti s = new Saluti("Secondo Thread");
Thread t = new Thread (s);
t.start();
}
}
Sistemi Operativi
11
P.Trunfio
La classe Thread (1)
Costruttori principali:
Thread (): crea un nuovo oggetto Thread.
Thread (String name): crea un nuovo oggetto Thread con nome name.
Thread (Runnable target): crea un nuovo oggetto Thread a partire
dall’oggetto target.
Thread (Runnable target, String name): crea un nuovo oggetto Thread con
nome name a partire dall’oggetto target.
Metodi principali:
String getName(): restituisce il nome di questo Thread.
void join() throws InterruptedException: attende fino a quando questo
Thread non termina l’esecuzione del proprio metodo run.
Sistemi Operativi
12
P.Trunfio
6
La classe Thread (2)
void join(long millis) throws InterruptedException: attende, per un tempo
massimo di millis millisecondi, fino a quando questo Thread non termina
l’esecuzione del proprio metodo run.
void run(): specifica le operazioni svolte dal Thread. Deve essere ridefinito
dalla sottoclasse, altrimenti non effettua alcuna operazione. Se il Thread è
stato costruito a partire da un oggetto Runnable, allora verrà invocato il
metodo run di tale oggetto.
static void sleep(long millis) throws InterruptedException: determina
l’interruzione dell’esecuzione del Thread corrente per un tempo di millis
millisecondi.
void start(): fa partire l’esecuzione del Thread. Viene invocato il metodo run
di questo Thread.
static void yield(): determina l’interruzione temporanea del Thread corrente,
e consente ad altri Thread di essere eseguiti.
Sistemi Operativi
13
P.Trunfio
La classe Thread (3)
static Thread currentThread(): restituisce un riferimento all’oggetto Thread
attualmente in esecuzione.
void setPriority(int newPriority): cambia la priorità di questo Thread.
int getPriority(): restituisce la priorità di questo Thread.
Costanti della classe:
static final int MAX_PRIORITY: la massima priorità (pari a 10) che un
Thread può avere.
static final int MIN_PRIORITY: la minima priorità (pari ad 1) che un Thread
può avere.
static final int NORM_PRIORITY: la priorità (pari a 5) che viene assegnata
di default ad un Thread.
Sistemi Operativi
14
P.Trunfio
7
Ciclo di vita di un Thread
yield()
New
start()
Runnable
Not Runnable
Il metodo run() termina
Dead
‰ New: subito dopo l’istruzione new le variabili sono state allocate e
inizializzate; il thread è in attesa di passare nello stato Runnable.
‰ Runnable: il thread è in esecuzione o in coda di attesa per ottenere
l’utilizzo della CPU.
‰ Not Runnable: il thread non può essere messo in esecuzione dallo
scheduler. Entra in questo stato quando è in attesa di operazioni di I/O,
oppure dopo l’invocazione del metodo sleep(), o del metodo wait(), che
verrà discusso in seguito.
‰ Dead: al termine dell’esecuzione del suo metodo run().
Sistemi Operativi
15
P.Trunfio
Scheduling dei Thread
La Java Virtual Machine (JVM) è in grado di eseguire una molteplicità di
thread su una singola CPU:
‰ lo scheduler della JVM sceglie il thread in stato Runnable con priorità più
alta;
‰ se più thread in attesa di eseguire hanno uguale priorità, la scelta dello
scheduler avviene con una modalità ciclica (round-robin).
Il thread messo in esecuzione dallo scheduler viene interrotto se e solo se:
‰ il metodo run termina l’esecuzione;
‰ il thread esegue yield();
‰ un thread con priorità più alta diventa Runnable;
‰ il quanto di tempo assegnato si è esaurito (solo su sistemi che
supportano time-slicing).
Sistemi Operativi
16
P.Trunfio
8
Un programma sequenziale (1)
class Printer {
private int from;
private int to;
public Printer (int from, int to) {
this.from = from;
this.to = to;
}
public void print () {
for (int i = from; i <= to; i++)
System.out.print (i+"\t");
}
}
public class PrinterApp {
public static void main (String args[]) {
Printer p1 = new Printer (1,10);
Printer p2 = new Printer (11,20);
p1.print();
p2.print();
System.out.println ("Fine");
}
}
Sistemi Operativi
P.Trunfio
17
Un programma sequenziale (2)
L’esecuzione di PrinterApp genera sempre il seguente output:
1
11
Fine
2
12
3
13
4
14
5
15
6
16
7
17
8
18
9
19
10
20
I metodi:
p1.print()
p2.print()
System.out.println(“Fine”)
sono eseguiti in modo sequenziale.
Sistemi Operativi
18
P.Trunfio
9
Un programma threaded (1)
class TPrinter extends Thread {
private int from;
private int to;
public TPrinter (int from, int to) {
this.from = from;
this.to = to;
}
public void run () {
for (int i = from; i <= to; i++)
System.out.print (i+"\t");
}
}
public class TPrinterApp {
public static void main (String args[]) {
TPrinter p1 = new TPrinter (1,10);
TPrinter p2 = new TPrinter (11,20);
p1.start();
p2.start();
System.out.println ("Fine");
}
}
Sistemi Operativi
P.Trunfio
19
Un programma threaded (2)
L’esecuzione di TPrinterApp potrebbe generare il seguente output:
1
2
3
4
5
6
7
8
9
11
12
13
14
15
16
17
18
19
Fine
10
20
oppure:
Fine
1
7
2
15
3
8
11
16
4
9
12
17
5
10
13
18
6
19
14
20
2
13
19
11
14
20
12
6
10
3
7
4
15
Fine
8
16
17
9
oppure:
1
5
18
e così via: non è possibile fare alcuna assunzione sulla velocità relativa di
esecuzione dei thread. L’unica certezza è che le operazioni all’interno di un
dato thread procedono in modo sequenziale.
Sistemi Operativi
20
P.Trunfio
10
Imporre la sequenzialità
Per imporre la sequenzialità nell’esecuzione dei diversi thread si può fare
uso del metodo join().
public class TPrinterApp {
public static void main (String args[]) {
TPrinter p1 = new TPrinter (1,10);
TPrinter p2 = new TPrinter (11,20);
p1.start();
try {
p1.join();
} catch (InterruptedException e) {
System.out.println(e);
}
p2.start();
try {
p2.join();
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println ("Fine");
}
}
Sistemi Operativi
public class PrinterApp {
public static void main (String args[]) {
Printer p1 = new Printer (1,10);
Printer p2 = new Printer (11,20);
p1.print();
p2.print();
System.out.println ("Fine");
}
}
21
P.Trunfio
Somma in concorrenza (1)
class Sommatore extends Thread {
private int da;
private int a;
private int somma;
public Sommatore (int da, int a) {
this.da = da;
this.a = a;
}
public int getSomma() {
return somma;
}
public void run () {
somma = 0;
for (int i = da; i <= a; i++)
somma += i;
}
}
Sistemi Operativi
22
P.Trunfio
11
Somma in concorrenza (2)
public class Sommatoria {
public static void main (String args[]) {
int primo = 1;
int ultimo = 100;
int intermedio = (primo+ultimo)/2;
Sommatore s1 = new Sommatore (primo,intermedio);
Sommatore s2 = new Sommatore (intermedio+1,ultimo);
s1.start();
s2.start();
try {
s1.join();
s2.join();
} catch (InterruptedException e) { System.out.println (e); }
System.out.println (s1.getSomma()+s2.getSomma());
}
}
Sistemi Operativi
23
P.Trunfio
Thread con attività ciclica (1)
Ciclo finito:
public void run ()
{
for (int i = 0; i < n; i++)
{
istruzioni
}
}
Ciclo infinito:
public void run ()
{
while (true)
{
istruzioni
}
}
Sistemi Operativi
24
P.Trunfio
12
Thread con attività ciclica (2)
Ciclo con terminazione:
class myThread extends Thread {
private boolean continua;
public myThread () {
continua = true;
}
public vodi termina () {
continua = false;
}
public void run () {
while (continua)
{
istruzioni
}
}
}
Sistemi Operativi
25
P.Trunfio
Una classe Clock (1)
class Clock extends Thread {
private boolean continua;
public Clock () {
continua = true;
}
public void block () {
continua = false;
}
public void run () {
int i = 1;
while (continua) {
try {
sleep (1000);
} catch (InterruptedException e){System.out.println (e);}
if (continua)
System.out.print ("\n"+i);
i++;
}
}
}
Sistemi Operativi
26
P.Trunfio
13
Una classe Clock (2)
class ClockController extends Thread {
private Clock clock;
public ClockController (Clock clock) {
this.clock = clock;
}
public void run () {
Console.readString ("Press enter to start");
clock.start();
Console.readString ("Press enter to stop");
clock.block();
}
}
public class ClockTest {
public static void main (String args[]) {
Clock t = new Clock();
new ClockController(t).start();
}
}
Sistemi Operativi
27
P.Trunfio
Sincronizzazione dei Processi (1a parte)
„ Introduzione
„ Il problema della sezione critica
„ Soluzioni hardware per la sincronizzazione
„ Semafori
Sistemi Operativi
28
P.Trunfio – R.Ortale
14
Introduzione
„ L’accesso concorrente a dati condivisi può
portare all’inconsistenza dei dati.
„ Per garantire la consistenza dei dati sono
necessari meccanismi per assicurare
l’esecuzione ordinata dei processi cooperanti.
Sistemi Operativi
29
P.Trunfio – R.Ortale
Bounded-Buffer (1)
„ Dati condivisi
class item {
...
}
…
int BUFFER_SIZE = 10;
item buffer[] = new item [BUFFER_SIZE];
int in = 0;
int out = 0;
int counter = 0;
…
Sistemi Operativi
30
P.Trunfio – R.Ortale
15
Bounded-Buffer (2)
„ Processo produttore
item nextProduced;
while (true) {
nextProduced = new item (…);
while (counter == BUFFER_SIZE) ; /* do nothing */
buffer[in] = nextProduced;
in = (in + 1) % BUFFER_SIZE;
counter++;
}
Sistemi Operativi
31
P.Trunfio – R.Ortale
Bounded-Buffer (3)
„ Processo consumatore
item nextConsumed;
while (true) {
while (counter == 0) ; /* do nothing */
nextConsumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
counter--;
}
Sistemi Operativi
32
P.Trunfio – R.Ortale
16
Bounded-Buffer (4)
„ Le istruzioni:
counter++;
counter--;
devono essere eseguite atomicamente.
„ Una operazione atomica è una operazione che si
completa nella sua interezza senza interruzioni.
Sistemi Operativi
33
P.Trunfio – R.Ortale
Bounded-Buffer (5)
„ L’istruzione “counter++” può essere implementata in
linguaggio macchina come:
register1 = counter
register1 = register1 + 1
counter = register1
„ L’istruzione “counter--” può essere implementata come:
register2 = counter
register2 = register2 – 1
counter = register2
Sistemi Operativi
34
P.Trunfio – R.Ortale
17
Bounded-Buffer (6)
„ Nel caso in cui il produttore ed il consumatore
cercassero di aggiornare il buffer contemporaneamente,
le istruzioni in linguaggio assembly potrebbero risultare
interfogliate (interleaved).
„ L’interleaving dipende da come i due processi
produttore e consumatore sono schedulati.
Sistemi Operativi
35
P.Trunfio – R.Ortale
Bounded-Buffer (7)
„ Si assuma che counter sia inizialmente 5. Un possibile
interleaving delle istruzioni è:
producer: register1 = counter (register1 = 5)
producer: register1 = register1 + 1 (register1 = 6)
consumer: register2 = counter (register2 = 5)
consumer: register2 = register2 – 1 (register2 = 4)
producer: counter = register1 (counter = 6)
consumer: counter = register2 (counter = 4)
„ Il valore di counter potrebbe essere o 4 o 6, mentre il
valore corretto dovrebbe essere 5.
Sistemi Operativi
36
P.Trunfio – R.Ortale
18
Race Condition
„ Race condition: la situazione in cui più processi
accedono e manipolano dati condivisi in modo
concorrente. Il valore finale dei dati condivisi dipende
da quale processo ha finito per ultimo.
„ Per eliminare le race condition, i processi concorrenti
devono essere sincronizzati.
Sistemi Operativi
37
P.Trunfio – R.Ortale
Il problema della sezione critica
„ n processi che competono per usare dati condivisi.
„ Ciascun processo ha un segmento di codice, chiamato
sezione critica, nel quale i dati condivisi sono acceduti.
„ Problema: garantire che quando un processo è in
esecuzione nella sua sezione critica, nessun altro
processo sia autorizzato ad eseguire la propria
sezione critica.
Sistemi Operativi
38
P.Trunfio – R.Ortale
19
Soluzione al problema della sezione critica
Devono essere soddisfatti i seguenti requisiti:
1) Mutua esclusione. Se il processo Pi è in esecuzione nella propria
sezione critica, nessuna altro processo può essere in esecuzione
nella sua sezione critica.
2) Progresso. se nessun processo è in esecuzione nella propria
sezione critica, allora soltanto i processi che cercano di entrare
nella sezione critica partecipano alla decisione di chi entrerà
davvero, e questa decisione deve avvenire in un tempo finito.
3) Attesa limitata. Deve esistere un limite nel numero di volte che altri
processi sono autorizzati ad entrare nelle rispettive sezioni critiche
dopo che un processo Pi ha fatto richiesta di entrare nella propria
sezione critica e prima che quella richiesta sia soddisfatta.
¾ Si assume che ciascun processo esegua a velocità non nulla
¾ Nessuna assunzione deve essere fatta sulla velocità relativa degli n
processi.
Sistemi Operativi
39
P.Trunfio – R.Ortale
Sezione critica: soluzioni
„ Soltanto 2 processi, P0 e P1
„ Struttura generale del processo Pi
while (true) {
entry section
sezione critica
exit section
sezione non critica
}
„ I processi possono far uso di variabili condivise per
sincronizzare le loro azioni.
Sistemi Operativi
40
P.Trunfio – R.Ortale
20
Algoritmo 1
„ Variabili condivise:
) int turn;
) Inizialmente turn = 0
) Quando turn = i ⇒ Pi può entrare in esecuzione
nella propria sezione critica
„ Processo Pi
while (true) {
while (turn != i) ;
sezione critica
turn = j;
sezione non critica
}
„ Soddisfa la condizione della mutua esclusione, ma non
quella del progresso (richiede una stretta alternanza dei
processi)
Sistemi Operativi
41
P.Trunfio – R.Ortale
Algoritmo 2
„ Variabili condivise:
) boolean flag[] = new boolean[2];
) Inizialmente flag[0] e flag[1] sono false.
) Quando flag[i] = true ⇒ Pi è pronto ad entrare nella
sua sezione critica
„ Processo Pi
while (true) {
flag[i] = true;
while (flag[j]) ;
sezione critica
flag[i] = false;
sezione non critica
}
„ Soddisfa la condizione della mutua esclusione, ma non
quella del progresso
Sistemi Operativi
42
P.Trunfio – R.Ortale
21
Algoritmo 3
„ Usa le variabili condivise degli algoritmi 1 e 2.
„ Processo Pi
while (true) {
flag[i] = true;
turn = j;
while (flag[j] && turn == j) ;
sezione critica
flag[i] = false;
sezione non critica
}
„ Soddisfa tutti e tre i requisiti; tuttavia vale solo per due
processi.
Sistemi Operativi
43
P.Trunfio – R.Ortale
Algoritmo del Fornaio (1)
Sezione critica per n processi:
„ Prima di entrare nella sezione critica, il
processo riceve un numero. Il possessore del
numero più piccolo entra nella sezione critica.
„ Se i processi Pi e Pj ricevono lo stesso numero,
se i < j, allora Pi è servito prima; altrimenti Pj è
servito prima.
„ Lo schema di numerazione genera sempre
numeri in ordine crescente; ad esempio:
1,2,3,3,3,3,4,5...
Sistemi Operativi
44
P.Trunfio – R.Ortale
22
Algoritmo del Fornaio (2)
„ La notazione < indica l’ordinamento lessicografico tra
coppie del tipo: (ticket #, process id #)
) (a,b) < (c,d) se a < c oppure a == c && b < d
) max (a0,…, an-1) è un numero k, tale che k ≥ ai
per i : 0,…, n – 1
„ Dati condivisi
boolean choosing[] = new boolean[n];
int number[] = new int [n];
Le strutture dati sono inizializzate a false e 0
rispettivamente
Sistemi Operativi
45
P.Trunfio – R.Ortale
Algoritmo del Fornaio (3)
while (true) {
choosing[i] = true;
number[i] = max(number[0], number[1], …, number [n – 1])+1;
choosing[i] = false;
for (j = 0; j < n; j++) {
while (choosing[j]) ;
while ((number[j] != 0) && ( (number[j],j) < (number[i],i) ) ;
}
sezione critica
number[i] = 0;
sezione non critica
}
Sistemi Operativi
46
P.Trunfio – R.Ortale
23
Soluzioni hardware per la sincronizzazione
„ Controlla e modifica atomicamente il contenuto di una
parola.
boolean TestAndSet (boolean &target) {
boolean rv = target;
target = true;
return rv;
}
Sistemi Operativi
47
P.Trunfio – R.Ortale
Mutua esclusione con Test-and-Set
„ Dati condivisi:
boolean lock = false;
„ Processo Pi
while (true) {
while (TestAndSet(lock)) ;
sezione critica
lock = false;
sezione non critica
}
Sistemi Operativi
48
P.Trunfio – R.Ortale
24
Hardware di Sincronizzazione
„ Scambia atomicamente il valore di due variabili.
void Swap (boolean &a, boolean &b) {
boolean temp = a;
a = b;
b = temp;
}
Sistemi Operativi
49
P.Trunfio – R.Ortale
Mutua Esclusione con Swap
„ Dati condivisi:
boolean lock, key; // lock inizializzato a false
„ Processo Pi
while (true) {
key = true;
while (key == true)
Swap(lock,key);
sezione critica
lock = false;
sezione non critica
}
Sistemi Operativi
50
P.Trunfio – R.Ortale
25
Semafori
„ Strumento di sincronizzazione che in determinate
implementazioni non richiede busy waiting.
„ Semaforo S: variabile intera
„ Può essere acceduta esclusivamente attraverso due
operazioni indivisibili (atomiche):
wait (S):
while (S <= 0) ; /* do no-op */
S--;
signal (S):
S++;
Sistemi Operativi
51
P.Trunfio – R.Ortale
Sezione critica di n Processi
„ Dati condivisi:
semaforo mutex; // inizialmente mutex = 1
„ Processo Pi:
while (true) {
wait(mutex);
sezione critica
signal(mutex);
sezione non critica;
}
Sistemi Operativi
52
P.Trunfio – R.Ortale
26
Implementazione dei Semafori
„ Definiamo un semaforo:
class Semaforo {
int value;
List listaProcessi = new List ();
}
„ Assumiamo che esistano le seguenti operazioni:
) block() sospende il processo che la invoca.
) wakeup(P) riprende l’esecuzione del processo
bloccato P.
Sistemi Operativi
53
P.Trunfio – R.Ortale
Operazioni associate ai Semafori
Supponiamo che sia dichiarato il Semaforo S:
wait(S):
S.value--;
if (S.value < 0) {
aggiunge questo processo a S.listaProcessi;
block();
}
signal(S):
S.value++;
if (S.value <= 0) {
rimuove un processo P da S.listaProcessi;
wakeup(P);
}
Sistemi Operativi
54
P.Trunfio – R.Ortale
27
Semaforo come strumento generale di
sincronizzazione
„ Esegue B in Pj solo dopo che A esegue in Pi
„ Usa il semaforo flag inizializzato a 0
„ Codice:
Pj
M
wait(flag)
B
Pi
M
A
signal(flag)
Sistemi Operativi
55
P.Trunfio – R.Ortale
Deadlock e Starvation
„ Deadlock – due o più processi sono indefinitamente in
attesa per un evento che può essere causato da uno
soltanto dei processi in attesa.
„ Siano S e Q due semafori inizializzati a 1
P1
P0
wait(S);
wait(Q);
wait(Q);
wait(S);
M
M
signal(S);
signal(Q);
signal(Q)
signal(S);
„ Starvation – blocking indefinito. Un processo A potrebbe
non essere mai rimosso dalla coda del semaforo in cui è
sospeso.
Sistemi Operativi
56
P.Trunfio – R.Ortale
28
Due tipi di semafori
„ Semaforo contatore – valore intero che può spaziare su
un dominio illimitato.
„ Semaforo binario – valore intero che può valere solo 0 e
1; può essere più semplice da implementare.
„ E’ possibile implementare un semaforo contatore usando
semafori binari.
Sistemi Operativi
57
P.Trunfio – R.Ortale
Sincronizzazione dei Processi (2a parte)
„ Problemi classici di sincronizzazione
„ Regioni critiche
„ Monitor
Sistemi Operativi
58
P.Trunfio – R.Ortale
29
Problemi classici di sincronizzazione
„ Problema del Bounded-Buffer
„ Problema dei Lettori e degli Scrittori
„ Problema dei Cinque Filosofi
Sistemi Operativi
59
P.Trunfio – R.Ortale
Problema del Bounded-Buffer
„ Dati condivisi:
Semaforo full, empty, mutex;
Inizialmente:
full = 0, empty = n, mutex = 1
Sistemi Operativi
60
P.Trunfio – R.Ortale
30
Bounded-Buffer: Processo Produttore
while (true) {
…
produce un item in nextp
…
wait(empty);
wait(mutex);
…
aggiunge nextp al buffer
…
signal(mutex);
signal(full);
}
Sistemi Operativi
61
P.Trunfio – R.Ortale
Bounded-Buffer: Processo Consumatore
while (true) {
wait(full);
wait(mutex);
…
rimuove un item dal buffer e lo inserisce in nextc
…
signal(mutex);
signal(empty);
…
consuma l’item in nextc
…
}
Sistemi Operativi
62
P.Trunfio – R.Ortale
31
Problema dei Lettori-Scrittori
„ Dati condivisi:
Semaforo mutex, wrt;
int readcount;
Inizialmente:
mutex = 1, wrt = 1, readcount = 0
Sistemi Operativi
63
P.Trunfio – R.Ortale
Lettori-Scrittori: Processo Scrittore
wait(wrt);
…
scrive
…
signal(wrt);
Sistemi Operativi
64
P.Trunfio – R.Ortale
32
Lettori-Scrittori: Processo Lettore
wait(mutex);
readcount++;
if (readcount == 1)
wait(wrt);
signal(mutex);
…
legge
…
wait(mutex);
readcount--;
if (readcount == 0)
signal(wrt);
signal(mutex);
Sistemi Operativi
65
P.Trunfio – R.Ortale
Problema dei Cinque Filosofi
„ Dati condivisi:
Semaforo chopstick[] = new Semaforo [5];
Inizialmente tutti i semafori valgono 1
Sistemi Operativi
66
P.Trunfio – R.Ortale
33
Problema dei Cinque Filosofi
„ Filosofo i:
while (true) {
wait(chopstick[i]);
wait(chopstick[(i+1) % 5]);
…
mangia
…
signal(chopstick[i]);
signal(chopstick[(i+1) % 5]);
…
pensa
…
}
Sistemi Operativi
67
P.Trunfio – R.Ortale
Regioni critiche
„ Costrutto di sincronizzazione di alto livello.
„ Una variabile condivisa v di tipo T, è dichiarata come:
v: shared T
„ La variabile v viene acceduta solo dentro uno statement:
region v when B do S
dove B è una espressione booleana.
„ Mentre lo statement S è in esecuzione, nessun altro
processo può accedere la variabile v.
Sistemi Operativi
68
P.Trunfio – R.Ortale
34
Regioni critiche
„ Regioni che fanno riferimento alla stessa variabile
condivisa si escludono reciprocamente.
„ Quando un processo prova ad eseguire lo statement
della regione critica, l’espressione booleana B viene
valutata. Se B è true, S viene eseguita. Se è false, il
processo viene ritardato fintanto che B diventa true e
nessun altro processo si trova nella regione critica
associata con v.
Sistemi Operativi
69
P.Trunfio – R.Ortale
Esempio: Bounded-Buffer
„ Dati condivisi:
class buffer {
int pool[] = new int[n];
int count, in, out;
}
Sistemi Operativi
70
P.Trunfio – R.Ortale
35
Bounded-Buffer: Processo Produttore
„ Il processo produttore inserisce nextp nel buffer
condiviso:
region buffer when (count < n) do {
pool[in] = nextp;
in = (in+1) % n;
count++;
}
Sistemi Operativi
71
P.Trunfio – R.Ortale
Bounded-Buffer: Processo Consumatore
„ Il processo consumatore rimuove un item dal buffer
condiviso e lo inserisce in nextc:
region buffer when (count > 0) do {
nextc = pool[out];
out = (out+1) % n;
count--;
}
Sistemi Operativi
72
P.Trunfio – R.Ortale
36
Monitor (1)
„ Costrutti di sicronizzazione di alto livello che consentono la condivisione
sicura di un tipo di dati astratto tra processi concorrenti.
monitor monitor-name
{
dichiarazione di variabili condivise
procedure entry P1 (…) {
...
}
procedure entry P2 (…) {
...
}
procedure entry Pn (…) {
...
}
monitor-name (…) {
codice di inizializzazione
}
}
Sistemi Operativi
73
P.Trunfio – R.Ortale
Monitor (2)
„ Per consentire ad un processo di attendere all’interno di un monitor,
si deve dichiarare una variabile condition:
condition x, y;
„ Una variabile condition può essere manipolata solo attraverso le
operazioni wait e signal.
) L’operazione
x.wait();
sospende il processo che la invoca fino a quando un altro
processo non invoca:
x.signal();
) L’operazione x.signal risveglia esattamente un processo. Se
nessun processo è sospeso l’operazione di signal non ha
effetto.
Sistemi Operativi
74
P.Trunfio – R.Ortale
37
Rappresentazione concettuale di un Monitor
Sistemi Operativi
75
P.Trunfio – R.Ortale
Monitor di Hoare e di Hansen
Supponiamo che un processo P esegua x.signal, ed esista un processo
sospeso Q associato alla variabile condition x. Dopo la signal, occorre evitare
che P e Q risultino contemporaneamente attivi all’interno del monitor:
„ Hansen: Q attende che P lasci il monitor o si metta in attesa su un’altra
variabile condition.
„ Hoare: P attende che Q lasci il monitor o si metta in attesa su un’altra
variabile condition. Questa soluzione è preferibile: infatti consentendo al
processo P di continuare (soluzione di Hansen), la condizione logica attesa da
Q può non valere più nel momento in cui Q viene ripreso.
„ Una soluzione di compromesso: P deve lasciare il monitor nel momento
stesso dell’esecuzione dell’operazione signal, in quanto viene immediatamente
ripreso il processo Q. Questo modello è meno potente di quello di Hoare,
perchè un processo non può effettuare una signal più di una volta all’interno di
una stessa procedura.
Sistemi Operativi
76
P.Trunfio – R.Ortale
38
Cinque Filosofi (1)
class Filosofi // una classe monitor
{
final int thinking=0, hungry=1, eating=2;
int state[] = new int[5]; // gli elementi valgono 0, 1 o 2
condition self[] = new condition[5];
void pickup(int i)
// lucidi seguenti
void putdown(int i)
// lucidi seguenti
void test(int i)
// lucidi seguenti
void init() {
for (int i = 0; i < 5; i++)
state[i] = thinking;
}
}
Sistemi Operativi
77
P.Trunfio – R.Ortale
Cinque Filosofi (2)
void pickup(int i) {
state[i] = hungry;
test(i);
if (state[i] != eating)
self[i].wait();
}
void putdown(int i) {
state[i] = thinking;
// controlla i vicini a destra e a sinistra
test((i+4) % 5);
test((i+1) % 5);
}
Sistemi Operativi
78
P.Trunfio – R.Ortale
39
Cinque Filosofi (3)
void test(int i) {
if ( (state[(i + 4) % 5] != eating) &&
(state[i] == hungry) &&
(state[(i + 1) % 5] != eating) ) {
state[i] = eating;
self[i].signal();
}
}
Sistemi Operativi
79
P.Trunfio – R.Ortale
Implementazione dei Monitor (1)
Implementazione dei monitor tramite semafori:
„ Variabili:
Semaforo mutex;
Semaforo next;
int nextCount = 0;
// (inizialmente = 1)
// (inizialmente = 0)
„ Ciascuna procedura atomica F deve essere sostituita da:
wait(mutex);
…
corpo di F;
…
if (nextCount > 0)
signal(next);
else
signal(mutex);
„ La mutua esclusione all’interno di un monitor è così assicurata.
Sistemi Operativi
80
P.Trunfio – R.Ortale
40
Implementazione dei Monitor (2)
„ Per ciascuna variabile condition x, abbiamo:
Semaforo xSem;
int xCount = 0;
// (inizialmente = 0)
„ L’operazione x.wait() può essere implementata come:
xCount++;
if (nextCount > 0)
signal(next);
else
signal(mutex);
wait(xSem);
xCount--;
Sistemi Operativi
81
P.Trunfio – R.Ortale
Implementazione dei Monitor (3)
„ L’operazione x.signal() può essere implementata come:
if (xCount > 0) {
nextCount++;
signal(xSem);
wait(next);
nextCount--;
}
Sistemi Operativi
82
P.Trunfio – R.Ortale
41
Implementazione dei Monitor (4)
„ Costrutto wait con priorità: x.wait(c);
) c: espressione intera valutata quando l’operazione wait viene
eseguita.
) Il valore di c (un numero di priorità) memorizzato con il nome
del processo che è sospeso.
) Quando viene eseguito x.signal, il processo che ha il più basso
numero di priorità viene svegliato.
„ Verifica due condizioni per stabilire la correttezza del
sistema:
) I processi utente devono sempre effettuare le invocazioni sui
metodi del monitor secondo una sequenza corretta.
) Occorre assicurare che i processi non cooperanti non tentino di
accedere alle variabili condivise direttamente, bypassando la
mutua esclusione fornita dai monitor.
Sistemi Operativi
83
P.Trunfio – R.Ortale
Monitor in Java (1)
Tutti i thread che fanno parte di una determinata applicazione Java condividono lo
stesso spazio di memoria, quindi è possibile che più thread accedano
contemporaneamente allo stesso metodo o alla stessa sezione di codice di un
oggetto.
Per evitare inconsistenze, e garantire meccanismi di mutua esclusione e
sincronizzazione, Java supporta la definizione di oggetti “monitor”.
In Java, un monitor è l’istanza di una classe che definisce uno o più metodi
synchronized. E’ possibile definire anche soltanto una sezione di codice (ovvero un
blocco di istruzioni) come “synchronized”.
In Java ad ogni oggetto è automaticamente associato un “lock”: per accedere ad un
metodo o a una sezione synchronized, un thread deve prima acquisire il lock
dell’oggetto.
Il lock viene automaticamente rilasciato quando il thread esce dal metodo o dalla
sezione synchronized, o se viene interrotto da un’eccezione. Un thread che non
riesce ad acquisire un lock rimane sospeso sulla richiesta della risorsa fino a quando
il lock non diventa disponibile.
Sistemi Operativi
84
P.Trunfio
42
Monitor in Java (2)
Ad ogni oggetto contenente metodi o sezioni synchronized viene
assegnata una sola variabile condition, quindi due thread non possono
accedere contemporaneamente a due sezioni synchronized diverse di uno
stesso oggetto.
L’esistenza di una sola variabile condition per ogni oggetto rende il modello
Java meno espressivo di un vero monitor, che presuppone la possibilità di
definire più sezioni critiche per uno stesso oggetto.
Sistemi Operativi
85
P.Trunfio
Monitor in Java (3)
Ogni oggetto Java fornisce un insieme di metodi di sincronizzazione:
void wait() throws InterruptedException: blocca l’esecuzione del thread invocante, in
attesa che un altro thread invochi i metodi notify() o notifyAll() sullo stesso oggetto
monitor. Prima di bloccare la propria esecuzione il thread rilascia il lock.
void wait(long timeout) throws InterruptedException: è una variante di wait().
Blocca l’esecuzione del thread invocante, che viene risvegliato o dall’invocazione dei
metodi notify() o notifyAll(), o quando sia trascorso un tempo pari a timeout
millisecondi. L’invocazione di wait(0) equivale all’invocazione di wait().
void notify(): risveglia un unico thread in attesa sull’oggetto monitor corrente. Se più
thread sono in attesa, la scelta avviene in maniera arbitraria, dipendente
dall’implementazione della Java Virtual Machine. Il thread risvegliato compete con
ogni altro thread, come di norma, per ottenere la risorsa condivisa.
void notifyAll(): esattamente come notify(), ma risveglia tutti i thread in attesa
sull’oggetto corrente. È necessario tutte le volte in cui più thread possano essere
sospesi su differenti sezioni critiche dello stesso oggetto (in quanto esiste una unica
coda d’attesa).
Sistemi Operativi
86
P.Trunfio
43
Bounded Buffer con sincronizzazione Java (1)
public class BoundedBuffer
{
private int buffer[];
private int in=0, out=0, count=0, dim;
public BoundedBuffer (int dim)
{
this.dim=dim;
buffer=new int[dim];
}
public synchronized int get ()
{
/* lucidi seguenti */
}
}
public synchronized void put (int item)
{
/* lucidi seguenti */
}
Sistemi Operativi
87
P.Trunfio – R.Ortale
Bounded Buffer con sincronizzazione Java (2)
public synchronized int get ()
{
while (count==0)
{
try { wait(); }
catch (InterruptedException e) { }
}
int item = buffer[out];
out=(out+1)%dim;
count--;
notifyAll();
return item;
}
Sistemi Operativi
88
P.Trunfio – R.Ortale
44
Bounded Buffer con sincronizzazione Java (3)
public synchronized void put (int item)
{
while (count==dim)
{
try { wait(); }
catch (InterruptedException e) { }
}
buffer[in] = item;
in=(in+1)%dim;
count++;
notifyAll();
}
Sistemi Operativi
89
P.Trunfio – R.Ortale
Bounded Buffer con sincronizzazione Java (4)
public class Consumer extends Thread
{
private BoundedBuffer buffer;
private int id;
public Consumer (BoundedBuffer buffer, int id)
{
this.buffer = buffer;
this.id = id;
}
public void run ()
{
for (int i=0; i<10; i++)
{
int value = buffer.get();
System.out.println ("Consumer #"+id+" ha estratto "+value);
}
}
}
Sistemi Operativi
90
P.Trunfio – R.Ortale
45
Bounded Buffer con sincronizzazione Java (5)
public class Producer extends Thread
{
private BoundedBuffer buffer;
private int id;
public Producer (BoundedBuffer buffer, int id)
{
this.buffer = buffer;
this.id = id;
}
public void run ()
{
for (int i=0; i<10; i++)
{
buffer.put(i);
System.out.println ("Producer #"+id+" ha inserito "+i);
}
}
}
Sistemi Operativi
91
P.Trunfio – R.Ortale
Bounded Buffer con sincronizzazione Java (6)
public class BBTest
{
public static void main (String args[])
{
BoundedBuffer bb = new BoundedBuffer (10);
Producer pv[] = new Producer [5];
for (int i = 0; i < 5; i++) {
pv[i] = new Producer (bb,i);
pv[i].start();
}
}
}
Consumer cv[] = new Consumer [9];
for (int i = 0; i < 9; i++) {
cv[i] = new Consumer (bb,i);
cv[i].start();
}
Sistemi Operativi
92
P.Trunfio – R.Ortale
46
Lettori-Scrittori con sincronizzazione Java (1)
public class Database {
private int readerCount;
private boolean dbReading;
private boolean dbWriting;
public Database() {
readerCount = 0;
dbReading = false;
dbWriting = false;
}
public synchronized int startRead() { /* lucidi seguenti */ }
public synchronized int endRead() { /* lucidi seguenti */ }
public synchronized void startWrite() { /* lucidi seguenti */ }
public synchronized void endWrite() { /* lucidi seguenti */ }
}
Sistemi Operativi
93
P.Trunfio – R.Ortale
Lettori-Scrittori con sincronizzazione Java (2)
public synchronized int startRead()
{
while (dbWriting == true) {
try {
wait();
}
catch (InterruptedException e) { }
}
++readerCount;
if (readerCount == 1)
dbReading = true;
return readerCount;
}
Sistemi Operativi
94
P.Trunfio – R.Ortale
47
Lettori-Scrittori con sincronizzazione Java (3)
public synchronized int endRead()
{
--readerCount;
if (readerCount == 0)
{
dbReading=false;
notifyAll();
}
return readerCount;
}
Sistemi Operativi
95
P.Trunfio – R.Ortale
Lettori-Scrittori con sincronizzazione Java (4)
public synchronized void startWrite()
{
while (dbReading == true || dbWriting == true) {
try { wait(); }
catch (InterruptedException e) { }
}
dbWriting = true;
}
public synchronized void endWrite()
{
dbWriting = false;
notifyAll();
}
Sistemi Operativi
96
P.Trunfio – R.Ortale
48
Blocchi sincronizzati (1)
„ Anche blocchi di codice, oltre che interi metodi, possono essere
dichiarati synchronized.
„ Ciò consente di associare un lock la cui durata è tipicamente inferiore
a quella di un intero metodo synchronized.
Sistemi Operativi
97
P.Trunfio – R.Ortale
Blocchi sincronizzati (2)
public void syncronized F() {
// sezione non critica (p.es.: inizializzazione di variabili locali)
// sezione critica
// sezione non critica
}
public void F() {
// sezione non critica
synchronized (this) {
// sezione critica
}
// sezione non critica
}
Sistemi Operativi
98
P.Trunfio – R.Ortale
49
Il problema dello Sleeping Barber
E’ dato un salone di barbiere, avente un certo numero di posti d’attesa
ed un’unica poltrona di lavoro. Nel salone lavora un solo barbiere, il
quale è solito addormentarsi sulla poltrona di lavoro in assenza di clienti.
Arrivando nel salone, un cliente può trovare le seguenti situazioni:
• Il barbiere dorme sulla poltrona di lavoro. Il cliente sveglia il barbiere e si
accomoda sulla poltrona di lavoro, quindi il barbiere lo serve.
• Il barbiere sta servendo un altro cliente: se ci sono posti d’attesa liberi, il
cliente attende, altrimenti se ne va.
Scrivere in Java un programma che risolva tale problema, simulando
l’attività dei diversi soggetti (il Salone, il Barbiere, i Clienti) ed
evidenziandone su video lo stato.
L’implementazione della soluzione deve far uso delle opportune primitive
di sincronizzazione e mutua esclusione.
Sistemi Operativi
99
P.Trunfio – R.Ortale
Il problema dello Sleeping Barber
public class SleepingBarber {
public static void main (String args[]) {
int postiDiAttesa=5;
Salone s = new Salone (postiDiAttesa);
Barbiere b = new Barbiere (s);
b.start();
for (int i = 1; i <= 10; i++) {
Cliente c = new Cliente (s, i);
c.start ();
}
}
}
class Barbiere extends Thread { … }
class Cliente extends Thread { … }
class Salone { … }
Sistemi Operativi
100
P.Trunfio – R.Ortale
50
Barbiere
class Barbiere extends Thread
{
private Salone salone;
public Barbiere (Salone salone)
{
this.salone = salone;
}
public void run ()
{
while (true)
salone.servizio();
}
}
Sistemi Operativi
101
P.Trunfio – R.Ortale
Cliente (1)
class Cliente extends Thread {
private Salone salone;
private int id;
public Cliente (Salone salone, int id) {
this.salone = salone;
this.id = id;
}
public void run () {
while (true) {
int tempoDiRicrescita = 1000+(int)(Math.random()*3000);
System.out.println ("Il cliente "+id+" attende la ricrescita dei capelli. "+
"Tempo di ricrescita = "+tempoDiRicrescita);
try {
sleep (tempoDiRicrescita);
} catch (InterruptedException e) { System.out.println (e); }
Sistemi Operativi
102
P.Trunfio – R.Ortale
51
Cliente (2)
int tempoDiServizio = salone.entraSalone(id);
if (tempoDiServizio != -1) {
try {
sleep (tempoDiServizio);
} catch (InterruptedException e) { System.out.println (e); }
salone.lasciaSalone(id);
}
} // while
}
}
Sistemi Operativi
103
P.Trunfio – R.Ortale
Salone (1)
class Salone {
private int posti; // numero di posti d’attesa
private int postiOccupati; // numero di posti d’attesa occupati
private int clientiDaServire;
private boolean barbiereDorme;
private boolean poltronaOccupataDaCliente;
public Salone (int postiAttesa) {
posti = postiAttesa;
postiOccupati = 0;
clientiDaServire = 0;
barbiereDorme = true;
poltronaOccupataDaCliente = false;
}
public synchronized int entraSalone (int idCliente) { … }
public synchronized void lasciaSalone (int idCliente) { … }
public synchronized void servizio () { … }
}
Sistemi Operativi
104
P.Trunfio – R.Ortale
52
Salone: entraSalone (1)
public synchronized int entraSalone (int idCliente) {
System.out.println ("Il cliente "+idCliente+" entra nel salone");
if (postiOccupati == posti) {
System.out.println ("Il cliente "+idCliente+" non trova posto ed "+
"abbandona il salone senza essere servito");
return -1;
}
clientiDaServire++;
if (!barbiereDorme) { // il barbiere è occupato, ma ci sono posti liberi
System.out.println ("Il cliente "+idCliente+" si mette in attesa");
System.out.println ("Posti di attesa occupati = "+(++postiOccupati));
do {
try {
wait();
} catch (InterruptedException e) { System.out.println (e); }
} while (poltronaOccupataDaCliente);
System.out.println ("Posti di attesa occupati = "+(--postiOccupati));
}
Sistemi Operativi
105
P.Trunfio – R.Ortale
Salone: entraSalone (2)
else // il salone è vuoto
{
System.out.println ("Il cliente "+idCliente+" sveglia il barbiere");
notify(); // il cliente sveglia il barbiere
}
poltronaOccupataDaCliente = true; // il cliente occupa la poltrona
int tempoDiServizio = 1000+(int)(Math.random()*4000);
System.out.println ("Il cliente "+idCliente+" inizia ad essere servito. "+
"Tempo di servizio = "+tempoDiServizio+" ms");
return tempoDiServizio;
}
Sistemi Operativi
106
P.Trunfio – R.Ortale
53
Salone: lasciaSalone
public synchronized void lasciaSalone (int idCliente) {
System.out.println ("Il cliente "+idCliente+" e' stato servito e lascia il salone");
clientiDaServire--;
poltronaOccupataDaCliente = false;
System.out.println ("Posti di attesa occupati = "+postiOccupati);
if (postiOccupati > 0) {
System.out.println ("Si svegliano i clienti in attesa");
notifyAll();
}
}
Sistemi Operativi
107
P.Trunfio – R.Ortale
Salone: servizio
public synchronized void servizio () {
if (clientiDaServire == 0) {
System.out.println("Il barbiere approfitta dell'assenza di clienti per dormire");
barbiereDorme = true;
try {
wait();
} catch (InterruptedException e) {System.out.println (e);}
barbiereDorme = false;
}
}
Sistemi Operativi
108
P.Trunfio – R.Ortale
54
Semafori in Java (1)
Java non fornisce semafori, ma una classe semaforo può essere
costruita usando i meccanismi di sincronizzazione di Java:
public class Semaphore {
private int value;
public Semaphore() {
value = 0;
}
public Semaphore(int v) {
value = v;
}
public synchronized void P () { /* lucido seguente */ }
public synchronized void V () { /* lucido seguente */ }
}
Sistemi Operativi
109
P.Trunfio – R.Ortale
Semafori in Java (2)
public synchronized void P ()
{
if (--value < 0)
{
try { wait(); }
catch (InterruptedException e) { }
}
}
public synchronized void V ()
{
if (++value <= 0)
notify ();
}
Sistemi Operativi
110
P.Trunfio – R.Ortale
55
Lettori-Scrittori con semafori Java (1)
public class Reader extends Thread {
private Database server;
public Reader (Database db) {
server = db;
}
public void run () {
int c;
while (true) {
c = server.startRead();
/* lettura dal database */
c = server.endRead();
}
}
}
Sistemi Operativi
111
P.Trunfio – R.Ortale
Lettori-Scrittori con semafori Java (2)
public class Writer extends Thread {
private Database server;
public Writer (Database db) {
server = db;
}
public void run () {
while (true) {
server.startWrite();
/* scrittura sul database */
server.endWrite();
}
}
}
Sistemi Operativi
112
P.Trunfio – R.Ortale
56
Lettori-Scrittori con semafori Java (3)
public class Database
{
private int readerCount; // numero di lettori attivi
private Semaphore mutex; // per l’accesso a readerCount
private Semaphore db; // per l’accesso al database
public Database () {
readerCount = 0;
mutex = new Semaphore(1);
db = new Semaphore(1);
}
public int startRead() { /* lucidi seguenti */ }
public int endRead() { /* lucidi seguenti */ }
public void startWrite() { /* lucidi seguenti */ }
public void endWrite() { /* lucidi seguenti */ }
}
Sistemi Operativi
113
P.Trunfio – R.Ortale
Lettori-Scrittori con semafori Java (4)
public int startRead () {
mutex.P();
++readerCount;
if (readerCount == 1)
db.P();
mutex.V();
return readerCount;
}
Sistemi Operativi
114
P.Trunfio – R.Ortale
57
Lettori-Scrittori con semafori Java (5)
public int endRead() {
mutex.P();
--readerCount;
if (readerCount == 0)
db.V();
mutex.V();
return readerCount;
}
Sistemi Operativi
115
P.Trunfio – R.Ortale
Lettori-Scrittori con semafori Java (6)
public void startWrite() {
db.P();
}
public void endWrite() {
db.V();
}
Sistemi Operativi
116
P.Trunfio – R.Ortale
58
Cinque Filosofi con semafori Java (1)
public class Filosofo extends Thread {
private int id;
private Semaphore destro, sinistro;
public Filosofo (int id, Semaforo dx, Semaforo sx) {
this.id=id; destro=dx; sinistro=sx;
}
public void run () {
while (true) {
/* pensa */
destra.P(); sinistra.P();
/* mangia */
destra.V(); sinistra.V();
}
}
}
Sistemi Operativi
117
P.Trunfio – R.Ortale
Cinque Filosofi con semafori Java (2)
public class CinqueFilosofi {
public static void main (String args[]) {
Semaphore sv[] = new Semaphore[5];
for (int i=0; i < 5; i++)
sv[i]=new Semaphore(1);
Filosofo fv[] = new Filosofo[5];
for (int i=0; i < 5; i++)
fv[i]=new Filosofo(i,sv[(i+4)%5], sv[(i+1)%5]);
for (int i=0; i < 5; i++)
fv[i].start();
}
}
Sistemi Operativi
118
P.Trunfio – R.Ortale
59
Comunicazione tra processi (IPC)
„ Meccanismo per la comunicazione e la sincronizzazione dei
processi.
„ Sistema basato sullo scambio di messaggi: i processi comunicano
tra loro senza far uso di variabili condivise.
„ IPC fornisce due operazioni:
) send(message) – la dimensione del messaggio può essere fissa o
variabile
) receive(message)
„ Se P e Q vogliono comunicare, devono:
) Stabilire un canale di comunicazione tra loro
) Scambiare messaggi attraverso send/receive
„ Implementazione del canale di comunicazione:
) fisica (ad. es., memoria condivisa, bus hardware)
) logica (ad es., proprietà logiche)
Sistemi Operativi
119
P.Trunfio – R.Ortale
Problemi implementativi (1)
Per effettuare l’implementazione è necessario sapere:
„ Come vengono stabiliti i canali.
„ Se un canale può essere associato a più di due processi.
„ Quanti canali possono esistere tra ogni coppia di processi.
„ Che cosa si intende per capacità di un canale, cioè se il canale
dispone di spazio buffer, e in caso ne disponga occorre conoscere
la dimensione di questo spazio.
„ Che cosa si intende per dimensione dei messaggi. Occorre sapere
se il canale può supportare messaggi con dimensione variabile o
messaggi con dimensione fissa.
„ Se un canale è unidirezionale o bidirezionale, e cioè se i messaggi
possono fluire soltanto in una direzione, come per esempio da P a
Q, oppure in entrambe le direzioni.
Sistemi Operativi
120
P.Trunfio – R.Ortale
60
Problemi implementativi (2)
Inoltre esistono diversi metodi per effettuare l’implementazione logica
di un canale e delle operazioni send/receive:
„ Comunicazione diretta o indiretta.
„ Comunicazione simmetrica o asimmetrica.
„ Buffering automatico o esplicito.
„ Invio per copia o per riferimento.
„ Messaggi con dimensione fissa o dimensione variabile.
Sistemi Operativi
121
P.Trunfio – R.Ortale
Nominazione
I processi che vogliono comunicare devono disporre di un modo con
cui riferirsi agli altri processi; a tale scopo è possibile utilizzare:
„ Comunicazione diretta.
„ Comunicazione indiretta.
Sistemi Operativi
122
P.Trunfio – R.Ortale
61
Comunicazione diretta (1)
„ I processi devono indicare esplicitamente il nome del proprio
interlocutore:
) send (P, message) – Invia message al processo P.
) receive(Q, message) – Riceve, in message, un messaggio dal
processo Q.
„ All’interno di questo schema, un canale di comunicazione ha le
seguenti caratteristiche:
) Tra ogni coppia di processi che intendono comunicare viene stabilito
automaticamente un canale. Per comunicare i processi devono
conoscere solo le reciproche identità.
) Un canale è associato esattamente a due processi.
) Tra ogni coppia di processi comunicanti c’è esattamente un canale.
) Il canale può essere unidirezionale, ma usualmente è bidirezionale.
Sistemi Operativi
123
P.Trunfio – R.Ortale
Comunicazione diretta (2)
Esempio: una soluzione al problema del produttore-consumatore:
„ Produttore:
while (true) {
…
genera un elemento in nextp
…
send (consumatore, nextp);
}
„ Consumatore:
while (true) {
receive (produttore, nextc);
…
consuma l’elemento nextc
…
}
Sistemi Operativi
124
P.Trunfio – R.Ortale
62
Comunicazione diretta (3)
Nello schema precedente si usa una simmetria nell’indirizzamento,
cioè per poter comunicare il trasmittente ed il ricevente devono
nominarsi a vicenda. Una variante di questo schema utilizza un
indirizzamento asimmetrico:
„ send(P,message) – Invia message al processo P.
„ receive(id,message) – Riceve, in message, un messaggio da
qualsiasi processo; nella variabile id viene riportato il nome del
processo con il quale ha avuto luogo la comunicazione.
Sistemi Operativi
125
P.Trunfio – R.Ortale
Comunicazione indiretta (1)
„ I messaggi vengono inviati a dei mailbox (chiamati anche porte).
Ciascun mailbox è caratterizzato da un id univoco.
) I processi possono comunicare solo se condividono un mailbox.
„ Le primitive send e receive sono definite come segue:
send(A, message) – Invia message al mailbox A.
receive(A, message) – Riceve, in message, un messaggio dal
mailbox A.
Sistemi Operativi
126
P.Trunfio – R.Ortale
63
Comunicazione indiretta (2)
„ In questo schema le proprietà del canale di comunicazione sono le
seguenti:
) Tra una coppia di processi viene stabilito un canale solo se i processi
hanno un mailbox in comune.
) Un canale può essere associato a piu di due processi.
) Tra ogni coppia di processi comunicanti può esserci un certo numero di
canali diversi, ciascuno corrispondente a un mailbox.
) Un canale può essere unidirezionale o bidirezionale.
„ Il sistema operativo offre un meccanismo che permette ad un
processo le seguenti operazioni:
) Creare un nuovo mailbox.
) Inviare e ricevere messaggi tramite il mailbox.
) Distruggere un mailbox.
Sistemi Operativi
127
P.Trunfio – R.Ortale
Comunicazione indiretta (3)
Si supponga che i processi P1, P2 e P3 condividano il mailbox A. Il
processo P1 invia un messaggio ad A, mentre sia P2 che P3 eseguono
una receive da A. Quale processo riceverà il messaggio?
Tale problema può essere affrontato adottando una delle seguenti
soluzioni:
„ E’ possibile fare in modo che un canale sia associato al massimo a
due processi.
„ E’ possibile consentire ad un solo processo alla volta di eseguire
una operazione receive.
„ E’ possibile consentire al sistema di decidere arbitrariamente quale
processo riceverà il messaggio (il messaggio sarà ricevuto da P2 o
da P3, ma non da entrambi). Il sistema può comunicare l’identità del
ricevente al trasmittente
Sistemi Operativi
128
P.Trunfio – R.Ortale
64
Comunicazione indiretta (4)
Un mailbox può appartenere al processo o al sistema:
„ Nel caso appartenga a un processo, il mailbox è associato al
processo definito come parte di esso.
„ Un mailbox posseduto dal sistema operativo ha sua una esistenza
propria: è indipendente e non è legato ad alcun processo particolare.
Sistemi Operativi
129
P.Trunfio – R.Ortale
Comunicazione indiretta (5)
Il processo che crea un nuovo mailbox è per default il proprietario del
mailbox.
„ Inizialmente, il proprietario è l’unico processo in grado di ricevere
messaggi attraverso questo mailbox. Tuttavia, il diritto di proprietà e
il privilegio di ricezione possono essere passati ad altri processi per
mezzo di idonee system call.
„ I processi possono anche condividere una mailbox tramite la
funzione di creazione di un processo.
„ Quando un mailbox condiviso non è più utilizzato da alcun processo,
è necessario deallocare lo spazio di memoria ad esso associato.
Tale operazione è delegata ad un garbage collector gestito dal
sistema operativo.
Sistemi Operativi
130
P.Trunfio – R.Ortale
65
Sincronizzazione
Lo scambio di messaggi può essere bloccante o non bloccante:
„ Se è bloccante, lo scambio di messaggi è considerato
sincrono.
„ Se è non bloccante, lo scambio di messaggi è considerato
asincrono.
„ Le primitive send e receive possono essere bloccanti o non
bloccanti.
Sistemi Operativi
131
P.Trunfio – R.Ortale
Buffering (1)
Un canale ha una capacità che determina il numero dei messaggi che
possono risiedere temporaneamente al suo interno. Questa caratteristica
può essere immaginata come una coda di messaggi legata al canale.
Fondamentalmente esistono tre modi per implementare questa coda:
„ Capacità zero. Il mittende deve aspettare che il ricevitore sia
effettivamente pronto a ricevere un messaggio.
„ Capacità limitata. Il mittente deve attendere se il canale di
comunicazione è saturo.
„ Capacità illimitata. Il mittente non attende mai.
Sistemi Operativi
132
P.Trunfio – R.Ortale
66
Buffering (2)
Capacità zero:
La coda ha lunghezza massima 0, quindi il canale non può avere
messaggi in attesa al suo interno. In questo caso il trasmittente deve
attendere che il ricevente abbia ricevuto il messaggio. Affinchè il
trasferimento di messaggi abbia luogo, i due processi devono essere
sincronizzati. Questo tipo di sincronizzazione è chiamato rendez-vous.
Sistemi Operativi
133
P.Trunfio – R.Ortale
Buffering (3)
Capacità limitata:
La coda ha lunghezza finita n, quindi al suo interno possono risiedere
al massimo n messaggi. Se la coda non è piena quando viene inviato
un nuovo messaggio, quest’ultimo viene posto in fondo alla coda, il
messaggio viene copiato oppure viene tenuto un puntatore a quel
messaggio. Il trasmittente può proseguire la propria esecuzione senza
essere costretto ad attendere. Ma il canale ha comunque una capacità
limitata. Se il canale è pieno, il trasmittente deve attendere che nella
coda ci sia spazio disponibile.
Sistemi Operativi
134
P.Trunfio – R.Ortale
67
Buffering (4)
Capacità non limitata:
La coda ha potenzialmente lunghezza infinita, quindi al suo interno
può essere in attesa un numero indeterminato di messaggi. Il
trasmittente non resta mai in attesa.
Sistemi Operativi
135
P.Trunfio – R.Ortale
Buffering (5)
Nel caso in cui la capacità è diversa da zero, un processo non è in grado di
sapere se al termine dell’operazione send il messaggio sia arrivato alla sua
destinazione. Se questa informazione è indispensabile ai fini del calcolo, il
trasmittente deve comunicare esplicitamente con il ricevente per sapere se
quest’ultimo abbia ricevuto il messaggio.
Si supponga che il processo P invii un messaggio al processo Q e possa
continuare la propria esecuzione solo dopo che questo messaggio sia stato
ricevuto.
Il processo P esegue la sequenza:
send (Q, message);
receive (Q, message);
Il processo Q esegue la sequenza:
receive (P, message);
send (P, “acknowledgement”);
Sistemi Operativi
136
P.Trunfio – R.Ortale
68
Condizioni di eccezione (1)
Un sistema di messaggi è utilizzato soprattutto in un sistema distribuito. In
presenza di un guasto è necesario un ripristino dall’errore (gestione delle
condizioni di eccezione).
Le condizioni d’errore più frequenti sono:
„ Terminazione del processo.
„ Messaggi perduti.
„ Messaggi alterati
Sistemi Operativi
137
P.Trunfio – R.Ortale
Condizioni di eccezione (2)
Terminazione del processo.
Un messaggio trasmittente o ricevente può terminare prima che un messaggio sia
stato elaborato. Questa situazione comporta la presenza di messaggi che non
vengono mai ricevuti o di processi che restano in attesa di messaggi che non
verranno mai inviati. In particolare possono verificarsi i seguenti casi:
„ Un processo ricevente P attende un messaggio del processo Q che ha
terminato. Se non viene intrapresa alcuna azione P rimane bloccato per
sempre. In questo caso, il sistema può terminare l’esecuzione di P, oppure
informare P che Q ha terminato.
„ Il processo P invia un messaggio al processo Q che ha terminato. P continua
semplicemente la propria esecuzione. Per sapere se il messaggio sia stato
elaborato da Q, P deve essere esplicitamente programmato per la ricezione di
un acknowledgement. Nel caso senza buffering ci sono due soluzioni: il sistema
può terminare l’esecuzione di P, oppure informare P che Q ha terminato.
Sistemi Operativi
138
P.Trunfio – R.Ortale
69
Condizioni di eccezione (3)
Messaggi perduti.
Un messaggio dal processo P al processo Q può andare perduto in
qualche punto della rete di comunicazione, a causa di un guasto
dell’hardware o alla linea di comunicazione. Per trattare questo tipo di
eventi è possibile ricorrere ad uno dei tre metodi fondamentali:
„ Il sistema operativo è responsabile del rilevamento dell’evento in
questione e del nuovo invio del messaggio.
„ Il processo trasmittente è responsabile del rilevamento dell’evento in
questione e dell’eventuale nuova trasmissione del messaggio.
„ Il sistema operativo è responsabile del rilevamento dell’evento in
questione, quindi informa il processo trasmittente che il messaggio è
andato perduto. Il processo trasmittente può procedere come
preferisce.
Sistemi Operativi
139
P.Trunfio – R.Ortale
Condizioni di eccezione (4)
Messaggi alterati.
Il messaggio può essere consegnato alla propria destinazione, ma durante
il percorso può subire delle interferenze, per esempio a causa di disturbi
sul canale di comunicazione. Questo caso è simile a quello di un
messaggio perduto; di solito il sistema operativo ritrasmette il messaggio
originale.
Sistemi Operativi
140
P.Trunfio – R.Ortale
70