Sei sulla pagina 1di 9

I Compendi OpenSource

di Giacomo Marciani

Teoria, Formulario e Suggerimenti Pratici

Sistemi Operativi

Sincronizzazione dei processi

La sincronizzazione dei processi1 quell'insieme di meccanismi che coordinano l'esecuzione concorrente di processi cooperanti, al ne di garantire la coerenza dei dati condivisi. Il context switch garantisce infatti la coerenza dei dati privati di ciascun usso, ma non dei dati condivisi tra ussi concorrenti.
Race Condition La race condition un errore software non deterministico2 per cui lo stato della memoria condivisa tra ussi di esecuzione concorrenti dipende dalla temporizzazione degli accessi alla memoria stessa: ne consegue una possibile incoerenza tra dati e logica dei ussi. Sezione critica Il codice di un processo pu essere suddiviso nelle seguenti sezioni:

sezione critica

uisiti


: accesso a dati condivisi; deve soddisfare i seguenti req-

: se un processo in esecuzione nella propria sezione critica, nessun altro processo pu eseguire la propria sezione critica.  progresso: se un processo desidera entrare nella propria sezione critica, solo i processi in esecuzione fuori dalle rispettive sezioni critiche possono partecipare alla scelta del processo che potr entrare per primo nella propria sezione critica; questa scelta non pu essere rimandata indenitivamente.  attesa limitata: se un processo ha gi richiesto l'ingresso nella propria sezione critica, esiste un limite al numero di volte in cui si consente ad altri processi di entrare nelle rispettive sezioni critiche prima che si accordi la richiesta del primo processo.
mutua esclusione

: richiesta e verica del permesso di accedere alla propria sezione critica.


sezione di ingresso sezione di uscita

: eventuali operazioni di uscita dalla sezione critica.

sezione non critica: restanti parti del codice per le quali non sussista alcun vincolo di esclusivit.

Soluzione di Peterson La soluzione di Peterson una soluzione software che realizza la sincronizzazione degli accessi alla sezione critica di due processi Pi e Pj , con j = 1 i, qualora sia soddisfatta la atomicit e la coerenza dell'accesso in memoria3 .
detta coordinazione dei processi. degli errori software pi insidiosi in quanto possono non apparire mai in fase di sviluppo e sono dicilmente replicabili, quindi dicilmente modellizzabili. 3 a causa della non atomicit delle moderne istruzioni elementari di load/store in alcuni calcolatori moderni, non detto che la soluzione di Peterson funzioni su tutti i sistemi.
1 anche 2 uno

boolean f l a g [ 2 ] ; // g l o b a l e i n t turno ; // g l o b a l e do { f l a g [ i ]= t r u e ; turno=j ; w h i l e ( f l a g [ j ] && turno==j ) ; // s e z i o n e c r i t i c a f l a g [ i ]= f a l s e ; // s e z i o n e non c r i t i c a } while ( true ) ;

ag e turno sono dati condivisi tra i due processi: turno segnala di chi sia il turno d'accesso alla sezione critica; mentre ag indica se un processo sia pronto per entrare nella propria sezione critica. La soluzione di Peterson soddisfa i requisiti della sezione critica. La mutua esclusione soddisfatta in quanto turno pu valere 0 o 1, ma non entrambi. Il progresso soddisfatto perch turno non viene modicato dentro il ciclo while, e ag viene modicato fuori dalla sezione critica. L'attesa limitata soddisfatta perch ag viene modicato nonappena fuori dalla sezione critica: ci determina l'ingresso di P i dopo al pi un ingresso di Pj .

Lock Un lock un meccanismo di sincronizzazione realizzato da una variabile il cui contenuto accessibile in modo atomico e coerente. Per accedere alla propria sezione critica un processo deve acquisire un lock, che restituir al momento dell'uscita. Tutti i meccanismi di sincronizzazione si basano sul concetto di lock. In un sistema monoprocessore si pu simulare un lock interdicendo le interruzioni durante l'esecuzione di una sezione critica4 . In un sistema multiprocessore questa soluzione comporterebbe una diminuzione dell'ecienza dovuta alla richiesta di disabilitazione delle interruzioni su pi processori. Per questo motivo le moderne architetture orono delle particolari istruzioni hardware atomiche che permettono la realizzazione di un lock:
FetchAndAdd (): legge ed incrementa il valore di una variabile. TestAndSet(): legge ed imposta il valore di una variabile. Swap (): scambia tra loro il contenuto di due variabili. TestAndSwap (): legge il valore di una variabile, se uguale ad un dato

