:: title Riassunto capitolo 8 - Lo stallo (deadlock) ::
Torna alla pagina di Sistemi Operativi
La maggior parte dei SO NON fornisce strumenti di prevenzione dello stallo -> probabilmente in futuro.
Risorse = di molti tipi, distribuite fra i vari processi.
Se un processo vuole un tipo di risorsa, può ottenere con soddisfazione una qualunque istanza di quella risorsa. Deve chiederla prima di usarla, e rilasciarla dopo:
Richiesta & rilascio = chiamate di sistema.
Un gruppo di processi è in stallo se ogni processo del gruppo è in attesa di un evento che può essere generato solo da un processo del gruppo.
Multithread => proni al deadlock perché i thread accedono a risorse condivise.
Condizioni affinché si abbia un deadlock:
Devono verificarsi tutte e 4 le condizioni.
Nodi = 2 tipi
Archi:
Se il grafo NON contiene cicli => di sicuro non ci sarà nessun deadlock.
Se il grafo ha un ciclo => possibile deadlock (condizione necessaria ma non sufficiente)
Ci sono 3 modi per gestire il deadlock:
Ignorare il problema è il sistema maggiormente utilizzato: spetta al programmatore far sì che i dl non accadano.
Prevenire i dl = impedire in partenza che l'assegnazione delle risorse possa ingenerare una delle 4 condizioni viste prima.
Evitare i dl = se conosco tutte le richieste che il processo farà, so dire in partenza se si può finire in dl.
4 tecniche diverse, ciascuna atta a scongiurare l'avverarsi di una delle 4 condizioni viste sopra.
Le risorse condivisibili non creano dl. Quelle non condivisibili invece potrebbero.
Dovrei impedire la mutua esclusione di risorse non condivisibili => ma è impraticabile perché ho risorse intrinsecamente non condivisibili (eg la stampante).
Per evitare la condizione di possesso ed attesa, devo fare in modo che un processo che abbia già una risorsa non possa attendere per un'altra.
Posso avere 2 protocolli:
Il problema della tecnica 1 è che un processo può tenere bloccata per lungo tempo una risorsa che userà invece per poco.
=> in generale
Protocollo 1: Se il processo non può ottenere le risorse che chiede deve mollare tutte quelle che ha, e rimettersi in lista di attesa per tutte.
Protocollo 2: Un processo che chiede risorse le può ottenere:
Tutte le risorse vengono numerate in modo crescente (non importa quale).
Si impone poi che un processo può richiedere solamente risorse con un ordine più alto di quelle che già ha => deve chiederle in ordine => si previene l'attesa circolare.
Ma l'imporre un ordinamento alle risorse di per sé non cambia nulla => occorre che il programmatore rispetti l'ordinamento.
Gli algoritmi di prevenzione del deadlock impediscono l'insorgere delle condizioni del deadlock => tuttavia le periferiche vengono sottoutilizzate.
=> si richiedono informazioni supplementari al processo, relativamente alle risorse che vorrà chiedere => sapendo in anticipo che cosa vorrà, si sa subito se far attendere o meno il processo.
Sequenza sicura = una sequenza di processi Pi ... Pn tale che le richieste del Pi-esimo programma possono essere soddisfatte
Esiste una sequenza sicura => siamo in uno stato sicuro.
Ocio: stato non sicuro non implica un dl, però non può garantirne la non insorgenza.
Un sistema può passare da uno stato sicuro ad uno non sicuro => occorre controllare le richieste e verificare che ciò possa non avvenire => le richieste vengono soddisfatte solo se mantengono il sistema in uno stato sicuro.
Si usa nel caso ci sia un'istanza per ogni tipo di risorsa.
Rispetto al grafo "normale" viene introdotto l'arco di prenotazione = da un processo ad una risorsa = vuol dire che il processo vorrà in futuro quella risorsa.
È necessario conoscere le richieste future quando un processo viene avviato.
Il sistema è in uno stato sicuro se non ci sono cicli, considerando anche gli archi di prenotazione.
Si usa quando ci sono più istanze per la stessa risorsa. Il processo deve dichiarare all'ingresso nel sistema quante istanze di ogni risorsa richiederà in futuro.
Occorrono parecchie strutture dati per implementare questo algoritmo.
n = numero di processi
m = numero di tipi di risorse
Attenzione: Need[i][j] = Max[i][j] - Allocation[i][j], il che vuol dire che le risorse di cui un processo ha ancora bisogno devno essere pari a quelle che ha chiesto all'inizio in totale (max) meno quelle già allocate (Allocation).
Ci sono poi 2 algoritmi:
Quello che succede è che per ogni processo controllo se ha abbastanza risorse per soddisfare tutte le sue richieste passate e future.
Le risorse le cerco in 2 punti:
Se ogni processo può avere tutte le risorse che chiede (Finish[i] = true, per ogni i), allora sono in uno stato sicuro.
Complessità: O(m * n2)
Request[m] è il vettore delle richieste dell'i-esimo processo.
Il SO lascia accadere i deadlock, ma cerca di rilevarli e poi di porvi rimedio.
Occorre
Anche in questo caso, i comportamenti sono diversi a seconda che si abbiano risorse ad istanze singole o multiple.
Grafo wait-for = variazione del grafo di allocazione delle risorse
Mantengo il grafo ad ogni richiesta di risorse.
Controllo periodicamente che non ci siano cicli in questo grafo. Se ci sono cicli, è possibile un deadlock.
Complessità: O(n2)
Uso strutture dati simili a quelle dell'algoritmo del banchiere.
Come prima, m è il numero di tipi di risorse, n è il numero dei processi.
Questo algoritmo all'inizio indica tutti i processi come adatti alla terminazione (Finish[i] = true) se non hanno nessuna risorsa allocata: niente risorse, niente deadlock. Altrimenti, Finish[i] = false.
Poi, analizzo tutte le richieste per ogni processo. Se sono soddisfacibili, allora stabilisco che le risorse di quel processo saranno utilizzabili da altri processo (Work = Work + Allocationi), e che quel processo potrà terminare.
Se c'è qualche processo che, alla fine, è indicato come impossibilitato a terminare, vuol dire che c'è un deadlock nel sistema, e che quel processo è in stallo.
La scelta del punto 3 si spiega in questo modo: se un processo ha le risorse che vuole, suppongo ottimisticamente che potrà finire tranquillamente. Se ciò non accadrà, una successiva passata dell'algoritmo lo rileverà comunque.
Complessità = O(m x n2)
La frequenza con cui invocare questo algoritmo dipende da 2 fattori:
Se i deadlock sono frequenti, allora l'algoritmo andrebbe invocato frequentemente.
Caso limite = invocarlo ogni volta che c'è una richiesta di allocazione non immediatamente soddisfacibile (quindi, una dipendenza da altri processi).
Questo algoritmo genera overhead => costoso eseguirlo sempre => posso eseguirlo eg solo quando l'uso della CPU scende sotto il 40%, perché i deadlock tendono a paralizzare il sistema.
Quando è stato rilevato un deadlock, posso
Posso
Per decidere quale processo uccidere, posso scegliere tra diversi criteri, eg priorità, interattività etc. => si sceglie quello la cui morte, in base al criterio scelto, sia di costo minimo.
Tolgo le risorse a qualcuno e le do a qualcun altro.