Articolo pubblicato sul n. 106 di MCmicrocomputer (Edizioni Technimedia Srl - Roma) nell'aprile 1991

MCmicrocomputer


Multitasking:
Dalle Stelle allo Stallo

di Andrea de Prisco

In  questa  puntata  di Multitasking, parleremo del problema dello stallo di due  o  piu'  processi.  Situazione in cui si ha praticamente un blocco del sistema  non  a  causa di errore di programmazione dei singoli processi, ma solo  per la concorrenza di questi e per il fatto che il sistema sul quale ci  muoviamo  concede  con  troppa  "leggerezza" l'uso esclusivo di risorse condivise.Copertina del numero di MCmicrocomputer contenente l'articolo

Problema di feeling

Lo scorso mese abbiamo visto come proteggerci da incauti utilizzi delle risorse condivise regolando l'accesso a queste tramite funzioni di sistema come la "Lock-UnLock" o i meno rudimentali semafori.
Non ci siamo pero', volutamente, posti il problema di come proteggerci dallo stallo ossia da quelle situazioni in cui piu' processi non possono proseguire perche' in attesa di risorse in quel momento utilizzate da altri processi che attendono a loro volta altre risorse utilizzate da noi.
E' chiaro che se il processo "A" assume il controllo esclusivo della risorsa "a" e tenta di accaparrarsi anche la risorsa "b" attualmente in uso presso il processo "B" che a sua volta cerca di guadagnare la risorsa "a", i due processi rimarranno bloccati (in stallo) per sempre. A meno di non prendere i necessari provvedimenti.
Facciamo subito un primo esempio: Immaginiamo di dover accedere in maniera esclusiva ad una  struttura dati condivisa sulla quale effettuare operazioni. Fatto questo procediamo all'acquisizione di una seconda struttura dati per aggiornarla con i nuovi valori della prima per poi terminare ancora le operazioni su quest'ultima prima di rilasciarla completamente.
Potrebbe essere l'esempio della lista posti e lista attesa di un qualsiasi volo aereo: chiusa l'accettazione dei prenotati e' necessario accedere alla lista dei posti per conteggiare quelli ancora liberi, accedere alla lista d'attesa per prelevare un pari numero di viaggiatori "speranzosi", e accedere nuovamente alla lista posti per aggiornare l'elenco dei viaggiatori.
Utilizzando le "P" e "V" viste lo scorso mese, il processo potrebbe avere una struttura di questo tipo:

P(Sem1)
Utilizzo struttura 1
P(Sem2)
Aggiornamento struttura 2
V(Sem2)
Utilizzo struttura 1
V(Sem1)

Un'altra procedura, differente dalla prima potrebbe avere la necessita' di accedere alle due strutture dati condivise in ordine inverso:

P(Sem2)
Utilizzo struttura 2
P(Sem1)
Aggiornamento struttura 1
V(Sem1)
Utilizzo struttura 2
V(Sem2)

Cosa succede se le due procedure appena mostrate sono eseguite contemporaneamente ad esempio da due terminali diversi?
Molto probabilmente si ha uno stallo in quanto il primo processo acquisisce l'uso esclusivo della struttura 1 e non la rilascia fino a quando non ha eseguito tutte le sue operazioni. Contemporaneamente potrebbe fare lo stesso la seconda procedura, acquisendo l'uso esclusivo della struttura 2 prima che lo faccia anche il primo processo. In pratica potremmo facilmente trovarci nella situazione in cui il processo 1 ha acquisito la struttura 1, il processo 2 ha acquisito la struttura 2, il processo 1 attende la liberazione della struttura 2 (in mano al processo 2 che, di certo, non la rilascia) e il processo 2 attende la struttura 1. Attesa infinita...

Una prima soluzione

Un primo metodo drastico per risolvere il problema e' quello di vedere le varie risorse cui siamo interessati nel corso della nostra elaborazione come un'unica risorsa indivisibile. In altre parole avremo un solo semaforo che controlla l'accesso simultaneo a tutte le risorse (in gioco) contemporaneamente. In pratica ogni processo che accede ad una qualsiasi risorsa condivisa blocca l'accesso a tutte le risorse disponibile e dunque, banalmente, il problema e' risolto utilizzando erroneamente la forza.
Erroneamente proprio perche' si avrebbe un costoso degrado delle prestazioni globali a causa del fatto che non e' giusto bloccare tutto il bloccabile solo perche' un singolo processo deve accedere ad una singola risorsa. E in piu' sarebbe come fare un vergognoso passo indietro rispetto alla gestione risorse "a semafori" che ha proprio come vantaggio principale il fatto di suddividere le risorse condivise in classi distinte e proprio per questo singolarmente arbitrabili.
Cio' che bisogna fare e' prevenire i possibili stalli: agire prima che sia troppo tardi con metodi non cruenti (escludendo, cioe', la possibilita' di uccidere i processi coinvolti in uno stallo).

