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.