valore ne scambia il contenuto con quello di un altra variabile.

TestAndSet() denita come segue

boolean TestAndSet ( boolean o b i e t t i v o ) { boolean v a l o r e= o b i e t t i v o ;


4 questo

l'approccio adottato dai kernel senza diritto di prelazione.

o b i e t t i v o=t r u e ;

return valore ;

e realizza un lock che, tuttavia, non soddisfa l'attesa limitata boolean l o c k=f a l s e ; // g l o b a l e do { w h i l e ( TestAndSet(& l o c k ) ) ; // s e z i o n e c r i t i c a l o c k=f a l s e ; // s e z i o n e non c r i t i c a } while ( true ) ;
Swap() denita come segue

void Swap ( boolean a , boolean b ) { boolean temp=a ; a=b ; b=temp ; } e realizza un lock che, tuttavia, non soddisfa l'attesa limitata boolean l o c k ; // g l o b a l e boolean c h i a v e ; // l o c a l e l o c k=f a l s e ; do { c h i a v e=t r u e ; w h i l e ( c h i a v e==t r u e ) Swap(& lock ,& c h i a v e ) ; // s e z i o n e c r i t i c a l o c k=f a l s e ; // s e z i o n e non c r i t i c a } while ( true ) ; Per garantire l'attesa limitata occorre modicare l'algoritmo come segue boolean a t t e s a [ n ] ; // g l o b a l e ( i n i z i a l i z z a t o f a l s e ) boolean l o c k=f a l s e ; // g l o b a l e do { a t t e s a [ i ]= t r u e ; c h i a v e=t r u e ; w h i l e ( a t t e s a [ i ] && c h i a v e ) c h i a v e=TestAndSet(& l o c k ) ; a t t e s a [ i ]= f a l s e ; // s e z i o n e c r i t i c a j =( i +1)%n ; 4

w h i l e ( ( j != i ) && ! a t t e s a [ i ] ) j =( j +1)%n ; i f ( j==i ) l o c k=f a l s e ; else a t t e s a [ j ]= f a l s e ; // s e z i o n e non c r i t i c a } while ( true ) ; La mutua esclusione soddisfatta in quanto attesa[i] pu diventare false solo se un altro processo esce dalla propria sezione critica, e solo un elemento alla volta in attesa[] varr false. Il progresso soddisfatto in quanto lock e attesa[] vengono modicati fuori dalla sezione critica. L'attesa limitata soddisfatta in quanto nella sezione di uscita viene scandito con ordinamento ciclico attesa[], e designato il primo processo presente nella propria sezione di ingresso (attesa[j]=true ) come primo processo avente diritto di entrare nella propria sezione critica: qualsiasi processo attende nella propria sezione di ingresso al pi n 1 turni.
Semafori Un semaforo un meccanismo di sincronizzazione che realizza il controllo degli accessi ad una risorsa a n istanze5 . E' realizzato da una variabile intera inizializzata al numero n di istanze disponibili, accessibile solo tramite le due operazioni atomiche wait() e signal(). I processi che desiderino acquisire un'istanza invocano wait() sul semaforo, decrementandone il valore di 1; quelli che ne rilasciano un'istanza invocano signal(), incrementandone il valore di 1. Esistono due tipologie di semaforo:

semaforo utile in caso di locking applicati per brevi intervalli di tempo7 . Sore di busy waiting8 , in quanto, ntantoch non sia disponibile alcuna istanza, qualsiasi processo che tenti di acquisirne permane attivo nel ciclo while della wait(). Il vantaggio consiste nel non richiedere alcun context switch per i processi in attesa di acquisire l'istanza9 .
spinlock

6:

i n t S=n ; // g l o b a l e wait ( i n t S ) {
5 in

istanza mutipla.

semaforo contatore: n > 1 applicato nel controllo degli accessi ad una risorsa ad

base ad n distinguiamo due tipologie di semaforo:

semaforo binario: n = 1 applicato nel controllo degli accessi alle sezioni critiche.

6 spinlock deriva dal fatto che i processi girano (spin) nonostante non abbiano acquisito l'istanza richiesta (lock ). 7 gli spinlock trovano grande applicazione nei sistemi multiprocessore, dove un processo gira su un processore mentre un altro thread esegue la propria sezione critica su un altro processore. 8 ovvero attesa attiva, costituisce un serio problema per un sistema con multiprogrammazione in quanto riduce gravemente la produttivit. 9 sono infatti trattenuti attivamente nel ciclo while().

S;

w h i l e ( S<=0);

s i g n a l ( i n t S ) { S++; } i n t mutex=1; // g l o b a l e do { wait(&mutex ) ; // s e z i o n e c r i t i c a s i g n a l (&mutex ) ; // s e z i o n e non c r i t i c a } while ( true ) ;

: semaforo che mantiene una propria coda di processi aventi richiesto un'istanza non disponibile. Questo semaforo risolve la busy waiting attraverso l'operazione di bloccaggio : un processo che richieda un'istanza non disponibile, anzich rimanere in busy waiting, blocca s stesso mediante una block()10 e viene aggiunto alla lista di processi11 coda propria del semaforo, quindi viene trasferito il controllo allo scheduler, che seleziona un altro processo dalla ready queue. Un processo bloccato sar riavviato mediante una wakeup()12 in seguito ad una qualsiasi signal() sul semaforo. Questo tipo di semaforo pu condurre ad un deadlock e/o alla starvation13 qualora la coda del semaforo sia regolata dalla disciplina di accesso LIFO.
semaforo con coda d'attesa

typedef struct { int valore ; s t r u c t p r o c e s s o coda ; } semaforo ; wait ( semaforo S ) { S>v a l o r e ; i f ( S>v a l o r e <0){ a g g i u n g i questo p r o c e s s o a S>l i s t a ;
di puntatori a PCB. In generale pu essere impiegato un qualsiasi criterio di accodamento, ma evidente che la disciplina di accesso FIFO soddis l'attesa limitata, mentre la LIFO possa provocare una situazione di attesa indenita. 12 wakeup(P) una chiamata di sistema che modica lo stato del processo P da attesa a pronto. 13 la starvation il fenomeno di attesa indenita.
10 block() una chiamata di sistema che sospende il processo che la invoca. 11 la coda di processi propria del semaforo si realizza mantenendo una lista

block ( ) ; // sospende i l p r o c e s s o

s i g n a l ( semaforo S ) { S>v a l o r e ++; i f ( S>v a l o r e <=0){ t o g l i un p r o c e s s o P da S>l i s t a ; wakeup (P ) ; // pone n e l l a ready queue i l p r o c e s s o P } }
Sincronizzazione in Linux Il kernel di Linux preemptive e si serve di spinlock 14 e semafori per implementare i lock a livello kernel. In sistemi monoprocessore gli spinlock sono inadatti e si ricorre all'abilitazione/inibizione della preemption tramite chiamate di sistema. Non dovendo essere possibile inibire la preemption quando un task in KM detiene un lock, si mantiene un contatore dei lock attivi nel kernel e si autorizza l'eventuale inibizione della preemption solo quando il contatore ha valore nullo. Transazioni atomiche: protocolli di ripristino e protocolli di serializzabilit Una transazione un insieme di operazioni che esegue atomicamente una singola funzione logica come singola unit logica di lavoro 15 . Si utilizzano le transazioni per assicurare l'atomicit, la ripristinabilit e la serializzabilit a fronte delle eventuali anomalie del sistema. Consideriamo una transazione come una sequenza di read() write() terminata da una commit() o una abort(), rispettivamente in caso di successo o fallimento della transazione. Il protocollo di write-ahead logging16 un protocollo di ripristino che assicura l'atomicit e, conseguentemente, la ripristinabilit delle transazioni. Consiste nel registrare in una memoria stabile un log, ovvero una struttura dati che descriva tutte le modiche che le transazioni intendano apportare ai dati a cui accedono. Ciascun elemento del log descrive una singola operazione write() eseguita dalla transazione. Per garantire la ripristinabilit la registrazione nel log viene fatta prima dell'eettiva esecuzione della write(). Tale protocollo pu essere ottimizzato facendo uso dei checkpoint17 , i quali rendono pi eciente l'eventuale procedura di ripristino. Una sequenza di esecuzione seriale una sequenza di esecuzione di transazioni caratterizzata dal fatto che tutte le operazioni appartenenti ad una singola transazione sono raggruppate. La serializzabilit la caratteristica
14 il kernel di LInux progettato per mantenere attivi gli spinlock solo per brevi periodi di tempo. 15 una singola unit logica di lavoro caratterizzata dal fatto di essere eseguita nella sua totalit o non essere eseguita per niente. 16 ovvero protocollo di registrazione con scrittura anticipata. 17 ovvero punto di verica.

per cui il risultato di una sequenza di esecuzione concorrente equivale a quello che si otterebbe in una sequenza di esecuzione seriale arbitraria. Due operazioni in successione sono dette operazioni conflittuali se tentano di accedere agli stessi dati e almeno una delle due una write(). Se una sequenza di esecuzione concorrente si pu trasformare in una sequenza di esecuzione seriale attraverso una serie di scambi tra operazioni non conittuali, si dice che la prima in 18 conflitto serializzabile . L'ordine di serializzabilit l'ordine della sequenza di esecuzione seriale con cui una sequenza di esecuzione concorrente deve essere in conitto serializzabile. Gli algoritmi di controllo della concorrenza assicurano la serializzabilit, senza i vincoli imposti dalla realizzazione di sezioni critiche: spesso infatti si pu consentire la sovrapposizione delle transazioni pur garantendo la serializzabilit. Essi sono:

oltre al normale protocollo di locking per lock condivisi ed esclusivi, previsto che ogni transazione richieda l'acquisizione/rilascio dei lock in due fasi distinte:
protocollo di locking bifase

19 :

 fase di crescita: la transazione pu acquisire nuovi lock, ma non rilasciarne alcuno in suo possesso.  fase di riduzione : la transazione pu rilasciare i lock in suo possesso, ma non acquisirne di nuovi.

Questo protocollo non elimina la possibilit dei deadlock ; per migliorarne le prestazioni indispensabile disporre di ulteriori informazioni sulle transazioni, oppure imporre un qualche ordinamento all'insieme dei dati.

protocollo a marcatura temporale

ad ogni transazione Ti associata una marca temporale unica T S (Ti ), denita prima della sua esecuzione e assegnata dal clock o da un contatore logico. Le marche temporali determinano dunque l'ordine di serializzabilit. Ad ogni elemento Q su cui possa operare una transazione, si associano due marche temporali:
 R-timestamp(Q): indica l'ultima operazione read(Q) completata con successo.  W-timestamp(Q): indica l'ultima operazione write(Q) completata con successo.
Ti esegue read(Q):

20 :

 se T S (Ti ) < W timestamp(Q): dovrebbe leggere un valore gi sovrascritto, quindi si riuta Ti e viene eseguito il rollback.
18 o anche che 19 anche detto 20 anche detto

tamp ordering.

la prima una sequenza di esecuzione concorrente serializzabile. protocollo per la gestione dei lock a due fasi. protocollo con ordinamento a marche temporali, o anche protocollo a times-

 se T S (Ti ) W timestamp(Q): si esegue l'operazione e Rtimestamp(Q) = T S (Ti ).


Ti esegue write(Q):

 se T S (Ti ) < R timestamp(Q): dovrebbe scrivere un valore necessario in precedenza, quindi si riuta Ti e viene eseguito il rollback.  se T S (Ti ) < W timestamp(Q): dovrebbe scrivere un valore obsoleto, quindi si riuta Ti e viene eseguito il rollback.  altrimenti: si esegue l'operazione e W timestamp(Q) = T S (Ti ).

Ogni transazione su cui venga eseguito il rollback viene riavviata e gli viene associata una nuova marca temporale. Questo protocollo elimina la possibilit dei deadlock.

Potrebbero piacerti anche