FILESYSTEM
LINUX
Il funzionamento di un filesystem Linux
Linux (ed ogni sistema unix-like) organizza i dati che tiene su disco attraverso l’uso di un
filesystem. Una delle caratteristiche di Linux rispetto agli altri Unix è quella di poter supportare,
grazie al VFS, una enorme quantità di filesystem diversi, ognuno dei quali ha una sua particolare
struttura e funzionalità proprie. Per questo, per il momento non entreremo nei dettagli di un
filesystem specifico, ma daremo una descrizione a grandi linee che si adatta alle caratteristiche
comuni di qualunque filesystem di sistema unix-like.
Lo spazio fisico di un disco viene usualmente diviso in partizioni; ogni partizione può contenere un
filesystem. Caratteristica comune di tutti i filesystem per Linux, indipendentemente da come poi
viene strutturata nei dettagli questa informazione, è quella di implementare la memorizzazione dei
file,delle directories e dei link atraverso l’utilizzo della tabella di i-node (index node). Questo
metodo consiste nell’associare ad ogni file ed ad ogni directories una tabella per mezzo di un
numero che elenca gli attributi e gli indirizzi dei blocchi fisici del disco occupati dalla struttura a cui
fa riferimento. Nel caso dei file, i primi indirizzi sono tutti memorizzati nella tabella in maniera tale
che piccoli file sono univocamente localizzati dall’inode. Per file di grandi dimensioni in cui l’inode non basta per memorizzare tutti gli indirizzi del file, il primo campo della tabella viene usato
come puntatore ad un blocco fisico del disco. Questo prende il nome di blocco a singola
idirizzazione e contiene tutti gli altri indirizzi. Se cio non basta possiamo espandere la struttura sino
a tripla indirizzazione.
Le directories in generale sono trattate in maniera esrtemamente semplice e gerarchica. Ogni
elemento contiene solo il nome e il suo numero di i-node e tutta l’informazione circa il tipo, il
tempo, il proprietario e i blocchi del disco è contenuta nella tabella di i-node. Pertanto quando
cerchiamo un file, il filesystem deve prendere il nome del file e localizzare i suoi blocchi nel disco
attraverso il path name che noi gli forniamo. Poiche la struttura delle directories è strettamente
gerarchica, il primo nome del path verrà ricercato nella directories /root (radice) della quale il
sistema operativo conosce la posizione dell’i-node poiché è posto in una posizione fissa della
memoria. All’interno di questa cerca il secondo nome del path e di consequenza il suo i-node
attraverso il quale trova la sua posizione di blocco e così avanti sino al compimento della ricerca.
Come ho detto prima Linux supporta il concetto di link ed anche questi utilizzano la struttura inode. Un nome può essere associato ad un i-node. In pratica piu nomi possono far riferimento ad un
file attraverso una tabella di i-node. Questa infatti al suo interno contiene un campo che tiene conto
dei vari collegamenti al file. Questo campo viene aggiornato ogni volta che un nuovo collegamento
viene creato o eliminato. La tabella di i-node viene deallocata solamente quando il contatore interno
arriva a zero cioè quando non esistono più riferimenti al file. I link non possono essere fatti per le
directories per non creare cicli.
Un altro tipo di link supportato da molti filesystem di Linux è quello simbolico, un semplice file
contenente un nome. Quando il kernel incontra questo tipo di link durante l’interpretazione del
pathname non fa altro che sostituirne il nome con il suo contenuto e ricalcolare il pathneme. Il
vantaggio del link simbolico rispetto a quello che sfrutta l’i-node è quella di permettere
collegamenti tra filesystem differenti.
Il filesystem Ext2
Visto che Linux fù inizialmente sviluppato sotto minix, non sorprenderà il fatto che il primo
filesystem di Linux fosse proprio quello del genitore. Comunque tutto cio era molto restrittivo
poiché supportava partizioni massimo da 64Mb e nomi di non più di 14 caratteri per i file.
La ricerca pertanto porto nel 1992 all’uscita dell’extFS, il primo ad essere progettato proprio per
Linux. Nonostante permettesse partizioni da 2Gb e nomi di 255 caratteri lascio la comunità di Linux
insoddisfatta poiché era più lento della sua controparte in minix e l’amministrazione dei blocchi
liberi favoriva una estensiva frammentazione. Non si aspetto quindi molto per la presentazione del
Second Extended FS il quale è diventato nel tempo il più usato in tutte le distribuzioni di Linux.
Minix FS Ext FS Ext2 FS
Max FS size
64 MB
2 GB
4 TB
Max file size
64 MB
2 GB
2 GB
Max file name
16/30 c
255 c
255 c
Extensible
No
No
Yes
Var. block size
No
No
Yes
Maintained
Yes
No
Yes
Caratteristiche standard di Ext2fs
L’Ext2fs supporta tutte le tipologie standard di Unix: i file di tipo standard, le directories, i file di
tipo device ed i link simbolici. E’ capace di gestire fs creati su partizioni molte grandi. Gli ultimi
aggiornamenti del VFS permettono di estendere la dimensione massima sino a 4Tb. L’Ext2fs
utilizza file identificati da nomi di 255 caratteri prolungabili sino a 1012.
Caratteristiche avanzate di Ext2fs

Ext2fs permette di gestire file e directories secondo le semantiche di due versioni molto
famose di Unix: la semantica BSD e SVr4(Sistem V Release 4). Entrambe possono essere
scelte al montaggio del filesystem. Scegliendo la prima, i file vengono creati con lo stesso
identificatore di identità della sua directory genitrice. SVr4 ha una gestione un po più
complessa: se una directory ha il bit di setgid (contenuto nel superblock) settato a uno, i
nuovi file ereditano il suo stesso identificatore e le sottodirectories lo stesso setgid.
Nell’altro caso i file o le directories vengono creati con lo stesso identificatore del gruppo
primario del processo chiamante.

Ext2fs permette all’amministratore di sistema di scegliere la dimensione dei blocchi logici
durante la creazione del filesystem. Blocchi logici di dimensioni più grandi permettono
prestazioni migliori da parte del sistema rispetto alle operazioni di I/O sui dati avendo come
controparte una frammentazione più estesa. Le dimensioni solitamente permesse sono 1Kb,
2Kb e 4Kb. L’ext2fs comunque implementa una tecnica di preallocazione dati pensata
proprio a ridurre gli svantaggi associati a blocchi di grandi dimensioni.

