:: Sistemi Operativi - Lezione del 22 aprile 2008 ::
Torna alla pagina di Sistemi Operativi
Struttura delle memorie di massa
Schedulazione degli accessi al disco
L'accesso al disco ha dei tempi di latenza. Il seek time è il tempo che si perde per posizionare la testina sulla traccia giusta. Poi c'è la rotational latency che è la latenza introdotta dal fatto che il braccio della testina ruota.
L'obiettivo è minimizzare il tempo di accesso medio, così che il disco sembri migliore. Non posso fare molto invece riguardo alla larghezza di banda, perché quella dipende dall'interconnessione. Però, gestendo bene la coda degli accessi, posso guadagnare qualcosa.
Schedulazione FCFS
First Come, First Served. Nient'altro da aggiungere.
SSTF = Shortest Seek Time First
Mi trovo in una data posizione, che chiamo X. Gli accessi in coda vengono ordinati in base alla distanza relativa ad X.
Una nuova richiesta di accesso può anche essere eseguita subito, se si trova vicino alla testina X.
Anche se alcuni processi verranno ritardati, non è un problema, perché il tempo di accesso medio si abbassa notevolmente, ed è questo quello che a noi interessa.
Schedulazione SCAN
Per evitare troppe sollecitazioni meccaniche al braccio ed alla testina, si ordinano gli accessi in modo che prima eseguo tutte le richieste che possono essere effettuate andando nella stessa direzione, poi cambio direzione e faccio le altre.
Schedulazione Circular SCAN (C-SCAN)
Come sopra, ma ad ogni cambio di direzione riparto dall'inizio del disco.
Schedulazione LOOK
Ordina gli elementi, e quando arriva all'ultimo elemento di una direzione, inverte e va nell'altra direzione. Invece di partire da capo come con la SCAN, parte da dove si trova, e guadagna un po' di tempo.
Qualsiasi schedulazione io scelga, non guadagno tanto se ho i files sparpagliati in blocchi qua e là per il disco: per rendere meglio, dovrebbero trovarsi il più contigui possibile.
Ad ogni modo, la SSTF e la LOOK sono quelle che vengono scelte maggiormente.
Un'altra considerazione da fare è che queste schedulazioni non fanno distinzione tra quale processo richiede l'accesso. Se invece ho delle priorità o delle scadenze per ogni processo, il SO potrebbe privilegiare i processi con alta priorità o scadenza vicina.
Bisogna però stare attenti: usare solo la priorità del processo per schedulare gli accessi al disco non è molto felice, perché se mando la testina troppo in giro qua e là per il disco, alla fine ci smeno.
Organizzazione del disco
Il disco viene organizzato e configurato secondo tre livelli diversi, che ora vedremo: formattazione fisica, partizionamento e formattazione logica.
Formattazione fisica
Altrimenti nota come a basso livello. Divide fisicamente il disco in settori, che poi il controller può raggiungere.
I settori hanno
così che si possono distinguere l'uno dall'altro. Nel terminatore in genere c'è una checksum che serve per controllarne l'integrità.
Partizionamento
Per vari motivi, si divide l'intero spazio offerto dal disco in più partizioni, ad esempio una contenente i files di sistema e un'altra per i files degli utenti. Però può anche avere una sola partizione.
Formattazione logica
Visto che non è pratico dire "leggimi i dati contenuti nel settore 4 traccia 3", occorre dotare la nostra partizione di un filesystem che organizzi logicamente, e non più fisicamente, i dati presenti sul disco.
Posso anche decidere di non formattare la partizione, e lasciarla in uno stato grezzo: raw disk. Questo serve per usare la partizione come area di swap, perché ci deve pensare il gestore della memoria ad organizzarsela come vuole lui.
Il blocco di avvio
Contiene il SO o una sua parte. È il settore 0. Quando partiziono, devo stabilire se far sì che un disco sia avviabile o no, e questo permette di far partire da quel disco il SO a partire proprio da quel settore.
I settori successivi sono usati in genere per filesystem etc, e poi cominciano i dati.
Blocchi difettosi
Può capitare che un blocco si danneggi. Che fare? Non può più essere utilizzato, ovviamente.
Ma se in una traccia devo avere eg 10 settori, e uno si danneggia, ne rimangono 9, e quella traccia rimane menomata rispetto alle altre => potrebbe essere un problema per la struttura logica.
Posso quindi prevedere questa situazione, e tenere via delle tracce spare, cioè di ricambio, e quando un settore si danneggia lo "sostituisco" con uno di quelli di ricambio. Ci penserà il controller a deviare l'accesso al blocco difettoso verso il blocco di ricambio. Questo sistema si chiama sector forwarding.
Se no, applico il sector slipping e salto tranquillamente al blocco dopo.
Finora abbiamo assunto che i settori si identificano tramite il loro numero all'interno di una traccia. In alcuni sistemi però si preferisce dare a tutti i settori del disco una numerazione assoluta, e poi ci penserà il controller a stabilire che il settor 1456 è nella traccia 7 alla posizione 15.
Uso dell'area di swap
Come già sappiamo, essa serve per supportare la multiprogrammazione ed il multitasking. E come dicevamo sopra, è il gestore della memoria a gestirla interamente con chiamate di basso livello.
A seconda delle tecniche di memoria virtuale che uso, la swap conterrà interi processi, pagine o segmenti.
Le partizioni RAW sono le più indicate, perché più veloci, per fare da swap. Alcuni sistemi operativi offrono anche la possibilità di usare un file nel filesystem come swap, ma dovendo appunto passare per il filesystem, è molto più lento.
La swap, comunque venga realizzata, deve contenere una mappa che permette di associare i settori alle pagine (o segmenti o quello che volete) che sono ivi contenuti.
Memoria terziaria
Tua sorella, a casa mia
La memoria secondaria può non bastare: è piuttosto costosa, ed è limitata nelle dimensioni.
Per poter archiviare grandi volumi a basso costo, sono stati inventati vari dispositivi, che vanno tutti insieme a formare la memoria terziaria. Costano poco, vengono parimenti poco usati, e sono lenti. Sono però più affidabili delle memorie secondarie
Il SO deve al solito fare in modo che un supporto removibile facente parte della memoria terziaria si integri bene col resto del sistema, gestendone filesystem, condivisione ed accesso (diretto o sequenziale a seconda del dispositivo).
Il vero problema è come identificare in modo univoco i files tra tutti i supporti della memoria terziaria. Su due chiavette USB diverse, per esempio, posso avere lo stesso file sorella.jpg, ma mentre uno contiene una foto di tua sorella, l'altro contiene invece una foto ben più angosciante di Carmen La Sorella.
Per distinguerli, devo anche dire su quale supporto il file è stato salvato.
Un altro problema è quello della portabilità. L'ideale sarebbe poter scrivere con un SO su di un supporto removibile, e poterlo leggere su di un altro supporto. Questo è possibile quando ci sono degli standard, come ad esempio i CD ed i DVD. Da notare che cmq questi standard non risolvono il problema dell'identificatore univoco dei files.
Infine, occorre che la memoria terziaria sia integrata nel sistema, o più precisamente, integrata dal gestore della memorizzazione gerarchica (Hierarchical storage managemente). La gerarchia del filesystem viene estesa "appendendoci" il supporto terziario.
Dischi RAID
RAID = Redundant Array of Inexpensive Disks.
Tutti vorrebbero dischi più veloci e più sicuri. Il problema è che un singolo disco, per avere caratteristiche di alto livello, può venire a costare parecchio.
Allora, si è pensato di usare dei dischi "normali" (gli Inexpensive Disks dell'acronimo) e di gestirli in un modo particolare per avere dei vantaggi.
I vantaggi che voglio avere sono in termini di:
- velocità
- capacità
- affidabilità
e i vari livelli RAID permettono di ottenere queste cose.
Parallelismo
Vuol dire mantenere i dati su più di un disco.
Ci sono 2 tecniche:
- bit-level striping
- block-level striping
Il bit-level striping divide una parola (byte) in n parti, e su ogni n-esimo disco scrive la n-esima parte. Per esempio, con 8 dischi la scrittura di 1 byte viene effettuata con il 1° bit nel primo disco, il 2° nel secondo etc.
Il block-level striping invece mette un blocco per disco.
I vantaggi del parallelismo sono soprattutto in termini di velocità, perché i dischi scrivono e leggono in parallelo, e i dati vengono recuperati dal controller senza bisogno di fare accessi sequenziali allo stesso disco.
Livelli RAID
RAID è stato standardizzato, e qui sotto vedremo i livelli di questo standard.
RAID 0
Distribuzione non ridondante, con l'obiettivo di migliorare le prestazioni in velocità.
Faccio striping, a livello di bit o di blocco, su diversi dischi.
RAID 1
Duplicazione dei dischi = mirroring.
L'obiettivo è alta affidabilità. Quando scrivo, scrivo su n dischi, così ho n copie dello stesso dato. Se uno si rompe, ho cmq le altre copie. Va bene perché in generale i guasti nei dischi sono totalmente scorrelati, e quindi se si rompe uno è poco probabile che si rompa ANCHE l'altro.
Il problema è che se ho n copie, e accadono n-1 guasti, ho sì i miei dati protetti, ma non lo saranno più in futuro, perché quel settore ormai è spacciato su tutti i mirror. Il problema sarà risolto nei prossimi livelli.
La ridondanza, ovvero la duplicazione dello spazio, qui è al 100%: ogni disco è copia dell'altro.
Posso anche sfruttare il parallelismo in lettura, eg leggendo un po' da un disco e un po' dall'altro. Occorre però tenere a mente che ogni utilizzo di un disco ne accorcia la vita, e quindi potrei sollecitare troppo entrambi i dischi. Posso quindi scrivere su tutti e due ma leggere solo da 1, così l'altro si consuma di meno.
RAID 2
Introduco ECC, cioè Error Correction Codes, un codice numerico che serve per controllare se i dati sono corretti o no.
I codici ECC sono messi tutti in un disco, detto disco C, mentre i dati stanno sugli altri dischi.
La faccenda funziona così. Se ho 3 dischi, di cui uno è il C, nel settore X del disco C metto l'ECC del settore X del disco A e del settore X del disco B. Non interessa se i settori in A e B rappresentano files diversi, non ha importanza ai fini dell'ECC.
Se uno dei due dischi si guasta in quel settore, io posso risalire al suo valore a partire dall'ECC e dal valore dello stesso settore nell'altro disco. Posso quindi fermare tutto, estrarre il disco rotto, mettere un disco nuovo, copiare i dati copiabili e quelli vecchi ricavarli dall'ECC e dall'altro disco.
In questo caso, con 3 dischi ho ridondanza del 33%, perché 1 su tre viene usato per ridondanza.
RAID 3
Parità a bit alternati = bit-interleaved parity.
Nel RAID 2, se modifico un settore di un disco devo anche modificare il suo ECC nel terzo disco. il C. Quindi il disco C viene usato per ogni operazione di scrittura su altri dischi.
Se uso in parallelo i dati degli altri due dischi, allora ottengo dei vantaggi in termini di tempo, ma se il disco C è sempre uno, i vantaggi vengono vanificati dai mille accessi a C.
L'idea è allora usare i singoli bit per calcolare la parità (che può essere vista come un ECC), e fare tutto in parallelo. Così, anche il disco C viene usato in parallelo con gli altri per calcolare la parità.
RAID 4
Come il 3 ma con la block-interleaved parity.
RAID 5
Invece di usare un disco per tutti, metto le info di parità distribuite in tutti i dischi del sistema, così che ho gli accessi distribuiti per disco, e non uso lo stesso disco C mille volte.
Così anche se muore un disco so ricalcolarne il contenuto a partire dagli altri rimanenti.
RAID 6
Uso P+Q redundancy, ovvero doppia parità, così che posso tollerare fino a 2 dischi che muoiono, ricostruibili tramite i rimanenti.
RAID 0+1 e 1+0
Posso anche fare delle combo, di cui noi vedremo 0+1 e 1+0.
Nel RAID 0+1 metto una serie di mirror in striping, e poi faccio il mirror, sempre in striping, di tutto la prima serie.
Nel RAID 1+0 metto i dischi in mirror a coppie, e poi metto i mirror in uno stripe. Rispetto a 0+1 tollera più guasti.
Tutti questi livelli vengono gestiti in hardware da appositi controller.
Il filesystem
I files e le loro caratteristiche
Non si vuole che la struttura fisica del disco rimanga visibile all'utente, perché è improbabile andare a cercare i dati nel settore X della traccia Z o robe simili. Sarebbe bello avere un'organizzazione logica e più "umana" dei dati presenti in un disco.
Il filesystem serve proprio a darci una visione logica e omogenea dei dispositivi fisici, così che non dobbiamo preoccuparci dei dettagli delle periferiche.
Le informazioni vengono organizzate in files, mentre i files vengono organizzati in directory. Se i files sono informazioni, le directory sono metainformazioni, perché di per se non aggiungono niente, ma servono di supporto alla gestione del file.
Aggregazione di informazioni
Dai linguaggi di programmazione, sappiamo che un'array è una lista di n elementi omogenei, mentre un record contiene n elementi anche disomogenei.
Ebbene, un file è un'aggregazione di elementi omogenei, in un numero non noto a priori.
Ogni elemento del file è identificato da un indice. Essendo l'indice numerico, il numero di elementi è determinato dal massimo numero rappresentabile da quella macchina, piuttosto che dallo spazio rimanente sul disco. Ma in linea teorica, l'unico limite alla dimensione di un file è lo spazio che ho.
Ho diversi tipi di dati, int, float, char o quello che vogliamo. Ma sotto sotto il computer se ne frega: lui vede e memorizza solamente sequenze di 0 e 1 organizzate in byte, e quindi al livello più basso un file è una sequenza di byte.
La struttura interna del files dipende dall'applicazione che l'ha creato, e viene interpretata dall'applicazione che lo sa fare, ma ciò non dipende dal fatto che è una sequenza di byte.
Attributi del file
Sono molteplici: nome, id, tipo, locazione, dimensione, timestamp dell'ultima lettura o scrittura, proprietario, permessi etc. etc.
Il tipo, come dicevamo sopra, non è una proprietà del file stesso in quanto sequenza di byte, ma serve di ausilio al SO per decidere quale applicazione utilizzare per lavorare su quel particolare file.
Per scoprire di che tipo è un file, si possono usare vari modi, tra cui l'estensione (.exe, .jpg e così via), o un magic number all'inizio del file. Il problema è che ci sono troppi tipi di files, e al SO non gliene frega niente. Se dovesse sapere come gestirli tutti, gli servirebbe tantissimo spazio. Pertanto, la tendenza è che il SO se ne frega, e spetta alla singola applicazione riconoscere il file che le va bene.
Tutti questi attributi rappresentano il descrittore del file, che viene salvato dal SO.
Operazioni sui files
Un file lo posso aprire, chiudere, leggere, modificare, troncare, estendere, bloccare per la condivisione.
Apertura
Devo innanzitutto avere i permessi per aprire il file. Il file va poi identificato tramite il filesystem, e occorre verificare che sia possibile aprirle nelle attuali condizioni di condivisione.
Il SO mantiene una lista dei files aperti.
Lettura & scrittura
C'è un puntatore che punta all'elemento corrente, e il SO copia in memoria a partire da quella posizione.
Come dicevamo sopra, sta all'applicazione che sa leggere il file dire quanta roba leggere, e poi attribuirgli un significato.
L'accesso sequenziale parte dall'inizio e va avanti. Ogni modifica di quest'ordine implica ricominciare da capo.
L'accesso diretto invece mi permette di specificare una qualsiasi posizione all'interno del file.
La tabella che il SO mantiene per i files aperti dice anche dove si trova il puntatore all'elemento corrente per ogni file.
Chiusura
Il processo rilascia tutte le info di gestione del file, ed eventualmente il lock che s'era preso prima.
Files con indice
Alcuni files molto grossi vengono di fatto divisi un due parti: una contiene un indice, e l'altra i dati effettivi.
Per cercare una posizione in un file, si consulta prima l'indice, che dice più o meno dove partire nel file grosso a cercare. Questo evita lunghi accessi sequenziali per trovare posizioni che non sono note a priori.
Torna alla pagina di Sistemi Operativi