Grafi e prenotazioni

Un metodo per prevenire gli stalli c'e' e consiste nell'istituire, oltre alla mutua esclusione implementata attraverso semafori, un meccanismo di prenotazione delle risorse.
In pratica ogni processo quando entra in una sezione critica che a sua volta contiene altre sezioni critiche chiede l'uso sclusivo della prima e in piu' si prenota per le quelle che, eventualmente, utilizzera' al suo interno.
Il sistema concedera' in questo modo l'uso esclusivo della prima sezione critica solo se le prenotazioni indicate non generano la possibilita' di stallo con gli altri processi (che a loro volta hanno fornito le loro prenotazioni).
Nell'esempio sopra visto, la prima P sul Sem1 diventera' una P su Sem1 con prenotazione Sem2: in pratica si chiede che venga dato l'uso esclusivo della risorsa controllata da Sem1 e si avvisa il sistema che prima di rilasciare tale risorsa potrebbe essere utilizzata anche la risorsa controllata da Sem2.
Il codice del primo processo diventa grossomodo cosi':

P(Sem1, Sem2)
Utilizzo struttura 1
P(Sem2)
Aggiornamento struttura 2
V(Sem2)
Utilizzo struttura 1
V(Sem1)

quello del secondo:

P(Sem2, Sem1)
Utilizzo struttura 2
P(Sem1)
Aggiornamento struttura 1
V(Sem1)
Utilizzo struttura 1
V(Sem2)

A questo punto il sistema rifiutera' la P(Sem1,Sem2) del secondo processo se verra' eseguita dopo la complementare richiesta del processo 1, evitando in questo modo lo stallo. Ma come fa il sistema per stabilire che una certa sequenza di prenotazioni puo' indurre uno stallo?
L'algoritmo esiste, ma per descriverlo nel piu' semplice dei modi ci avvarremo di un grafo.
In pratica il sistema mantiene una tabella delle prenotazioni che si modifica man mano che vengono eseguite le varie P e V sui semafori. Prima di concedere o meno l'uso esclusivo di una risorsa condivisa viene generato all'interno del sistema il grafo delle prenotazioni ed esplorato per stabilire se esiste o meno la possibilita' di stallo.
La tabella delle prenotazioni e' formata da una riga per ogni risorsa condivisa e contiene i seguenti campi:

1) stato della risorsa
2) processo utilizzatore
3) lista prenotazioni

Il primo campo e' generalmente impiegato per indicare lo stato della risorsa in questione. Il secondo indica il processo che, in quel momento, sta utilizzando in modo esclusivo la risorsa. Il terzo campo contiene la lista dei processi che hanno prenotato la risorsa ma e che ancora non ne hanno chiesto l'uso esclusivo. A partire dalla tabella delle prenotazioni e' generato il grafo delle prenotazioni come mostrato nei tre esempi di figura 2.
Nel primo dei tre esempi l'unita' U1 e' attualmente utilizzata dal processo P1 e ha una prenotazione da parte di P3 e P2, l'unita' U2 e' utilizzata da P2, l'unita' U3 e' utilizzata da P3 e ha una sola prenotazione da parte di P2. Nel grafo corrispondente (figura 2b) gli archi uscenti dai nodi "processo" indicano l'utilizzo in esclusiva da parte di questi, gli archi uscenti dai nodi "unita'" indicano le dipendenze dovute alle prenotazioni. Cosi' U3 (sempre in figura 2b) e' utilizzata in maniera esclusiva da P3 ed avendo quest'ultimo una prenotazione anche per U1 (P3 compare infatti anche nella lista prenotazioni di questa unita') e' con questa collegata da un arco unita'-unita'.
L'assenza di stallo e' graficamente rappresentata dall'assenza di cicli di dipendenza tra le unita': non esiste infatti un percorso che partendo dall'unita' Ux attraverso le frecce dirette torni nuovamente sull'unita' di partenza.
Diverso e' il caso della tabella di figura 2c e relativo grafo di figura 2d. Li' il percorso circolare esiste, infatti partendo da un qualiasi nodo unita' seguendo le frecce si torna allo stesso nodo di partenza: stallo immediato.
Del resto dando uno sguardo piu' attento alla relativa tabella possiamo dedurre lo stallo anche senza bisogno di grafo. L'unita' U1 e' utilizzata da P1; l'unita' U2 e' utilizzata da P2; l'unita' U3 e' utilizzata da P3. Inoltre P3 ha una prenotazione per U1, P2 per U3 e P1 per U1. P1 "mollera'" U1 dopo aver utilizzato anche U2 che e' utilizzata da P2 che la laciera' dopo aver utilizzato U3. Questa, a sua volta, e' utilizzata da P3 che la lasciera' dopo aver usato anche U1 che e' utilizzata attualmente da P1. Classico gatto che si morde la coda...