Ext2fs implementa un terzo tipo di link ai file: il Fast Symbolic Link. La caratteristica
saliente di questa struttura è quella di non utilizzare nessun blocco dati ma di risiedere
direttamente nella tabella di i-node stessa. Questa politica permette di salvare un po’ di
spazio nel disco e velocizzare le operazioni di link poiché non è necessario leggere anche un
blocco dati per il link. L’unica cosa che limita il Fslink è l’esiguo spazio disponibile nella
tabella di i-node. Esistono progetti per estendere questo schema a tutti i piccoli file.

Ext2fs tiene traccia del suo stato interno. Un campo particolare inserito nel superblock è
usato dal kernel per indicare lo stato del filesystem. Quando un filesystem è montato in
lettura/scrittura il suo stato è settato a ‘not clean’. Quando viene riportato in modalita di sola
lettura lo stato viene resettato a ‘clean’. Il checker del filesystem si basa su queste
informazioni per rendersi conto di quando è il momento per controllare la situazione interna
al filesystem. Oltre a questo Ext2fs prende nota di eventuali inconsistenze trovate dal Kernel
segnalando il filesystem come erroneo e forzando il checker ad entrare in funzione. Saltare
questi controlli alla lunga crea degli errori pertanto nel superblock viene memorizzato in un
campo contatore anche il numero di volte che il fs è entrato in modalità lettura/scrittura.
Quando questo contatore raggiunge un valore massimo prestabilito, anch’esso memorizzato
nel superblock , il checker entra in funzione anche se il fs è settato a ‘clean’.

Ext2fs implementa anche alcuni accorgimenti per migliorare la sicurezza del sistema. Una di
queste è l’eliminazione sicura dei dati da disco. In pratica l’utilizzatore ha la possibilità di
richiedere al sistema di eliminare dati in maniera definitiva e non recuperabile. Il filesystem
in questo caso si occupa di sovrascrivere i dati interessati con altri puramente casuali
impedendo a malintenzionati di guadagnarne l’accesso con un disk editor.

Per ultimo, sono stati aggiunte due nuove tipologie di file inspirate dal BSD filesystem. I file
immutabili sono file di sola lettura usualmente utilizzati per preservare importanti
impostazioni di sistema. Gli append-only sono file a cui è possibile accedere in scrittura
solamente per aggiungere dati alla fine del file. Tanto i primi che i secondi non possono
essere ne cancellati ne rinominati.
La struttura
La struttura interna del’Ext2fs è molto influenzata dal BSD Fast File System. Infatti Ext2fs è
organizzato secondo una divisione dello spazio su disco a lui associato in tanti block groups. Questi
sono l’analogo nel FFS dei cylinder groups. Comunque i block groups non rispettano la geometria
dei blocchi sul disco come il FFS poiché i moderni hard disk hanno densita variabili nella
disposizione dei blocchi dipendente dalla loro distanza in raggio dal centro del disco.
Questa tabella rappresenta la struttura generale:
Boot
Sector
Block Block ...
Group 1 Group 2 ...
Block
Group N
Ogni block groups contiene una copia delle informazioni critiche del filesystem (il superblock e il
filesystem descriptor) e una parte del filesystem ( il block bitmap, l’i-node bitmap, un pezzo dell’inode table e i blocchi dei dati).
Super
Block
FS
Block Inode Inode Data
descriptors Bitmap Bitmap Table Blocks
L’utilizzo dei block groups è stato un notevole passo avanti in termini di affidabilità: poiché le
strutture di controllo sono replicate in ogni gruppo, queste possono essere facilmente recuperate in
caso di corruzione del filesystem. Questa strutturazione inoltre aiuta ad avere buone prestazioni
poiché, riducendo la distanza tra i blocchi di dati e le loro tabelle di i.node, riduce anche il tempo di
seek durante le operazioni di I/O sui files.
Innovazioni portate dal Filesystem Ext3
Cosa può indurre un amministratore a passare da Ext2 ad Ext3? Ci sono quattro ragioni principali:
disponibilità, integrità di dati, velocità, e transizione facile.

Disponibilità : Dopo una sospensione dell’attività di sistema impura (guasto alla linea
elettrica, crollo del sistema), ogni filesystem Ext2 non può essere montato fino a che la sua
consistenza non è stata controllata dal programma e2fsck. La quantità di tempo che il
programma e2fsck richiede è determinata principalmente dalla grandezza del filesysytem, e
per i filesystem di oggi relativamente grandi (molte decine di gigabytes), questo richiede
tempi lunghi. I filesystem che sono diverse centinaia di gigabytes possono prendere un'ora o
più per il controllo. Questo limita severamente la disponibilità. Per contrasto, Ext3 non
richiede un controllo del filesystem, mai dopo una sospensione dell'attività di sistema
impura, tranne che per certi casi rari di fallimenti hardware ( e.g. fallimenti del disco rigido
). Questo perché il dato è scritto sul disco in tal modo che il filesystem è sempre consistente.
Il tempo per recuperare un filesystem Ext3 dopo una sospensione dell'attività di sistema
impura non dipende dalla grandezza del filesystem o dal numero di file; piuttosto, esso
dipende dalla grandezza del journal usato per mantenere la consistenza. La grandezza di
default del journal richiede circa un secondo per ripristinarlo (dipende dalla velocità
dell'hardware ).

Integrità dei dati : L’uso del filesystem Ext3 può provvedere garanzie più forti
sull’integrità dei dati in caso di una sospensione dell’attività di sistema impura. E’ possibile
scegliere il tipo e il livello della protezione che i dati ricevono. Si può decidere di mantenere
il filesystem consistente, ma si permette il danneggiamento dei dati nel caso di un crash di
sistema; questo può dare un modesto acceleramento sotto alcune circostanze, ma non tutte.
Alternativamente, è possibile mantenere anche la consistenza dei dati; questo significa che
non vedremo mai dati danneggiati in file recentemente scritti dopo un crollo. La scelta
sicura, mantenere i dati consistenti con lo stato del filesystem, è di default.

Velocità : Ext3 è spesso più veloce (produttività più alta) dell’Ext2 poiché la sua modalità di
journaling ottimizza il movimento della testina del disco rigido. Sono disponibili tre
impostazioni di journaling per ottimizzare la velocità, opzionalmente scegliendo di
controbilanciare alcune integrità dei dati :


La prima maniera, Data=writeback, limita le garanzie di integrità dei dati, permettendo
che i dati vecchi si mostrino nei file dopo un crollo, per un potenziale aumento in
velocità sotto alcune circostanze. (Questa modalità, che è quella di default per la maggior
parte dei filesystem journaling, prevede essenzialmente delle garanzie di integrità dei dati
più limitate del filesystem Ext2 ed evita soltanto il lungo filesystem check al momento
dell’avvio).

La seconda maniera, Data=ordered (la maniera di default), garantisce la consistenza dei
dati e del filesystem; file recentemente scritti non si mostreranno mai con i contenuti
danneggiati dopo un crollo.

L'ultima maniera, Data=journal, nella maggior parte dei casi richiede un journal più
grande per una velocità ragionevole e perciò prende tempi più lunghi per ristabilirsi in
caso di una sospensione dell'attività impura.
Transizione facile : E’ facile cambiare da Ext2 fino ad Ext3 e guadagnare i benifici di un
filesystem journaling robusto, senza riformattare. Infatti, non c'è nessun bisogno di fare un
lavoro lungo, tedioso, e propenso ad errori di backup, riformattazione e ripristino per
sperimentare i vantaggi di Ext3. Ci sono due modi di compiere la transizione:

L'installazione delle più recenti distribuzioni di Linux offrono la transizione al
filesystem quando si aggiorna il sistema.

Il programma tune2fs può aggiungere un journal ad un filesystem Ext2 esistente. Se il
filesystem è già montato mentre si sta eseguendo una transizione, il journal sarà visibile
come file journal nella directory root del filesystem. Se il filesystem non è montato, il
journal sarà nascosto ed non apparirà nel filesystem.
Rappresentazione su disco
La configurazione del filesystem Ext3 sul disco è completamente compatibile con il kernel
dell’Ext2 esistente. I filesystems tradizionali UNIX immagazzinano i dati nel disco per associazione
di ogni file con un unico i-node numerato sul disco, e l’implementazione Ext2 include già un certo
numero di i-nodes numerati. Ext3 utilizza uno di questi inodes numerati per registrare il filesystem
journal. La realizzazione esistente del filesystem Ext2 include una sequenza di bitmap di
compatibilità, nella quale i bit possono essere settati per indicare che il filesystem usa certe
estensioni. Attraverso l’allocazione di un nuovo bit di compatibilità per l’estensione del journaling,
Ext3 assicura che ogni tipo di vecchio kernel sarà capace di supportarlo con successo.
Formato del filesystem Ext3
Il lavoro del filesystem Journal è semplice: registra i nuovi contenuti dei blocchi metadata
all’interno di un file di log mentre noi siamo in procinto di commettere delle operazioni su disco. Il
solo altro requisito necessario per la scrittura del file di log è che noi dobbiamo essere capaci di
risolvere queste operazioni in maniera atomica.
Il file di log tiene conto di tre diversi tipi di blocchi di dati : metadata blocks, descriptor blocks ed
header blocks.

Un blocco metadata del journal memorizza i campi modificati da una transazione all’interno
di un blocco metadata. Questo significa che qualsiasi piccolo cambiamento noi facciamo al
metadata del filesystem noi dobbiamo scrivere un intero blocco journal in modo da tener
nota di tutti i cambiamenti avvenuti.

I descriptor blocks sono blocchi che descrivono altri metadata blocks nel journal. Ogni
qualvolta vogliamo trascrivere metadata nel journal abbiamo bisogno di registrare quali
blocchi del disco sono normalmente occupati da essi, in modo che il meccanismo di
recupero possa ricopiare i metadata all’interno del filesystem principale. I descriptor blocks
sono trascritti prima di ogni sequenza di metadata blocks nel journal, e contengono il
numero dei blocchi metadata scritti più il loro numero di blocco. Descriptor blocks e
metadata blocks sono scritti sequenzialmente nel journal, cominciando di nuovo dall’inizio
del journal ogni volta che si raggiunge la fine. In ogni istante,viene mantenuta la posizione
corrente della testa e della coda corrente del log. Ogni qualvolta si esaurisce lo spazio di log,
le nuove scritture sul log vengono fermate fino a che non stata cancellata la coda del log per
liberare più spazio.

Infine il file journal contiene un numero di header blocks in locazioni fisse. Questi
registrano la testa e la coda corrente del journal, più una sequenza numerica. Al momento
del recupero, i blocchi header sono scanditi per trovare il blocco con il più alto numero di
sequenza. Durante il recupero dobbiamo ripercorrere tutti i blocchi del journal dalla coda
alla testa, come registrato nell’ header blocks.
Perché Ext3?
Segue un elenco di ragioni per cui Red Hat ha scelto Ext3 come suo primo filesystem journaling
ufficialmente supportato. Si noti che queste ragioni non sono necessariamente uniche per l’Ext3
(altri filesystem journaling condividono diversi di questi punti), ma la collezione intera delle ragioni
è unica per Ext3.

Ext3 è compatibile avanti e indietro con Ext2, permettendo agli utenti di mantenere
filesystem esistenti molto semplicemente aggiungendo la capacità journaling. Tutti gli utenti
che desiderano un filesystem journal possono fare facilmente così. Inoltre, un filesystem
Ext3 si può montare come Ext2 senza rimuovere il journal, finché una versione recente di
e2fsprogs è installata.

Ext3 fa uso di un generico journal a strati jbd che può essere usato in altri contesti. Il
journaling dell’Ext3 può essere usato non soltanto all’interno dell filesystem, ma anche con
altre periferiche, ad esempio quando le apparecchiature di NVRAM saranno disponibili
sotto Linux, Ext3 le potrà supportare.

Ext3 ha più modalità di journaling. L’amministratore del sistema può scegliere se fare il
journal di tutti i file dati e metadati (data=journal) oppure solamente dei metadata
(data=ordered; data=writeback). Nel caso in cui si faccia solo il journaling dei metadata, è
possibile scegliere di scrivere i dati del filesystem su disco prima di modificare i metadata (
Data=ordered; questo garantisce che i metadata puntino sempre a dati validi), oppure di non
gestire in alcun modo la scrittura dei dati ( Data=writeback; il filesystem sarà comunque
consistente, ma nel caso di un crash di sistema impuro si possono ritrovare al posto dei
nuovi dati quelli vecchi). Queste modalità permettono il trade off tra velocità e consistenza
dei dati.

Qualunque sistema (includendo molti cloni di Unix e varianti, BeOS, e Windows ) capace di
accedere ai file su un filesystem Ext2 potrà accedere anche ai file su un filesystem Ext3.
Inoltre non necessita di estensivi cambiamenti all’interno del kernel e non richiede nessun
nuovo tipo di system calls.
File system di tipo journaled
Una caratteristica importante di un fs è quella di riuscire a ripristinarsi in caso di incoerenze interne.
Pensiamo ad esempio al caso in cui un file system cerchi di scrivere dei dati attraverso una write
cache (un tipo di buffer comunemente usato dai fs per aumentare le prestazioni complessive). Se si
ha un crash di sistema prima che la cache sia completamente scritta su disco, al successivo reboot il
fs troverà delle incoerenze. A questo punto il sistema cerca di ripristinare la situazione a prima che
iniziasse la scrittura. Il fsch di Ext2fs, per esempio, per riuscire nel suo compito deve controllare
tutte le partizioni del disco prima di riportarlo ad uno stato consistente. Possiamo quindi renderci
conto che una situazione del genere applicata ad un server capace di gestire qualche terabytes
diventa inaccettabile poiché lo esclude dalla rete per svariate ore.
Un altro tipo di struttura che in caso di errore riesce a ripristinarsi è il database. Molti motori di
database utilizzano una struttura chiamata transazione. Queste sono un set di singole operazioni che
soddisfano alcune proprietà dette ACID. ACID sta per Atomicity, Consistency, Isolation e
Durability. Atomicity implica che tutte le operazioni appartenenti a una singola transazione
vengono completate senza possibilità di errori o cancellazioni. Questa insieme a Isolation fa in
modo che la transazione non possa essere eseguita in maniera parziale. I database sfruttano tutto ciò
prendendo nota di ogni singola operazione in un file di log. Al suo interno oltre al nome
dell’operazione vengono memorizzati anche gli argomenti su cui è stata svolta quella operazione.
Pertanto in caso di stallo del sistema, il database può ripercorrere all’indietro le operazioni fatte fino
ad uno stato stabile riscrivendo anche i dati cambiati sino ad allora.
I Journal File System utilizzano approssimativamente la stessa tecnica permettendo così al fs di
ricostituirsi in poco tempo. La più grande differenza esistente tra i due metodi è il fatto che i
database tengono traccia del processo utilizzatore e dei dati utilizzati mentre i fs solamente dei
meta-data. Per meta-data intendiamo tutte le strutture di controllo del fs: i-nodes, free blok
allocation maps, i-nodes maps, etc…
Soddisfare i bisogni di scalabilità
I filesystem UFS e ext2fs furono progettati quando hard disk e altri tipi di memoria non
raggiungevano le capacita' attuali. La crescita della capacita' dei dispositivi ha consentito file,
directory e partizioni sempre piu' grandi, causando molti problemi a livello di filesystem. Si tratta di
conseguenze delle strutture interne scelte come fondamento di questi filesystem: anche se tali
strutture erano adeguate per le dimensioni medie di file e directory dell' epoca, si sono rivelate
inefficienti per quelle attuali. Ci sono due problemi fondamentali inerenti alle vecchie strutture:

Non sono in grado di gestire le nuove capacita' dei dispositivi: come detto sopra, i
vecchi filesytem furono progettati considerando certi valori medi per le dimensioni di file,
directory e partizioni. Le strutture di un filesystem hanno un numero fissato di bit destinati a
conservare informazioni circa la dimensione dei file, il numero di blocchi logici, etc. Come
conseguenza di quel numero prefissato di bit le dimensioni possibili per file, partizioni, e
contenuti di una directory sono limitate. Le vecchie strutture spesso sono prive del numero
di bit necessario a gestire oggetti di certe dimensioni.

Sono inefficienti per gestire le nuove capacita' di memorizzazione: sebbene talvolta le
vecchie strutture riescano a gestire ancora la nuova dimensione degli oggetti, in certi casi
non sono efficienti dal punto di vista delle prestazioni. La ragione principale e' che certe
strutture lavorano bene con dimensioni un tempo "medie", ma lavorando con i nuovi
standard sono inadeguate.
La maggior parte dei nuovi filesystem ha notevolmente aumentato il numero di bit destinati a certi
campi, al fine di superare le vecchie limitazioni. I nuovi limiti per questi nuovi filesystem sono:
XFS
Max.
Dimensione dei blocchi
dimensione del
filesystem
Max.
dimensione
di un file
18 mila
petabyte
9 mila
petabyte
Da 512 byte fino a 64KB
Con blocchi di
512 byte: 4
petabytes
JFS
512 Tb con
blocchi di
512 byte
512, 1024, 2048, 4096 byte
Con blocchi di
4KB: 32
petabytes
4 petabyte
con blocchi
di 4KB
ReiserFS
max 256 Tb
Fino a 64KB Attualmente
fissata a 4KB
4GB
Ext3FS
4Tb
1KB-4KB
2GB
Purtroppo questi dati non prendono in considerazione le limitazioni imposte da una gestione a
blocchi, la quale impone una dimensione massima della partizione pari a 2Tbytes, e quelle interne
al virtual file system, il quale non riesce a gestire file più grandi di 2Gbytes. La cosa comunque
positiva è quella di avere dei file system che offrono garanzie per i futuri sviluppi.
Gestione dello spazio libero
La maggior parte dei filesystem mantengono delle strutture in cui sono registrati i blocchi liberi.
Spesso queste strutture consistono in una semplice lista, in cui sono scritti gli indirizzi dei blocchi
liberi. In tal modo il filesystem sa come soddisfare le richieste di memorizzazione di un programma.
UFS e ext2fs ricorrono alle cosiddette bitmap per registrare i blocchi liberi: queste consistono in un
array di bit, in cui ogni bit corrisponde ad un blocco logico all' interno della partizione. Lo stato di
ogni blocco logico e' rappresentato dal bit corrispondente nelle bitmap; pertanto un valore "1"
potrebbe significare che il blocco logico associato e' in uso, mentre uno "0" significherebbe che e'
libero. Il problema principale con questo tipo di strutture e' che con la crescita del filesystem anche
le bitmap devono crescere allo stesso modo, dal momento che ogni blocco nel filesystem deve avere
nelle bitamp un bit associato. Con l' uso di un algoritmo di ricerca sequenziale per allocare blocchi
liberi si nota un calo delle prestazioni, dal momento che il tempo necessario per trovare un blocco
libero aumenta linearmente (nel caso peggiore si ha una complessità' di ordine O(n), dove n e' la
dimensione in bit delle bitmap). Si noti che l' approccio basato sulle bitmap non e' cosi' cattivo
quando la dimensione del filesystem e' media, ma con la sua crescita la struttura si comporta
peggio.
Per capire le soluzioni implementate dai file sytem di tipo journaled dobbiamo introdurre due nuove
strutture: l’extents e i B+trees.
Extents
Sono detti Extents gli insiemi di blocchi logici continui utilizzati da alcuni filesystem e anche da
alcuni motori di database. Un descrittore di extent contiene qualcosa come inizio, dimensione
dell'extent, offset, in cui inizio e' l'indirizzo del blocco iniziale dell' extent, la dimensione dell'
extent e' espressa in blocchi, e l'offset e' il divario che il primo byte dell' extent ha all'interno del file
cui appartiene.
Gli extent migliorano la gestione dello spazio di memorizzazione (la superficie del disco), dal
momento che tutti i blocchi all' interno di un extent sono contigui. Cio' porta a migliori tempi di
accesso, poiche' si rendono necessari meno movimenti della testina. Considerate inoltre che l' uso
degli extent riduce l'insorgere della frammentazione interna, dal momento che piu' blocchi sono
mantenuti vicini. E, per concludere con i vantaggi degli extent, essi migliorano le possibilita' di
trasferimenti multisettore e riducono il numero di richieste di blocchi non contenuti nella cache del
disco.
Infine, gli extent forniscono anche un modo di organizzare efficientemente grandi quantita' di
spazio libero contiguo. Usare gli extent ci aiuta a ridurre la quantita' di spazio necessario a registrare
i blocchi liberi, e migliora anche le prestazioni di queste operazioni. Ma la loro adozione non e'
sempre un beneficio. Nel caso che il nostro programma richieda extent di dimensione simile a
quella dei blocchi logici perderemmo i vantaggi degli extent, arrivando ad avere molti piccoli extent
che apparirebbero solo come blocchi logici.
B+Trees
La stuttura di B+tree e' stata usata negli indici dei database per molto tempo. Questa struttura ha
fornito ai database un metodo scalabile e veloce di accedere ai loro record. B+tree significa Albero
Bilanciato; il + significa che si tratta di una versione modificata del Btree originale: piu'
precisamente, si mantengono puntatori da ogni nodo foglia al prossimo, in modo da non penalizzare
accessi sequenziali.
I B+trees hanno due diversi tipi di nodo: i nodi interni e quelli foglia. Entrambi consistono di una
serie di coppie (chiave, puntatore), ordinate in base al valore della chiave in ordine ascendente, e di
un puntatore finale privo della chiave corrispondente. Mentre i puntatori interni sono usati per
indirizzare ad altri nodi interni o esterni (nodi foglia), i puntatori dei nodi foglia indirizzano
direttamente alla informazione finale. La chiave di ogni singola coppia e' usata per organizzare
l'informazione all'interno del B+Tree. Nei database ogni record ha un campo chiave, un campo il
cui valore e' utilizzato per distinguere quel record dai record dello stesso genere. I Btree sfruttano
queste chiavi per indicizzare i record del database per raggiungere migliori tempi di ricerca.
Nel diagramma un nodo e' formato da una serie di coppie di riquadri grigio chiaro – grigio scuro:
le chiavi sono i nomi dei file (grigio scuro), i puntatori sono la parte grigia chiara. I record sono i
riquadri rossi: i dati dei file. La riga in fondo, sopra i riquadri rossi, contiene una chiave per ogni
file nella directory: questi sono i nodi foglia. Al di sopra di questi ci sono i nodi interni, nodi che
sono stati scelti dal sistema per rendere piu' veloce la ricerca di altri nodi.
Come abbiamo detto sopra, la coppia di un nodo interno (chiave, puntatore) e' usata per indirizzare
a un altro nodo interno o a un nodo foglia. In entrambi i casi la chiave che e' associata al puntatore
sara' piu' grande di tutte le chiavi del nodo indirizzato. Quindi nodi con uguale chiave dovrebbero
essere indirizzati dalla coppia successiva all' interno del nodo. Questa e' la ragione principale per l'
esistenza di un nodo finale privo della chiave corrispondente. Notate che la chiave di ogni coppia
deve essere indirizzata da un altro puntatore. Il puntatore finale nei nodi foglia e' utilizzato per
indirizzare al prossimo nodo foglia. In questo modo si puo' ancora accedere sequenzialmente ai
contenuti.
I B+Trees devono anche essere bilanciati. Cio' significa che la lunghezza del percorso che conduce
dalla radice dell' albero fino ad ogni nodo foglia dovrebbe essere sempre della stessa lunghezza.
Inoltre i nodi all' interno di un BTree devono contenere un minimo numero di coppie per esistere.
Qualora il numero di coppie di un nodo scenda al di sotto di tale minimo il contenuto del nodo
viene assegnato ad un altro nodo gia' esistente.
Per leggere un file specifico si opera nel modo seguente: Supponiamo di essere alla ricerca di un
file con un certa chiave, "K". Cominceremmo la ricerca del suo indirizzo al nodo radice,
esaminando poi sequenzialmente tutte le chiavi in esso contenute. Continuiamo a leggerle finche'
non si trova una chiave dal valore maggiore di quello di "K". Quindi ci si sposta al nodo indirizzato
dal puntatore associato a questa chiave: potrebbe trattarsi di un nodo interno o esterno (foglia),
ancora non lo sappiamo. Una volta letto tale nodo, nel caso sia un nodo interno si ripete la
procedura ricorsivamente; saremo infine diretti al nodo foglia, che leggeremo sequenzialmente fino
a quando raggiungeremo la chiave desiderata "K", che ci dirige ai dati cercati. Dal momento che si
devono leggere meno blocchi intermedi per giungere a quelli desiderati questa tecnica causa un
carico minore della semplice lettura sequenziale di tutti i blocchi, in cui nel caso peggiore (blocco
cercato alla fine) si devono leggere tutti i blocchi esistenti.
Soluzioni possibili
La soluzione proposta dai filesystem di nuova generazione consiste nell' uso di extent insieme all'
organizzazione in B+Tree. L' approccio degli extent e' utile dal momento che puo' essere usato per
localizzare molti blocchi liberi in un colpo solo. Inoltre gli extent forniscono un modo di ridurre la
dimensione della struttura stessa, dal momento che piu' blocchi logici sono gestiti con meno
informazioni: non serve piu' un bit nella struttura per ogni blocco nella partizione. Inoltre
utilizzando gli extent la struttura di controllo dei blocchi liberi non dipende piu' dalla dimensione
del filesystem (ma dal numero di extent mantenuti). Ma in ogni caso, qualora il filesystem fosse
tanto frammentato che un extent corrispondesse a un singolo blocco logico, la struttura risulterebbe
piu' grande che nell' approccio delle bitamp. Si noti che le prestazioni dovrebbero aumentare in
maniera significativa se la nostra struttura contenesse solo i blocchi liberi, poiche' andrebbero
considerati meno oggetti. Inoltre con l' uso degli extent, anche se questi fossero organizzati in liste e
fossero usati algoritmi di scansione sequenziale, le prestazioni risulterebbero migliori, poiche' la
struttura unirebbe in un solo elemento molti blocchi liberi, riducendo il numero di ricerche da
effettuare per reperire tutti i blocchi liberi necessari.
Il secondo approccio per superare il problema della gestione dei blocchi liberi e' l'uso di strutture
complesse che consentano algoritmi di scansione meno complessi. Sappiamo tutti che ci sono
metodi migliori delle liste con scansione sequenziale per organizzare un insieme di elementi da
localizzare piu' tardi. Si usano i B+tree dal momento che sono in grado di trovare rapidamente gli
oggetti che li compongono. E cosi' i blocchi liberi sono organizzati in B+Tree invece che in liste, in
modo da poter sfruttare migliori algoritmi di ricerca. Quando dei programmi richiedono molti
blocchi liberi il filesystem esaminerà' il B+Tree principale dei blocchi liberi in modo da localizzare
lo spazio libero richiesto. Inoltre esiste un approccio combinato "B+Tree & extent", in cui sono gli
extent e non i singoli blocchi liberi ad essere organizzato nell'albero. Questo approccio rende
possibili varie tecniche di indicizzazione: indicizzare secondo le dimensioni degli extent, e anche
secondo la localizzazione degli extent, sono metodi gia' realizzati che rendono possibile per il
filesystem localizzare rapidamente molti blocchi liberi, secondo la dimensione complessiva
richiesta o secondo la posizione
Elevato numero di elementi in una directory
Tutti i filesystem usano uno speciale oggetto chiamato directory. Le directory, dal punto di vista del
filesystem, sono insiemi di elementi di elenchi. Gli elementi di questi elenchi sono coppie costituite
da numero I-node e nome file, in cui il "numero I-node" e' il numero dell' I-node -una struttura
interna del filesystem - usata per conservare informazioni relative al file associato. Quando un
programma desidera cercare un file all' interno di una directory, noto il nome del file, si deve
esaminare la struttura degli elementi di directory che la compongono. I vecchi filesystem
organizzavano gli elementi di directory in una lista, che comportava algoritmi di accesso
sequenziale. Come conseguenza le prestazioni sono veramente basse lavorando con grandi directory
in cui sono mantenuti migliaia di file e magari altre sottodirectory. Questo problema, come quello
descritto sopra riguardante i blocchi liberi, e' strettamente legato al tipo di struttura usato. I
filesystem di nuova generazione necessitano di migliori strutture e algoritmi di localizzazione
rapida dei file all' interno di una directory. I filesystem presi in considerazione (tranne l’ext3fs)
fanno uso di B+Tree per organizzare gli elementi di directory all' interno di ogni directory,
realizzando cosi' migliori tempi di accesso. In questi filesystem gli elementi di ogni singola
directory sono organizzati in un diverso B+Tree, indicizzato per nome. In tal modo quando e'
richiesto un certo file in una data directory il corrispondente B+Tree e' esaminato per localizzare
rapidamente l' i-node del file. E il ricorso ai B+Tree adottato dipende dal tipo di filesystem: alcuni
mantengono un B+Tree distinto per ogni directory, mentre altri ne usano uno generale contenente
l'albero delle directory dell' intera partizione.
Per minimizzare il ricorso a puntatori indiretti potremmo pensare di utilizzare blocchi logici piu'
grandi. Questo porterebbe a un miglior rapporto dati utili/blocco, con meno puntatori indiretti. Ma
blocchi logici piu' grandi aumentano la frammentazione interna, e quindi si usano altre tecniche.
Una di queste e' il ricorso agli extent per riunire molti blocchi logici insieme. Usare extent di
puntatori al posto di blocchi di puntatori porterebbe gli stessi benefici di usare blocchi logici piu'
grandi, dal momento che si raggiunge un rapporto migliore di "unita' di informazione per unita' di
indirizzi". Alcuni dei filesystem recensiti usano extent per superare i problemi dell' accesso a grandi
file. Inoltre gli extent possono essere indicizzati all' interno di una struttura B+Tree in base al loro
offset all' interno del file cui appartengono, rendendo possibili migliori tempi di accesso. I nuovi inode solitamente contengono puntatori diretti agli extent, e qualora il file richieda piu' extent questi
vengono organizzati in un B+Tree. E, per conservare alte prestazioni nell' accesso ai file piccoli, i
filesystem di nuova generazione registrano i loro dati all' interno dell' i-node stesso:
conseguentemente raggiungere l'i-node significa anche raggiungere i dati del file, se
sufficientemente piccolo. Questa tecnica e' utile in particolare per i collegamenti simbolici, cioe' file
cbhe contengono realmente pochi dati.
In
particolare,
conserva i
dati degli
elementi di
directory
piccole all'
interno dell'
i-node
Usa Btree
Usa Btree
per
per gli
indirizzare
elementi di
i blocchi
directory
del file
Usa Extent
per
indirizzare i
blocchi del
file
Conserva i
dati di file
piccoli all'
interno dell'
i-node
In
particolare,
conserva i
dati dei
collegamenti
simbolici all'
interno dell'
i-node
XFS
B+Tree,
indicizzati
secondo
Sì
offset e
dimensione
Sì
Sì
Sì
Sì
Sì
Sì
JFS
Tree +
Binary
Buddy.
Sì
Sì
Sì
No
Sì
Sì, fino a 8
Usa extent
Gestione
Tecnica /
per lo
dei blocchi
filesystem
spazio
liberi
libero
No
Sì, con un
sottoalbero
Non
B+tree dell'
ancora
albero
supportata principale
del
filesystem
All' interno
dell' albero
principale
del
filesystem
Previsto col
rilascio
Si basa sui
della
b*tree
versione 4
ReiserFS
basata su
Bitmap
Si basa sui
b*tree
Si basa sui
b*tree
Ext3fs
Ext3fs non è filesystem di nuva idazione: si basa su ext2fs, quindi non supporta nessuna delle tecniche
sopra riassunte. Il punto è che ext3fs implementa il journalig in ext2fs mantenendone tutte le
compatibilità.
JFS usa un approccio differente per organizzare i blocchi liberi. La struttura e' un albero, in cui i
nodi foglia sono pezzi di una bitmap invece che extent. In realta' i nodi foglia sono la
rappresentazione della tecnica "binary buddy" per quella specifica partizione (Binary Buddy e' un
metodo per localizzare e poi riunire insieme gruppi contigui di blocchi logici liberi, in modo da
ottenere un gruppo piu' grande). Come abbiamo detto discutendo le tecniche basate su bitmap, ogni
singolo bit della bitmap corrisponde a un blocco logico sul disco. Il valore di un singolo bit puo'
essere "1", cioè' blocco in uso, o "0", cioè' blocco disponibile. I pezzi della bitmap, ciascuno dei
quali e' costituito da 32 bit, possono essere considerati come un numero esadecimale. Quindi un
valore "FFFFFFFF" significherebbe che tutti i blocchi associati a quel pezzo di bitmap sono gia' in
uso. Per finire JFS costruisce un albero in grado di fornire rapidamente un gruppo di blocchi
contigui, basandosi su questo numero di allocazione e su altre informazioni.
Il nucleo di Reiserfs e' basato su B*Trees (una versione migliorata dei B+tree). La differenza
principale e' che ogni oggetto del filesystem e' collocato in un singolo B*Tree. Cio' significa che
non ci sono alberi diversi per ogni directory, ma ciascuna e' un sotto-albero dell' albero principale
del filesystem. Questo tipo di organizzazione rende necessario per Reiserfs ricorrere a tecniche di
indicizzazione piu' complesse. Un' altra grossa differenza e' che Reiserfs non usa extent, sebbene si
preveda di supportarli. ReiserFS organizza ogni oggetto del filesystem all' interno di un B*Tree.
Tali oggetti (directory, blocchi logici di file, attributi di file, collegamenti, etc.) sono tutti
organizzati all' interno del medesimo albero. Sono utilizzate tecniche di hashing per ottenere i valori
delle chiavi necessarie a organizzare gli oggetti in un BTree. Il vantaggio maggiore e' che
cambiando l' algoritmo di hashing possiamo cambiare il metodo con cui il filesytem organizza gli
oggetti, e la loro posizione relativa all' interno dell' albero. Ci sono tecniche di hashing che aiutano a
mantere contiguita' spaziale per gli oggetti correlati (attributi di directory insieme a elementi di
directory, attributi di file con i blocchi di dati del file, etc.).
Altri miglioramenti
Ci sono altre limitazioni nei filesystem simili a UFS. Tra queste c'è l’incapacità' di gestire file sparsi
come caso speciale, e il problema del numero fisso di i-node.
Supporto per i file sparsi
Supponiamo di creare un nuovo file, e di scrivere un paio di byte all' inizio. Finora tutto bene. Ma
cosa accade se proseguiamo a scrivere con un offset di 10000 byte sempre all' interno di quel file? Il
filesystem dovrebbe ora andare in cerca dei blocchi liberi necessari a colmare anche il buco tra l'
offset 2 iniziale e il seguito del file, con offset 10000. Cio' potrebbe richiedere tempo. E la domanda
e': perche' il filesystem dovrebbe inoltre allocare questi blocchi intermedi, se non ci servono? La
riposta a tale esigenza e' il supporto per file sparsi fornito dai nuovi filesystem.
Il supporto per i file sparsi e' strettamente legato alla tecnica di indirizzamento basata su extent per i
blocchi del file. Il supporto per i file sparsi sfrutta il campo "offset nel file" del descrittore di extent.
Cosi', qualora il filesystem si trovi nella situazione appena descritta, e' sufficiente costruire un
nuovo extent con il corretto campo "offset nel file". Pertanto se un programma dovesse cercare di
leggere un byte nel buco gli verrebbe restituito un valore null, dal momento che il buco non
contiene informazione. E i byte che altrimenti rimarrebbero inutilizzati nel buco prima o poi
saranno destinati ad altri programmi, che scrivono nel buco.
La soluzione di ReiserFS alla frammentazione interna
Quando abbiamo discusso la frammentazione interna e le prestazioni del filesystem abbiamo detto
che gli amministratori spesso devono scegliere tra buone prestazioni e spreco di spazio. Se
osserviamo ora la prima tabella possiamo vedere che i nuovi filesystem sono in grado di gestire
blocchi che arrivano fino a 64Kb. Dimensioni di blocchi come questa, o anche minori,
produrrebbero un significativo spreco di spazio a causa della frammentazione interna. Per rendere
possibile l' uso di grandi dimensioni di blocchi ReiserFS implementa una tecnica che risolve il
problema.
Come detto prima, ReiserFS utilizza B*Tree per organizzare gli oggetti del filesystem. Questi
oggetti sono le strutture usate per conservare le informazioni relative al file -momento di accesso,
permessi, etc.: in altre parole, l' informazione contenuta nell' i-node-, alle directory e ai dati del file.
ReiserFS chiama questi oggetti "stat data", oggetti directory e oggetti diretti/indiretti,
rispettivamente. Gli oggetti indiretti consistono in puntatori verso nodi non formattati, usati per
conservare il contenuto del file. Gli oggetti diretti immagazzinano direttamente il contenuto del file
(di piccole dimensioni). Inoltre questi oggetti sono di dimensione variabile e vengono conservati nei
nodi foglia dell' albero, talvolta insieme ad altri oggetti in caso ci sia abbastanza spazio nel nodo.
Questa e' la ragione per cui abiamo detto prima che le informazioni relative al file sono registrate
vicino ai dati del file, poiche' il filesystem tenta sempre di conservare insieme i dati e gli oggetti
diretti/indiretti dello stesso file insieme. Si noti che, a differenza degli oggetti diretti, i dati del file
indirizzati da puntatori indiretti non sono conservati all' interno dell' albero. Questa speciale
gestione degli oggetti diretti e' destinata al supporto efficiente dei file di piccole dimensione.
Gli oggetti diretti sono stati ideati per salvare piccoli file ed eventualmente la parte finale di altri
file. Pertanto molte "code" di file potrebbero essere conservate in un solo nodo foglia, producendo
una notevole riduzione dello spazio sprecato. Il problema e' che l' adozione di questa tecnica di
conservare le code dei file insieme aumenterebbe la frammentazione esterna, dal momento che i
dati di un file sarebbero in tal modo piu' lontani dalla sua coda. Inoltre l' operazione di riunire le
code e' dispendiosa in termini di tempo, e riduce le prestazioni. Questa e' una conseguenza degli
spostamenti in memoria necessari ogni volta che qualcuno accoda dati al file. Ad ogni modo la
tecnica di riunire le code puo' essere disabilitata se l' amministratore lo desidera. Conseguentemente
si tratta ancora una volta di una scelta di gestione.
Allocazione dinamica degli i-node
Un grave problema dei filesystem di tipo UFS e' l' uso di un numero fisso di i-node. Come spiegato
prima, gli i-node conservano le informazioni relative ad ogni oggetto del filesystem. Quindi un
numero prefissato di i-node limita il massimo numero di oggetti che possono essere conservati nel
filesystem. Nel caso usassimo tutti gli i-node del filesystem saremmo obbligati a fare un backup del
filesystem, riformattarlo con un maggior numero di i-node e poi ripristinare i dati. La ragione per
cui si ha un numero fisso di i-node e' che UFS ricorre a strutture statiche per registrare lo stato degli
i-node - in maniera analoga a quanto visto per i blocchi liberi. Inoltre UFS registra gli i-node in
posizioni ben definite all' interno del filesystem, in modo che non sia necessario effettuare una
conversione tra i-node e blocco logico. Il problema si presenta quando l' amministratore deve
indovinare il massimo numero di oggetti che il filesystem dovra' gestire. Si noti che non e' sempre
una buona politica di gestione quella di creare il maggior numero possibile di i-node, dal momento
che anch' essi occupano dello spazio su disco che non puo' essere usato ad altri scopi e risulterebbe
sprecato.
Per superare il problema e' stata ideata l' allocazione dinamica degli i-node. L' allocazione dinamica
degli i-node evita agli amministratori la necessita' di indovinare al momento della formattazione il
massimo numero di oggetti che saranno necessari. Ma l' uso di tecniche dinamiche comporta altri
problemi: si devono usare strutture di conversione tra i-node e blocchi logici, strutture di
registrazione degli i-node etc. I filesystem recensiti ricorrono ai B+Tree per organizzare gli i-node
allocati nel filesystem. Inoltre JFS usa extent di i-node che formano i nodi foglia di tale B+Tree, e
cosi' puo' riunire insieme fino a 32 i-node. Ci sono anche strutture che aiutano ad allocare i-node
vicino alle altre strutture correlate nel filesystem. Conseguentemente l' uso di i-node dinamici e'
complesso e dispendioso in termini di tempo, ma aiuta a superare i limiti dei vecchi filesystem.
Altre tecniche
Usa
strutture
Usa allocazione
E'
dotato
di
dinamiche
di
dinamica degli isupporto per i file
registrazione degli inode
sparsi
node
XFS
Si
B+Tree
Si
JFS
Si
B+Tree di extentdi iSi
node
ReiserFS
Si
Nel B*tree principale
Si
Ext3FS
No
No
NA
Come abbiamo spiegato nel paragrafo sulla soluzione di ReiserFS al problema della
frammentazione interna, ReiserFS adotta gli oggetti "stat_data" per conservare informazioni relative
a file specifici. Il numero di hard link, l' identificativo numerico del proprietario del file, quello del
gruppo, il tipo file, i permessi di accesso , le dimensioni etc. sono tutti conservati all' interno di un
oggetto stat_data associato al file corrispondente. Quindi l' oggetto stat_data si sostituisce all' i-node
tradizionale in tutte le funzioni tranne quella di conservare una lista di puntatori ai blocchi dati del
file. Inoltre gli oggetti di ReiserFS sono creati dinamicamente e organizzati all' interno del B*tree
principale del filesystem, in modo da consentire una allocazione dinamica di tali "i-node". Per
finire, ogni singolo oggetto del filesystem ha un campo chiave associato, che serve a localizzare l'
oggetto all' interno del B*tree. In questo campo chiave e' riservato alla fine un certo numero di bit
per registrare il tipo di oggetto in questione: stat_data, diretto, indiretto, etc. Pertanto potremmo dire
che l'organizzazione degli
i-node e' eseguita tramite
il
B*tree principale.