Costruiamo un grafo

Come esempio finale di quest'articolo proviamo a giocare con sei processi che si contendono l'assegnazione esclusiva di altrettante risorse condivise. Possiamo immaginare che al sistema operativo arrivino le seguenti sei richieste da altrettanti processi:

P1:: P(Sem1, Sem2, Sem3, Sem5)

P2:: P(Sem2)

P3:: P(Sem3, Sem5)

P4:: P(Sem4, Sem2)

P5:: P(Sem5)

P6:: P(Sem6, Sem1, Sem4)

Come negli esempi precedenti il semaforo "SemX" controlla l'unita' UX e nelle P con piu' parametri il primo identifica il semaforo sul quale si richiede l'accesso i rimanenti parametri indicano le prenotazioni su altrettante risorse condivise. Ad occhio, sapreste giudicare se la sequenza di P sopra indicate mette i processi (non necessariamente tutti) in stato di stallo ?
No, non c'e' stallo: possono essere soddisfatte tutte le P. Vediamo perche': ci aiuteremo con un grafo, mostrato in figura 3b. La costruzione e' assai semplice. Si tracciano tanti nodi quante sono le unita' condivise e da ognuno di questi tante frecce dirette verso i nodi segnalati nelle liste di prenotazione. Cosi' da quanto evidenziato dalla prima delle sei P da U1 tracceremo tre frecce verso U2, U3, U5. Con la seconda P, non essendoci prenotazioni non aggiungeremo alcuna freccia. Con la terza P tracceremo semplicemente un arco da U3 a U5 e cosi' via fino alla sesta P ottenendo il grafo mostrato in figura 3b. Come facilmente visibile "ad occhio" non esiste un percorso circolare che partendo da uno qualsiasi dei nodi riporti al nodo di partenza: non c'e' infatti pericolo di stallo.
Certo la cosa cambia se poco dopo P5 esegue una V(Sem5) (liberando U5) e prima che altri processi blocchino la risorsa esegue un bel P(Sem5, Sem6). La tabella prenotazioni e' mostrata in figura 3c, il nuovo grafo in figura 3d. Qui lo stallo c'e' in quanto esiste un percorso circolare che partendo da U1, U5 o U6 porta allo stesso nodo di partenza: il sistema non deve permettere questo: P5 verra' sospeso come se avesse trovato la sezione critica occupata da un altro processo mentre sara' concessa la medesima sezione critica ad esempio a P1 o P3 che avendola gia' precedentemente prenotata non aggiungono nuovi archi al grafo e quindi probabilita' si "introdurre" cicli e quindi stalli.
Dal punto di vista del computer
Certo un conto e' prendere carta e penna  disegnare un bel grafo, ben altro e' la visione dal punto di vista del computer che ha schemi di funzionamento ben diversi dalla nostra visione-intuizione.
Un grafo puo' essere memorizzato sottoforma di lista multipla in cui i nodi sono gli elementi della lista e gli archi i puntatori tra questi. E la visita di un grafo non e' certo un problema irrisolvibile, specialmente quando di hanno a disposizione linguaggi di programmazione ricorsivi.
In altre parole il grafo viene costruito e aggiornato in memoria ad ogni chiamata P contenente prenotazioni. L'aggiornamento in questo modo e' semplicissimo: basta riportare nei vai campi i puntatori agli elementi indicati nella lista di prenotazione. Per la visita i problemi non sono poi cosi' tanti. Si parte infatti dal nodo che abbiamo in quel momento aggiornato (relativo alla unita' che si intende utilizzare, indicata nel primo parametro della P) e da quello si scorre il grafo come fosse un albero. La visita ha esito positivo solo se termina senza mai ripassare per il nodo in partenza. Da notare che non e' possibile entrare in cicli in cui non e' compreso l'ultimo nodo aggiornato in quanto cio' indicherebbe la preesistenza di cicli che avremmo dovuto scartare precedentemente.


Impaginato originale...


Clicca per ingrandire... Clicca per ingrandire...
Clicca per ingrandire...  

Articolo pubblicato su www.digiTANTO.it - per ulteriori informazioni clicca qui