23.01.2015 Views

Tesi Completa (PDF) - Università degli Studi di Modena e Reggio ...

Tesi Completa (PDF) - Università degli Studi di Modena e Reggio ...

Tesi Completa (PDF) - Università degli Studi di Modena e Reggio ...

SHOW MORE
SHOW LESS
  • No tags were found...

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

UNIVERSITÀ DEGLI STUDI DI MODENA E REGGIO EMILIA<br />

Facoltà <strong>di</strong> Ingegneria – Sede <strong>di</strong> <strong>Modena</strong><br />

Corso <strong>di</strong> Laurea in Ingegneria Informatica<br />

Analisi <strong>di</strong> alcune implementazioni<br />

moderne <strong>di</strong> file systems: Ext3,<br />

ReiserFS e WinFS<br />

Relatore:<br />

Prof.ssa Letizia Leonar<strong>di</strong><br />

<strong>Tesi</strong> <strong>di</strong> Laurea <strong>di</strong>:<br />

Alessandro Davolio<br />

Correlatore:<br />

Ing. Luca Ferrari<br />

__________________________________________________________________________<br />

Anno accademico 2004-2005<br />

1


SOMMARIO<br />

INTRODUZIONE 4<br />

1 REQUISITI DI UN FILE SYSTEM MODERNO 6<br />

1.1 STRUTTURA DI BASE E MASSIMA CAPACITÀ DI PARTIZIONE 7<br />

1.2 GARANZIA D’INTEGRITÀ DEI DATI 9<br />

1.3 PROBLEMI DI OCCUPAZIONE DEL DISCO IN SISTEMI MULTIUTENTE 11<br />

1.4 PRESERVARE I DATI PRIVATI DA MODIFICHE NON AUTORIZZATE 12<br />

2 IL FILE SYSTEM VIRTUALE DI LINUX (VFS) 15<br />

2.1 DOVE REPERIRE IL CODICE 17<br />

2.2 COLLEGARE UN FILE SYS TEM AL KERNEL 19<br />

2.3 CONNETTERE UN FILE SYSTEM AL DIS CO 20<br />

2.4 TROVARE UN FILE 22<br />

2.5 OPERAZIONI SUGLI INODE 23<br />

3 IL FILE SYSTEM EXT3 27<br />

3.1 STRUTTURA FISICA 27<br />

3.2 GESTIONE DELLE DIRECTORY 30<br />

3.3 ATTRIBUTI ESTESI ED ACL 32<br />

3.4 QUOTE DISCO IN EXT3 34<br />

3.5 JOURNALING 36<br />

3.6 ALTRI PARTICOLARI DI EXT3 39<br />

4 IL FILE SYSTEM REISERFS 41<br />

4.1 ORGANIZZAZIONE DELLA MEMORIA 41<br />

4.2 STRUTTURA FISICA 44<br />

4.3 IMPLEMENTAZIONE DI STREAM DI DATI E DI ATTRIBUTI 47<br />

4.4 JOURNALING 48<br />

4.5 REPACKING 50<br />

4.6 FUTURE CARATTERISTICHE DEL FILE SYSTEM 51<br />

5 WINFS 53<br />

5.1 MEMORIZZAZIONE DEI DATI: IL MODELLO NTFS 54<br />

5.2 MODELLI DEI DATI 58<br />

5.3 TIPI E SOTTOTIPI 58<br />

5.4 PROPRIETÀ DEGLI OGGETTI E CAMPI DATI 59<br />

5.5 LINGUAGGIO DI DEFINIZIONE DEGLI SCHEMI 60<br />

5.6 RELAZIONI 61<br />

5.7 USARE IL MODELLO DATI DI WINFS 62<br />

5.8 ADO.NET E WINFS 62<br />

5.9 NOTIFICAZIONI 63<br />

2


6 CONFRONTO PRESTAZIONALE 64<br />

6.1 MONGO BENCHMARK 64<br />

6.2 SLOW BENCHMARK 68<br />

7 CONCLUSIONI 69<br />

APPENDICE A – GLI ALBERI DI DATI 71<br />

A.1. DEFINIZIONI 72<br />

A.2. CHIAVI 73<br />

A.3. B ILANCIAMENTO DELLA STRUTTURA 74<br />

APPENDICE B: ACCESS CONTROL LISTS (ACLS) 77<br />

B.1. EVOLUZIONE DEI PERMESSI D’ACCESSO NEI FILE SYS TEM 77<br />

B.2. STRUTTURA DI UNA ACL 78<br />

B.3. ALGORITMO DI CONTROLLO DEGLI ACCESSI 79<br />

APPENDICE C – LINKED LISTS 81<br />

RIFERIMENTI BIBLIOGRAFICI 84<br />

3


Introduzione<br />

Un file system è quella parte del sistema operativo che consente all’utente <strong>di</strong> interagire<br />

con i dati immagazzinati nei supporti <strong>di</strong> memoria secondaria: esso rappresenta quin<strong>di</strong><br />

l’interfaccia tra i dati fisici in queste memorie ed il resto del sistema operativo, oltre che<br />

con gli applicativi che utilizzano questi dati.<br />

In questo elaborato si analizzeranno alcune implementazioni <strong>di</strong> file system, e si andrà a<br />

vedere in che modo questi interagiscano con il resto del sistema operativo e con gli<br />

applicativi <strong>di</strong> più alto livello, tenendo conto <strong>di</strong> tutti i requisiti che un moderno software<br />

<strong>di</strong> questo tipo si trova a dover sod<strong>di</strong>sfare.<br />

I file system che saranno presi in analisi sono tre:<br />

Ext3<br />

ReiserFS<br />

WinFS<br />

Ext3, che assieme a ReiserFS viene utilizzato con i sistemi operativi UNIX e Linux, è<br />

un file system <strong>di</strong> larghissimo impiego, e rappresenta un ottimo esempio <strong>di</strong> file system<br />

moderno sviluppato utilizzando il meccanismo dell’open-source. ReiserFS, anch’esso<br />

software <strong>di</strong>stribuito liberamente, è stato però principalmente sviluppato dai<br />

programmatori della Namesys, sua azienda produttrice, ed è un software molto più<br />

giovane del primo che presenta molte caratteristiche interessanti. WinFS è adottato dal<br />

nuovo sistema operativo della Microsoft (Longhorn), ed è ancora in fase <strong>di</strong> test, ma<br />

promette grosse innovazioni per quanto riguarda la gestione dei dati. Di tutti e tre questi<br />

file system saranno prese in analisi le principali caratteristiche, ma per problemi <strong>di</strong><br />

<strong>di</strong>sponibilità <strong>di</strong> co<strong>di</strong>ce sorgente e documentazione, un confronto <strong>di</strong>retto sarà fatto solo<br />

tra i primi due elencati.<br />

Il seguente elaborato è composto <strong>di</strong> sei capitoli e tre appen<strong>di</strong>ci, e dopo questa breve<br />

introduzione si andranno ad analizzare nel primo capitolo le caratteristiche e i requisiti<br />

che deve fornire un moderno file system: oggi, infatti, un software <strong>di</strong> questo tipo deve<br />

risolvere tutte le problematiche che concernono la gestione dei dati immagazzinati in<br />

memorie <strong>di</strong> massa <strong>di</strong> <strong>di</strong>mensioni sempre maggiori, e quin<strong>di</strong> <strong>di</strong>venta importante poter<br />

recuperare velocemente le informazioni richieste attraverso opportune strategie <strong>di</strong><br />

gestione della memoria secondaria. Un altro problema molto sentito oggi, è quello della<br />

sicurezza delle informazioni memorizzate: un sistema informatico deve garantire<br />

4


l’integrità dei dati presenti sui propri supporti <strong>di</strong> memoria sia rispetto ad un<br />

danneggiamento fisico (che può essere dovuto a blackout, crash del sistema operativo, o<br />

altri motivi che non sono <strong>di</strong>rettamente collegati con operazioni eseguite dall’utente), che<br />

rispetto all’alterazione dei dati dovuta ad accessi e mo<strong>di</strong>fiche effettuati su questi ultimi<br />

da utenti non autorizzati. I moderni sistemi <strong>di</strong> elaborazione si trovano spesso ad operare<br />

con dati memorizzati su supporti <strong>di</strong> <strong>di</strong>versa natura, che possono archiviare i dati con<br />

meccanismi anche molto <strong>di</strong>versi tra loro (per esempio <strong>di</strong>schi rigi<strong>di</strong> o CD); <strong>di</strong>venta<br />

quin<strong>di</strong> importante il fatto che un file system possa essere gestito dal resto del sistema<br />

operativo in modo trasparente e in<strong>di</strong>pendente dal tipo <strong>di</strong> <strong>di</strong>spositivo da cui esso va a<br />

recuperare i dati. Un file-system dovrà quin<strong>di</strong> presentarsi rispetto al co<strong>di</strong>ce sovrastante<br />

come un insieme uniforme d’istruzioni, con le quali quest’ultimo può interagire con tutti<br />

i dati ad esso accessibili, in<strong>di</strong>pendentemente dal tipo <strong>di</strong> supporto in cui sono<br />

memorizzati. Nel secondo capitolo sarà quin<strong>di</strong> presentata l’interfaccia con cui i sistemi<br />

operativi UNIX-like si relazionano con i file systems, sod<strong>di</strong>sfacendo quin<strong>di</strong> la necessità<br />

<strong>di</strong> trasparenza del co<strong>di</strong>ce rispetto alle applicazioni <strong>di</strong> livello più alto.<br />

I tre capitoli successivi, e cioè il terzo il quarto ed il quinto, riportano l’analisi effettuata<br />

sui tre file systems sopra elencati, mentre il sesto capitolo riporta i risultati <strong>di</strong> due<br />

benchmark eseguiti per confrontare Ext3 e ReiserFS, la cui documentazione è stata<br />

reperita sulla rete internet.<br />

5


1 Requisiti <strong>di</strong> un file system moderno<br />

In questo capitolo verranno prese in analisi le principali caratteristiche ed i requisiti che<br />

un file system deve sod<strong>di</strong>sfare per rispondere alle necessità dei moderni sistemi <strong>di</strong><br />

elaborazione.<br />

Per fare ciò, saranno in primo luogo analizzate, in linea <strong>di</strong> massima, le caratteristiche e<br />

gli usi dei computer o<strong>di</strong>erni.<br />

Al giorno d’oggi i computer sono utilizzati per le applicazioni più svariate, ed un<br />

calcolatore general-purpose spesso viene utilizzato con programmi anche molto <strong>di</strong>versi<br />

tra loro (ad esempio video e<strong>di</strong>ting, gestione <strong>di</strong> database, programmi <strong>di</strong> contabilità,<br />

eccetera), e non si è quin<strong>di</strong> in grado <strong>di</strong> conoscere a priori il carico <strong>di</strong> lavoro che la<br />

macchina dovrà sopportare.<br />

Non potendo conoscere le con<strong>di</strong>zioni <strong>di</strong> lavoro in cui un computer andrà ad operare, non<br />

si è in grado neanche <strong>di</strong> conoscere quali componenti (sia hardware che software)<br />

saranno sottoposti ad un maggiore stress da lavoro. Per questo motivo i progettisti<br />

(anche in questo caso sia hardware che software), negli ultimi anni, hanno cercato <strong>di</strong><br />

sviluppare i singoli componenti <strong>di</strong> una macchina in modo che si possano interfacciare<br />

con gli altri apparati secondo standard ben definiti, e senza così doversi preoccupare<br />

<strong>degli</strong> altri componenti con i quali andranno ad interagire. Concentrandosi sull’aspetto<br />

software, un esempio <strong>di</strong> come gli sviluppatori abbiano perseguito questa politica è<br />

rappresentato dalla stratificazione del co<strong>di</strong>ce: tutti i sistemi sono strutturati secondo<br />

livelli crescenti, in cui il co<strong>di</strong>ce <strong>di</strong> livello più basso è quello che comanda <strong>di</strong>rettamente<br />

la macchina, ed il co<strong>di</strong>ce più alto è quello più astratto e con cui interagisce l’utente. In<br />

una struttura <strong>di</strong> questo tipo, ogni strato (layer) <strong>di</strong> software comunica con lo strato<br />

superiore mettendo a sua <strong>di</strong>sposizione un certo numero <strong>di</strong> funzioni standard da esso<br />

interpretabili.<br />

Per quanto riguarda la gestione dei dati nelle memorie secondarie, e più nello specifico i<br />

file-system, ci si accorge che anche questi implementano <strong>di</strong>versi meccanismi per<br />

garantire la sicurezza delle informazioni rispetto a problemi molto <strong>di</strong>versi tra loro. Nei<br />

paragrafi seguenti saranno illustrati i principali problemi, riguardanti la sicurezza dei<br />

dati, che un progettista <strong>di</strong> file-system si trova a dover risolvere.<br />

6


1.1 Struttura <strong>di</strong> base e massima capacità <strong>di</strong> partizione<br />

Per dare un’idea <strong>di</strong> base <strong>di</strong> come un file-system organizzi i dati memoria, si può<br />

affermare che esso <strong>di</strong>vide quest’ultima in una serie <strong>di</strong> blocchi <strong>di</strong> <strong>di</strong>mensione finita nei<br />

quali va ad immagazzinare i dati. Le informazioni che vengono immagazzinate dentro i<br />

blocchi possono essere principalmente <strong>di</strong> tre tipi:<br />

Dati archiviati dall’utente e dalle applicazioni che operano sul sistema: sono i<br />

dati generici dei quali fanno uso gli utenti ed i programmi installati (che per fare<br />

alcuni esempi possono essere il contenuto <strong>di</strong> un file <strong>di</strong> testo oppure un brano<br />

au<strong>di</strong>o).<br />

Dati riguardanti l’organizzazione della struttura gerarchica della memoria<br />

visibile agli occhi dell’utente: per fare un esempio sono tutte le formazioni che<br />

<strong>di</strong>cono quanti e quali file sono presenti in un <strong>di</strong>rettorio, e dove questi si trovano<br />

fisicamente in memoria. Questo tipo <strong>di</strong> dati deve rappresentare un’immagine<br />

istantanea della memoria secondaria, e per questo, molte <strong>di</strong> queste informazioni<br />

vengono ricalcolate ed usate nella memoria volatile a run-time.<br />

Dati riguardanti la struttura fisica del file-system (metadati): sono tutti quei dati<br />

che non sono <strong>di</strong> <strong>di</strong>retta utilità per l’utente e non sono ad esso accessibili, ma<br />

sono utilizzati dal file-system e dal sistema operativo per gestire in modo<br />

corretto tutte le informazioni immagazzinate dall’utente e dagli applicativi.<br />

Questi dati sono ad esempio il numero <strong>di</strong> blocchi in cui è <strong>di</strong>viso il <strong>di</strong>sco rigido,<br />

la <strong>di</strong>mensione d’ogni blocco, o una descrizione riassuntiva dell’utilizzo <strong>di</strong> ogni<br />

blocco, e nella maggior parte dei file-system sono memorizzati in un apposito<br />

blocco, a cui ha accesso soltanto il sistema operativo, chiamato superblocco.<br />

Questa <strong>di</strong>stinzione fatta per i tipi <strong>di</strong> dati spesso vale anche per i blocchi <strong>di</strong> memoria<br />

secondaria, in quanto questi sono utilizzati in modo <strong>di</strong>verso a seconda <strong>di</strong> dove si trovano<br />

all’interno dell’albero dei <strong>di</strong>rettori. Questo albero, che rispecchia l’organizzazione in<br />

file e cartelle che appare agli occhi dell’utente, utilizza ciascun blocco o come nodo o<br />

come foglia.<br />

I no<strong>di</strong> sono quei blocchi che memorizzano tutte le informazioni riguardanti la struttura<br />

gerarchica del file system: essi conterranno quin<strong>di</strong> i riferimenti ad altri no<strong>di</strong> <strong>di</strong> livello a<br />

loro <strong>di</strong>rettamente inferiore, oppure potranno contenere i riferimenti alle foglie, che sono<br />

i blocchi che vengono utilizzati per immagazzinare i dati che sono utilizzati da gli<br />

7


applicativi (file). Esistono poi blocchi speciali riservati a contenere soltanto metadati (il<br />

superblocco appunto, con le eventuali sue copie), che sono al <strong>di</strong> fuori<br />

dell’organizzazione gerarchica dei <strong>di</strong>rettori, e piuttosto ne gestiscono l’utilizzo.<br />

Per ulteriori informazioni riguardanti gli alberi si faccia riferimento all’appen<strong>di</strong>ce A.<br />

Il problema della massima <strong>di</strong>mensione <strong>di</strong> partizione per un file-system è un problema<br />

che viene affrontato nelle prime fasi <strong>di</strong> progetto, in quanto esso va ad influenzare<br />

parametri che <strong>di</strong>fficilmente possono essere gestiti in modo automatico dal calcolatore a<br />

runtime, come la lunghezza in byte <strong>degli</strong> in<strong>di</strong>rizzi fisici dei dati presenti in memoria:<br />

decidendo per esempio che lo spazio d’in<strong>di</strong>rizzamento del file system che si vuole<br />

progettare sarà <strong>di</strong> quattro byte, si va a limitare automaticamente a 2 32 il numero<br />

massimo <strong>di</strong> in<strong>di</strong>rizzi allocabili, ma si va anche stabilire che qualunque oggetto che fa<br />

riferimento a dati presenti memoria dovrà contenere un campo <strong>di</strong> in<strong>di</strong>rizzo della<br />

<strong>di</strong>mensione <strong>di</strong> 4 byte (32 bit).<br />

Oltre allo spazio d’in<strong>di</strong>rizzamento, esiste poi tutta una serie <strong>di</strong> parametri che possono<br />

andare a ridurre notevolmente lo spazio per i dati archiviati dall’utente, nel caso in cui<br />

ci si trovasse a lavorare con applicazioni che archiviano soltanto file <strong>di</strong> <strong>di</strong>mensione<br />

molto ridotta: questi parametri sono tutte le informazioni aggiuntive riguardanti i file<br />

che si vogliono memorizzare (e che non sono <strong>di</strong> <strong>di</strong>retto interesse per l’utente), come ad<br />

esempio i permessi d’accesso, la data <strong>di</strong> creazione, l’autore, gli attributi, e la stringa<br />

contenente il nome del file. Tutti questi parametri hanno una loro precisa occupazione<br />

<strong>di</strong> memoria e, nel loro insieme, in certi casi possono occupare più spazio su <strong>di</strong>sco <strong>di</strong><br />

quello occupato dai dati d’interesse per l'utente (in altre parole il contenuto del file<br />

stesso).<br />

Un altro problema strettamente correlato con la <strong>di</strong>mensione dei dati che non sono<br />

d’interesse <strong>di</strong>retto dell’utente, è il numero massimo <strong>di</strong> file memorizzabili all’interno <strong>di</strong><br />

un <strong>di</strong>rettorio. Nei file system o<strong>di</strong>erni, le informazioni aggiuntive riguardanti i file<br />

(in<strong>di</strong>rizzo fisico da cui recuperare i dati) sono raggruppati in sequenze <strong>di</strong> dati chiamati<br />

“descrittori” del file stesso; e sono questi descrittori che, memorizzati consecutivamente<br />

all’interno <strong>di</strong> un blocco <strong>di</strong>rettorio, vanno a descrivere i file contenuti all’interno del<br />

<strong>di</strong>rettorio stesso. Per aumentare il numero massimo <strong>di</strong> file memorizzabili all’interno <strong>di</strong><br />

un <strong>di</strong>rettorio, il progettista può scegliere <strong>di</strong> fare due cose:<br />

Aumentare la <strong>di</strong>mensione <strong>di</strong> blocchi in cui viene <strong>di</strong>visa la memoria: in questo modo<br />

viene reso <strong>di</strong>sponibile più spazio in cui memorizzare i descrittori dei file ed allo<br />

8


stesso tempo vengono velocizzare le operazioni <strong>di</strong> I/O, in quanto le testine del <strong>di</strong>sco<br />

rigido dovranno fare meno salti tra un blocco e l’altro per accedere al contenuto <strong>di</strong><br />

un file. D’altro canto, però, blocchi <strong>di</strong> maggiore <strong>di</strong>mensione implicano un maggiore<br />

spreco <strong>di</strong> spazio su <strong>di</strong>sco. Bisogna considerare che me<strong>di</strong>amente l’ultimo blocco<br />

allocato ad un file è <strong>di</strong> solito occupato solo per la metà del suo spazio, quin<strong>di</strong> più un<br />

blocco <strong>di</strong>venta grande, maggiore sarà lo spazio sprecato nell’ultimo blocco d’ogni<br />

file.<br />

Diminuire la <strong>di</strong>mensione delle informazioni che si vogliono memorizzare nei<br />

descrittori: questa soluzione permette sì <strong>di</strong> memorizzare un maggior numero <strong>di</strong> file<br />

all’interno <strong>di</strong> un <strong>di</strong>rettorio, ma allo stesso tempo <strong>di</strong>minuisce drasticamente il numero<br />

d’informazioni aggiuntive che possono essere memorizzate come riferimento ad un<br />

file.<br />

Bisogna però ricordare che la maggior parte dei file-system, per ovviare a questo<br />

problema, utilizza una soluzione interme<strong>di</strong>a: si sceglie in pratica <strong>di</strong> memorizzare<br />

all’interno del descrittore soltanto le informazioni che devono essere recuperate in modo<br />

più veloce, e si decide <strong>di</strong> collocare tutti i dati che sarebbero letti solo in caso<br />

d’interazione con il contenuto del file (come ad esempio i permessi d’accesso, che<br />

possono essere letti in caso <strong>di</strong> richiesta <strong>di</strong> accesso contenuto del file) assieme al<br />

contenuto del file stesso, cioè i dati generici archiviati da utente ed applicazioni.<br />

1.2 Garanzia d’integrità dei dati<br />

Un altro requisito che i file system devono sod<strong>di</strong>sfare, è quello <strong>di</strong> garantire l’integrità<br />

dei dati anche dopo blackout o altri arresti improvvisi del sistema che possono lasciare<br />

incompiute le operazioni <strong>di</strong> I/O.<br />

Per prima cosa bisogna osservare che in caso <strong>di</strong> riavvio dopo blackout, i dati che<br />

potrebbero risultare corrotti o danneggiati possono essere sia i dati utente sia quelli<br />

riguardanti il file system in sè; è quin<strong>di</strong> necessario provvedere a meccanismi che<br />

assicurino sia i dati dell’utente sia i dati ad uso ristretto del sistema operativo.<br />

Nel caso in cui un arresto improvviso del sistema dovesse danneggiare il superblocco,<br />

nel migliore dei casi, il sistema operativo si troverebbe ad interpretare (se i dati<br />

danneggiati risultassero per caso interpretabili dalla macchina) informazioni che non<br />

rispecchiano l’organizzazione e l’uso della memoria, col rischio <strong>di</strong> danneggiare le<br />

9


imanenti porzioni <strong>di</strong> dati che non sono stati coinvolti in precedenza. Per ovviare a<br />

questo problema la maggior parte dei file system mantiene almeno una copia del<br />

superblocco, o comunque <strong>di</strong> tutte le informazioni sulla struttura della memoria, in<br />

settori del <strong>di</strong>sco lontani dal superblocco stesso; questo per scongiurare che le testine del<br />

<strong>di</strong>sco rigido, durante l’arresto, possano danneggiare le copie nel caso queste fossero<br />

posizioniate in settori a<strong>di</strong>acenti il superblocco. Una soluzione <strong>di</strong> questo tipo garantisce<br />

nella maggior parte dei casi che il sistema riesca a recuperare i dati riguardanti<br />

l’organizzazione delle informazioni su <strong>di</strong>sco, ma allo stesso tempo crea un’elevata<br />

ridondanza della metadata; cosa che però non va ad influire molto sullo spazio messo a<br />

<strong>di</strong>sposizione per i dati utente, viste le piccole <strong>di</strong>mensioni occupate dalle informazioni<br />

strutturali rispetto alle <strong>di</strong>mensioni dei <strong>di</strong>schi rigi<strong>di</strong> in questo momento in commercio.<br />

Un’altra soluzione che viene utilizzata per controllare la presenza <strong>di</strong> errori all’interno<br />

dei blocchi sono le “bitmap”: esse sono dei record <strong>di</strong> <strong>di</strong>mensione variabile (<strong>di</strong>pendente<br />

<strong>di</strong>mensione del blocco stesso) che rappresentano l’utilizzo dei vari byte (oppure altra<br />

misura <strong>di</strong> memoria utilizzata dal sistema operativo) del blocco stesso; in questi record,<br />

generalmente, ogni bit rappresenta un’unità <strong>di</strong> memoria del blocco, ed il fatto che un bit<br />

sia uguale a 1 o a 0 sta ad in<strong>di</strong>care l’utilizzo o meno della relativa unità <strong>di</strong> memoria del<br />

blocco (dove per unità <strong>di</strong> memoria s’intende appunto la misura <strong>di</strong> memoria utilizzata dal<br />

S.O., qualunque essa sia). Una soluzione <strong>di</strong> questo tipo può essere adottata anche nel<br />

superblocco, per descrivere sia l’utilizzo del superblocco stesso che l’uso, nel loro<br />

complesso, <strong>di</strong> tutti gli altri blocchi <strong>di</strong> memoria.<br />

Un metodo già da tempo adottato per garantire l’integrità delle informazioni in memoria<br />

è quello <strong>di</strong> eseguire automaticamente, al riavvio della macchina dopo un arresto non<br />

previsto, le utility per il controllo dell’integrità dei dati messe spesso a <strong>di</strong>sposizione<br />

assieme dal sistema operativo (ad esempio scan<strong>di</strong>sk per i sistemi Microsoft, oppure<br />

e2fsck per le partizioni formattate con Ext2): l’utilizzo <strong>di</strong> questi programmi, però, si sta<br />

rivelando sempre meno pratico per via delle <strong>di</strong>mensioni sempre maggiori dei <strong>di</strong>schi<br />

rigi<strong>di</strong> o<strong>di</strong>erni. Questo tipo <strong>di</strong> programmi, generalmente, non compie ricerche mirate dei<br />

dati danneggiati, ma esegue scansioni complete della memoria, cercando blocco per<br />

blocco gli eventuali dati danneggiati, aumentando così il tempo necessario ad eseguire<br />

una scansione completa proporzionalmente alle <strong>di</strong>mensioni della memoria stessa.<br />

10


Un metodo per garantire l’integrità dei dati che ha preso piede negli ultimi anni,<br />

consiste nel registrare perio<strong>di</strong>camente le operazioni <strong>di</strong> I/O eseguite su <strong>di</strong>sco all’interno<br />

<strong>di</strong> un log (registro, <strong>di</strong>ario), in modo da avere sempre sotto controllo quali dati sono stati<br />

realmente scritti in memoria secondaria. In linea <strong>di</strong> massima questi meccanismi<br />

funzionano tutti allo stesso modo: prima <strong>di</strong> eseguire un’operazione <strong>di</strong> scrittura su <strong>di</strong>sco,<br />

il file-system registra una copia dei dati che andranno ad essere scritti nella porzione <strong>di</strong><br />

memoria a<strong>di</strong>bita a registro, dati che saranno tolti dal registro soltanto dopo che<br />

l’operazione <strong>di</strong> scrittura è stata eseguita. In questo modo, nel caso <strong>di</strong> arresti non previsti<br />

<strong>di</strong> sistema, il file-system può usare il registro per controllare quali operazioni <strong>di</strong> scrittura<br />

sono rimaste eventualmente incompiute, andando così a ripristinare integrità dei dati.<br />

Una soluzione <strong>di</strong> questo tipo risulta essere molto più funzionale sui sistemi o<strong>di</strong>erni<br />

dell’utilizzo <strong>degli</strong> appositi programmi per il controllo d’integrità, ma allo stesso tempo<br />

deve far fronte a tutti i problemi d’integrità dei dati nel registro che non riguardano tutto<br />

il resto della memoria in caso <strong>di</strong> blocchi improvvisi: il file-system deve essere quin<strong>di</strong> in<br />

grado <strong>di</strong> “capire“ quali operazioni <strong>di</strong> scrittura sono andate a buon fine,<br />

in<strong>di</strong>pendentemente dal fatto che il registro sia o meno stato danneggiato durante<br />

l’arresto. Un altro problema che questo tipo <strong>di</strong> registri deve affrontare riguarda la<br />

scrittura su <strong>di</strong>sco <strong>di</strong> file molto estesi: il fatto <strong>di</strong> porre sul registro tutti i file che devono<br />

essere scritti, in<strong>di</strong>pendentemente dalla loro <strong>di</strong>mensione, causa uno spreco <strong>di</strong> memoria ed<br />

un rallentamento delle operazioni <strong>di</strong> scrittura (con questo meccanismo un file viene<br />

scritto due volte in memoria secondaria) proporzionali alla <strong>di</strong>mensione del file stesso;<br />

bisogna quin<strong>di</strong> porre un limite massimo alla <strong>di</strong>mensione dei file scrivibili in registro, ed<br />

allo stesso tempo definire una politica <strong>di</strong> trattamento della scrittura dei file <strong>di</strong><br />

<strong>di</strong>mensioni superiori.<br />

1.3 Problemi <strong>di</strong> occupazione del <strong>di</strong>sco in sistemi multiutente<br />

Un altro dei problemi che i file-system si trovano a dover risolvere riguarda l’utilizzo<br />

della memoria <strong>di</strong> massa da parte dei vari utenti <strong>di</strong> uno stesso sistema. Il centro della<br />

questione non è tanto quello <strong>di</strong> garantire un equo spazio a tutti gli utenti, ma<br />

principalmente è l’impe<strong>di</strong>re che l’intero <strong>di</strong>sco rigido venga occupato dai dati dei vari<br />

utilizzatori, impedendo così la possibilità <strong>di</strong> eseguire operazioni <strong>di</strong> manutenzione o <strong>di</strong><br />

ripristino <strong>di</strong> informazioni su <strong>di</strong>sco da parte dell’amministratore. Le soluzioni per<br />

11


ovviare a questo problema sono numerose, e ciascun file-system ne adotta alcune in<br />

particolare; in questo paragrafo ci si limiterà ad elencare le caratteristiche delle<br />

principali <strong>di</strong> queste.<br />

Il metodo più banale per ovviare a questo problema è quello <strong>di</strong> riservare<br />

permanentemente uno spazio minimo <strong>di</strong> memoria, <strong>di</strong> <strong>di</strong>mensione stabilita,<br />

all’amministratore <strong>di</strong> sistema; in questo modo si lascia ciascun utente libero <strong>di</strong> utilizzare<br />

tutte le risorse <strong>di</strong> sistema <strong>di</strong>sponibili, lasciando un minimo <strong>di</strong> spazio riservato al<br />

superutente per le operazioni d’emergenza, nel caso il <strong>di</strong>sco si riempisse.<br />

Una soluzione più raffinata della precedente consiste nel riservare a ciascun utente<br />

opportune porzioni <strong>di</strong> memoria, con le quali memorizzare i propri dati personali: queste<br />

porzioni prendono il nome <strong>di</strong> “quote <strong>di</strong>sco” (<strong>di</strong>sk quotas). Assieme alle quote <strong>di</strong>sco può<br />

essere poi definita una serie <strong>di</strong> politiche, riguardanti l’uso <strong>di</strong> queste ultime, spesso<br />

gestite dai livelli superiori del sistema operativo, e non <strong>di</strong>rettamente dal file-system:<br />

viene spesso permesso <strong>di</strong> con<strong>di</strong>videre le quote <strong>di</strong>sco tra utenti dello stesso gruppo,<br />

oppure viene dato il permesso a certi utenti <strong>di</strong> poter usufruire delle quote <strong>di</strong>sco <strong>di</strong> altri,<br />

ecc.<br />

Accanto alle politiche <strong>di</strong> gestione delle quote si affiancano poi le politiche<br />

d’archiviazione dei dati: la stragrande maggioranza dei file-system o<strong>di</strong>erni permette <strong>di</strong><br />

definire il <strong>di</strong>rettorio <strong>di</strong> lavoro e quello <strong>di</strong> partenza <strong>di</strong> ciascun utente, in modo che questi<br />

possa archiviare i propri dati soltanto in questi spazi definiti, a cui possono avere<br />

accesso soltanto altri utenti autorizzati; si può anche decidere <strong>di</strong> utilizzare le politiche in<br />

modo combinato, riservando tutta la quota <strong>di</strong>sco <strong>di</strong> un singolo utente soltanto all’interno<br />

della propria cartella <strong>di</strong> lavoro, in modo che esso non abbia la possibilità <strong>di</strong> archiviare i<br />

suoi dati fuori da quest’ultima.<br />

1.4 Preservare i dati privati da mo<strong>di</strong>fiche non autorizzate<br />

Accanto al problema delle quote <strong>di</strong>sco e delle politiche <strong>di</strong> gestione dello spazio utente,<br />

si affiancano tutti i problemi che riguardano la garanzia dell’integrità e della privatezza<br />

dei dati rispetto ad accessi effettuati da utenti non autorizzati; ecco perchè la maggior<br />

parte dei sistemi operativi multiutente prevede la possibilità <strong>di</strong> limitare l’accesso ai<br />

contenuti dei file.<br />

12


Tutti i sistemi <strong>di</strong> sicurezza delle memorie secondarie, utilizzati dai sistemi operativi,<br />

ruotano attorno ai concetti <strong>di</strong> utente, gruppo, permesso e proprietà.<br />

Per utente s’intende ogni singolo utilizzatore, che è automaticamente identificato<br />

attraverso un co<strong>di</strong>ce univoco chiamato “identificativo <strong>di</strong> protezione” (security identifier<br />

- SID); co<strong>di</strong>ce che viene utilizzato dal sistema anche per identificare i profili utente, che<br />

raccolgono tutte le informazioni e le impostazioni personalizzate sull’utilizzo del<br />

sistema <strong>di</strong> ogni utente. Per gruppo s’intende invece un generico insieme d’utenti,<br />

accomunato dal fatto <strong>di</strong> possedere le stesso co<strong>di</strong>ce identificativo <strong>di</strong> gruppo (group<br />

identifier – GID), che viene memorizzato all’interno <strong>di</strong> ciascun profilo utente. Se si<br />

definisce il termine “proprietà” come l’insieme <strong>di</strong> tutte le operazioni che sono eseguibili<br />

su un documento o, in generale, un file, il termine permesso assume il significato <strong>di</strong><br />

relazione tra utente o gruppo, e proprietà <strong>di</strong> un documento: un permesso <strong>di</strong> lettura, per<br />

esempio, è la relazione <strong>di</strong> autorizzazione all’azione <strong>di</strong> lettura <strong>di</strong> un determinato file,<br />

concessa ad un utente oppure ad un gruppo.<br />

I sistemi <strong>di</strong> controllo d’accesso ai dati <strong>degli</strong> o<strong>di</strong>erni sistemi operativi hanno, in generale,<br />

alcune caratteristiche in comune:<br />

Accesso <strong>di</strong>screzionale ad oggetti da proteggere: il proprietario <strong>di</strong> un oggetto, ad<br />

esempio un file o una cartella, è in grado <strong>di</strong> concedere o negare l’autorizzazione<br />

ai vari utenti, per controllare come e da chi l’oggetto viene utilizzato.<br />

Ere<strong>di</strong>tarietà delle autorizzazioni: gli oggetti possono ere<strong>di</strong>tare l’autorizzazione<br />

dall’oggetto che li contiene, ad esempio un file può ere<strong>di</strong>tare le autorizzazioni<br />

della cartella in cui è contenuto.<br />

Privilegi <strong>di</strong> amministratore: è possibile controllare gli utenti o i gruppi che<br />

possono eseguire funzioni amministrative e apportare mo<strong>di</strong>fiche che influiscono<br />

sulle risorse <strong>di</strong> sistema.<br />

Controllo <strong>di</strong> eventi <strong>di</strong> sistema: è possibile utilizzare delle funzionalità <strong>di</strong><br />

controllo per in<strong>di</strong>viduare eventuali tentativi <strong>di</strong> elusione della protezione o per<br />

creare un itinerario <strong>di</strong> controllo.<br />

Utilizzo <strong>di</strong> liste <strong>di</strong> controllo accesso (access control list – acl): le acl sono liste<br />

or<strong>di</strong>narie <strong>di</strong> regole che vengono usate per prendere una decisione, ad esempio se<br />

permettere o meno ad un certo utente l’accesso ad un file; ciascuna regola<br />

esprime una o più proprietà dell’oggetto da valutare (ad esempio l’autore, il<br />

nome o l’in<strong>di</strong>rizzo <strong>di</strong> un file), e se queste proprietà sono verificate essa in<strong>di</strong>ca<br />

13


quale decisione prendere 1 . Queste strutture non trovano impiego soltanto<br />

all’interno <strong>di</strong> file system, ma sono largamente usate per la gestione <strong>degli</strong> accessi<br />

nei <strong>di</strong>spositivi <strong>di</strong> rete: esse, infatti, possono essere per esempio utilizzate nella<br />

configurazione dei firewall o come controlli <strong>di</strong> smistamento dei pacchetti<br />

passanti sui router.<br />

1 Per una trattazione più ampia <strong>di</strong> questo tema si faccia riferimento all’appen<strong>di</strong>ce B.<br />

14


2 Il File System Virtuale <strong>di</strong> Linux (VFS)<br />

Prima <strong>di</strong> iniziare ad analizzare due file system utilizzabili su sistemi Linux, è necessario<br />

dare un paio <strong>di</strong> nozioni riguardo a come il sistema operativo <strong>di</strong>spone <strong>di</strong> un’unica<br />

interfaccia in grado <strong>di</strong> relazionarsi in modo trasparente con i file system che sono<br />

utilizzati sulla memoria, e in modo in<strong>di</strong>pendente da questi ultimi.<br />

In Linux, l’accesso a tutti i file avviene attraverso il Virtual Filesystem Switch, o VFS<br />

[VFS]. Questo è uno strato <strong>di</strong> co<strong>di</strong>ce interposto tra il file-system ed il resto del sistema<br />

operativo, che implementa le operazioni generiche <strong>di</strong> file system richieste dal sistema<br />

collegandole con lo specifico co<strong>di</strong>ce necessario per gestirle (co<strong>di</strong>ce che sarà<br />

necessariamente <strong>di</strong>verso a seconda del file system utilizzato). Per ragioni <strong>di</strong> como<strong>di</strong>tà,<br />

nei seguenti capitoli, si farà riferimento al Virtual Filesystem Switch anche con i termini<br />

“file system virtuale” e “switch virtuale”.<br />

La figura 2.1 mostra come il Kernel <strong>di</strong> sistema operativo intercetti tutte le richieste<br />

d’accesso ai dati eseguite dalle applicazioni, le passi al VFS che, associando ad esse il<br />

corretto co<strong>di</strong>ce a seconda del file system su cui i dati si trovano, formula delle richieste<br />

<strong>di</strong> accesso al file system appropriate. Tutte le richieste d’accesso ai dati sono gestite<br />

utilizzando un buffer dati virtuale (buffer cache), le cui operazioni in uscita vanno ad<br />

accedere alle informazioni per mezzo dei driver che comandano le memorie fisiche.<br />

Tutte queste operazioni sono eseguite in modo del tutto trasparente rispetto alle<br />

applicazioni <strong>di</strong> più alto livello, in quanto l’elaborazione delle richieste avviene a livello<br />

<strong>di</strong> Kernel.<br />

15


Figura 2.1: Funzionamento del VFS all’interno del kernel Linux.<br />

Per meglio chiarire il funzionamento del VFS, in questo capitolo saranno utilizzate parti<br />

<strong>di</strong> co<strong>di</strong>ce che lo switch usa per gestire il funzionamento del file system Ext2. Il modo<br />

con cui il VFS interagisce con il co<strong>di</strong>ce <strong>degli</strong> altri file system è del tutto analogo a<br />

quello riportato nei paragrafi successivi.<br />

Tutto il VFS interagisce con i file system presenti sulle memorie secondarie utilizzando<br />

due entità <strong>di</strong>verse (e le relative strutture dati che le descrivono, <strong>di</strong>chiarate nel file<br />

linux/fs.h):<br />

Il superblocco, descritto nella struttura super_block, che contiene la<br />

descrizione del superblocco (e quin<strong>di</strong> <strong>di</strong> tutto il file system) e fornisce una serie<br />

<strong>di</strong> meto<strong>di</strong> al sistema operativo per interagire con esso.<br />

L’inode, definito nell’omonima struttura, che contiene la descrizione <strong>di</strong> un file o<br />

<strong>di</strong>rettorio a cui si vuole accedere (in Linux le <strong>di</strong>rectory sono interpretate dal<br />

sistema come particolari tipi <strong>di</strong> file contenenti una serie <strong>di</strong> riferimenti, “inode<br />

number”, ad altri file). Ogni inode rappresenta un file, e contiene tutte le<br />

informazioni necessarie alla gestione del contenuto del file stesso (in<strong>di</strong>rizzo<br />

fisico, nome, lunghezza, ecc.), ed è identificato da un preciso “inode number”<br />

16


che permette <strong>di</strong> rintracciarlo all’interno <strong>di</strong> una tabella in cui sono memorizzati<br />

tutti gli inode presenti sul file system (e <strong>di</strong> conseguenza anche tutti i file). Un<br />

esempio <strong>di</strong> come il VFS utilizza gli inode per organizzare i dati all’interno delle<br />

<strong>di</strong>rectory è mostrato in figura 2.2: ogni <strong>di</strong>rettorio contiene un riferimento alla<br />

tabella <strong>degli</strong> inode per ogni file in esso contenuto, e per accedere ai dati del file<br />

sarà quin<strong>di</strong> necessario andare a leggere il contenuto dello specifico inode nella<br />

tabella. L'inode table, infatti, contiene l'elenco <strong>di</strong> tutti gli inode (quin<strong>di</strong> <strong>di</strong> tutti i<br />

file) sulla partizione (questo per i file system fisici, <strong>di</strong> cui si tratta nel presente<br />

elaborato), e per ognuno <strong>di</strong> questi vengono memorizzati uno o più riferimenti<br />

alla corrispondente voce <strong>di</strong> tabella all’interno dei <strong>di</strong>rettori.<br />

Figura 2.2: Utilizzo della tabella <strong>degli</strong> inode per organizzare i file all'interno delle <strong>di</strong>rectory.<br />

Assieme a queste entità entrano anche in gioco le strutture dati ad esse correlate, che<br />

forniscono le operazioni che il sistema può eseguire sulle entità stesse; strutture che<br />

sono principalmente super_operations, inode_operations e<br />

file_operations, ed altre che sono comunque contenute in linux/fs.h.<br />

2.1 Dove reperire il co<strong>di</strong>ce<br />

Il co<strong>di</strong>ce sorgente del VFS si trova nel sotto<strong>di</strong>rettorio fs/ dei sergenti del kernel <strong>di</strong><br />

Linux, assieme ad altre parti <strong>di</strong> co<strong>di</strong>ce correlate, come la buffer cache ed il co<strong>di</strong>ce per<br />

interagire con tutti i formati <strong>di</strong> file eseguibile. Ogni specifico file system è contenuto in<br />

una <strong>di</strong>rectory inferiore; per esempio, i sorgenti del file system Ext2 sono contenuti in<br />

fs/ext2/.<br />

17


La tabella 2.1 riporta il nome dei file del <strong>di</strong>rettorio fs/, e dà per ciascuno <strong>di</strong> essi una<br />

breve descrizione. La colonna centrale, chiamata system, vuole in<strong>di</strong>care a quale<br />

sottosistema il file è (principalmente) de<strong>di</strong>cato:<br />

EXE significa che i file è utilizzato per gestire ed interagire con i file eseguibili<br />

DEV significa che viene utilizzato come supporto ai driver per i vari <strong>di</strong>spositivi<br />

installati sulla macchina<br />

BUF significa gestione della buffer cache.<br />

VFS significa che il file è parte del file system virtuale, e delega alcune<br />

funzionalità al co<strong>di</strong>ce specifico <strong>di</strong> ogni file system<br />

VFSg in<strong>di</strong>ca che il co<strong>di</strong>ce presente nel file è completamente generico e non<br />

delega mai parte le sue funzioni a co<strong>di</strong>ce specifico <strong>di</strong> ogni file system.<br />

File<br />

binfmt_aout.c EXE<br />

system Funzione<br />

Esecuzione <strong>degli</strong> eseguibili <strong>di</strong> tipo a.out<br />

binfmt_elf.c EXE Esecuzione dei file eseguibili <strong>di</strong> tipo ELF<br />

binfmt_java.c EXE<br />

Esecuzione dei file java e delle applets<br />

binfmt_script.c EXE Esecuzione <strong>degli</strong> script <strong>di</strong> tipo # e !<br />

block_dev.c DEV Funzioni read(), write(), e fsync() per blocchi generici<br />

buffer.c<br />

dcache.c<br />

devices.c<br />

BUF<br />

VFS<br />

DEV<br />

Gestione della buffer cache, che memorizza i blocchi letti dai<br />

<strong>di</strong>spositivi.<br />

La <strong>di</strong>rectory cache, che memorizza i nomi dei <strong>di</strong>rettori durante<br />

le ricerche.<br />

Funzioni per il supporto <strong>di</strong> <strong>di</strong>spositivi generici, come ad<br />

esempio i registri<br />

dquot.c VFS Supporto generico per la gestione delle quote <strong>di</strong>sco.<br />

exec.c<br />

fcntl.c<br />

VFSg<br />

VFSg<br />

Supporto generico per i file eseguibili. Le funzioni <strong>di</strong> call si<br />

trovano nei files binfmt_*.<br />

Supporto per la gestione dei descrittori dei file con la funzione<br />

fcntl().<br />

fifo.c VFSg Gestione del buffer FIFO per l’accesso ai <strong>di</strong>schi.<br />

file_table.c VFSg Lista <strong>di</strong>namicamente estensibile dei files aperti dal sistema.<br />

filesystems.c<br />

VFS<br />

Tutti il file system precompilati sono richiamati da questo file<br />

attraverso la funzione init_name_fs().<br />

inode.c VFSg Lista <strong>di</strong>namicamente estensibile <strong>degli</strong> inode aperti dal sistema.<br />

ioctl.c<br />

VFS<br />

Primo livello per l’handling dei controlli <strong>di</strong> I/O;<br />

successivamente l’handling viene passato al file system o al<br />

driver interessato, se necessario.<br />

18


locks.c VFSg Supporto per le varie operazioni <strong>di</strong> locking dei file<br />

namei.c<br />

noquot.c<br />

VFS<br />

VFS<br />

Riempie l’inode una volta fornito il percorso. Implementa<br />

<strong>di</strong>verse system calls collegate ai nomi dei file.<br />

Ottimizzazione per gestire il sistema nel caso non si usino le<br />

quote <strong>di</strong>sco<br />

open.c VFS Contiene system calls, comprese open(), close(), and vhangup().<br />

pipe.c VFSg Implementazione delle pipes.<br />

read_write.c VFS read(), write(), readv(), writev(), lseek().<br />

read<strong>di</strong>r.c VFS Contiene <strong>di</strong>verse interfacce usate per la lettura delle <strong>di</strong>rectory<br />

select.c VFS Le basi per la system call select()<br />

stat.c VFS Supporto per stat() e readlink()<br />

super.c<br />

VFS<br />

Supporto per superblocco, filesystem registry,<br />

mount()/umount().<br />

Tabella 2.1: Breve descrizione del contenuto dei file che compongono il co<strong>di</strong>ce del VFS Linux.<br />

2.2 Collegare un file system al kernel<br />

Per poter utilizzare i dati presenti in un particolare file system, in UNIX e Linux, sono<br />

necessarie due operazioni: la registrazione del file system ed il montaggio della<br />

partizione dati.<br />

Registrare un file system significa fornire al VFS le caratteristiche del file system che si<br />

vuole utilizzare, come ad esempio il tipo <strong>di</strong> file system, in modo che lo switch virtuale<br />

sia poi in grado <strong>di</strong> reperire il co<strong>di</strong>ce necessario per gestire quel particolare tipo <strong>di</strong><br />

partizione.<br />

Se si cerca nel co<strong>di</strong>ce <strong>di</strong> ogni file system la funzione init_name_fs(), si vede che<br />

essa contiene poche linee <strong>di</strong> co<strong>di</strong>ce. Per esempio, nel file system Ext2, la funzione è<br />

come segue (da fs/ext2/super.c):<br />

int init_ext2_fs(void)<br />

{<br />

}<br />

return register_filesystem(&ext2_fs_type);<br />

Tutto quello che la funzione svolge è registrare il file system nel sistema operativo<br />

attraverso la struttura ext2_fs_type:<br />

static struct file_system_type ext2_fs_type = {<br />

};<br />

ext2_read_super, "ext2", 1, NULL<br />

19


ext2_read_super è un puntatore a funzione che in<strong>di</strong>ca al sistema operativo<br />

l’in<strong>di</strong>rizzo in cui si trova la funzione per la lettura del superblocco nei file system <strong>di</strong> tipo<br />

Ext2 (operazione necessaria per montare correttamente un qualsiasi file system).<br />

“ext2” è il nome del tipo <strong>di</strong> file system, che è usato (ad esempio quando si <strong>di</strong>gita il<br />

comando mount … -t ext2) per determinare quale file system utilizzare per montare<br />

un <strong>di</strong>sco rigido. “1” in<strong>di</strong>ca che il file system richiede un <strong>di</strong>spositivo <strong>di</strong> memoria su cui<br />

operare (a <strong>di</strong>fferenza per esempio dei file system <strong>di</strong> rete, che non si relazionano<br />

<strong>di</strong>rettamente con una memoria <strong>di</strong> massa, ma recuperano i dati, attraverso un’interfaccia<br />

<strong>di</strong> rete, da una memoria remota), e NULL è necessario per riempire lo spazio che verrà<br />

utilizzato per mantenere una linked list <strong>di</strong> tutti i file system nel registro del VFS,<br />

contenuto in fs/super.c.<br />

2.3 Connettere un file system al <strong>di</strong>sco<br />

Il resto della comunicazione tra il co<strong>di</strong>ce del file system ed il Kernel non avviene finché<br />

non viene montato un <strong>di</strong>spositivo (partizione dati, o intero <strong>di</strong>sco rigido) che utilizza quel<br />

tipo <strong>di</strong> file system. Quando si monta un <strong>di</strong>spositivo contenente un file system <strong>di</strong> tipo<br />

Ext2, viene chiamata la funzione ext2_read_super() che, andando a leggere i dati<br />

contenuti nel superblocco, fornisce al sistema operativo i dati riguardanti la struttura<br />

fisica della partizione. Se la lettura del superblocco avviene con successo e la funzione è<br />

in grado <strong>di</strong> collegare lo specifico file system al <strong>di</strong>sco che si vuole montare, essa riempie<br />

la struttura super_block con <strong>di</strong>verse informazioni che includono un puntatore alla<br />

struttura chiamata super_operations, che a sua volta contiene puntatori a funzioni<br />

volte alla manipolazione dei superblocchi; in questo caso, puntatori a funzioni<br />

specifiche <strong>di</strong> Ext2.<br />

Il superblocco è la parte <strong>di</strong> memoria che definisce la struttura dell’intero file system su<br />

<strong>di</strong> un <strong>di</strong>spositivo, e le operazioni che riguardano il file system nella sua interezza (al<br />

contrario delle operazioni che riguardano i singoli file) sono considerate operazioni <strong>di</strong><br />

superblocco. La struttura dati super_operations contiene puntatori a funzione volte<br />

alla manipolazione <strong>degli</strong> inode, del superblocco, e che notificano o cambiano lo stato<br />

del file system nel suo complesso (statfs() e remount()).<br />

20


Ecco com’è definita la struttura super_operations nel file system virtuale (da<br />

linux/fs.h):<br />

struct super_operations {<br />

void (*read_inode) (struct inode *);<br />

int (*notify_change) (struct inode *, struct iattr *);<br />

void (*write_inode) (struct inode *);<br />

void (*put_inode) (struct inode *);<br />

void (*put_super) (struct super_block *);<br />

void (*write_super) (struct super_block *);<br />

void (*statfs) (struct super_block *, struct statfs *,<br />

int);<br />

int (*remount_fs) (struct super_block *, int *, char *);<br />

};<br />

Come si nota, tutti i dati definiti dalla struttura sono puntatori a funzione, che andranno<br />

a contenere l’in<strong>di</strong>rizzo <strong>di</strong> memoria a cui reperire le funzioni che saranno usate dal VFS<br />

per interagire con i dati sulle memorie <strong>di</strong> massa. Sotto viene riportata la <strong>di</strong>chiarazione<br />

più semplice <strong>di</strong> istanza <strong>di</strong> questa struttura nel file system Ext2, che rappresenta le<br />

funzioni che questo file system fornisce allo switch virtuale associandole a<br />

super_operations (ve<strong>di</strong> in fs/ext2/super.c):<br />

static struct super_operations ext2_sops = {<br />

ext2_read_inode,<br />

NULL,<br />

ext2_write_inode,<br />

ext2_put_inode,<br />

ext2_put_super,<br />

ext2_write_super,<br />

ext2_statfs,<br />

ext2_remount<br />

};<br />

Quando un file system viene connesso al <strong>di</strong>sco (ed il modulo che si prende carico <strong>di</strong><br />

questo compito corrisponde al file sorgente fs/super.c), la funzione do_umount()<br />

chiama read_super la quale, a sua volta, termina chiamando (nel caso si stia<br />

utilizzando il file system Ext2) ext2_read_super(), funzione che ritorna il<br />

superblocco al resto del sistema operativo. Il superblocco ritornato contiene un<br />

21


iferimento alla struttura ext2_super_operations; esso include anche molti altri<br />

dati, che possono essere reperiti nello specifico all’interno della definizione <strong>di</strong> struct<br />

super_block in include/linux/fs.h.<br />

2.4 Trovare un file<br />

Una volta che il file system è stato montato correttamente, è possibile accedere ai file<br />

che sono presenti al suo interno. I passi da compiere in questo caso sono due: utilizzare<br />

il nome del file per trovare l’inode da esso puntato, e quin<strong>di</strong> accedere all’inode.<br />

Quando il file system virtuale cerca il nome <strong>di</strong> un file o <strong>di</strong> un <strong>di</strong>rettorio, esso include<br />

nella ricerca anche il suo percorso, operazione che viene fatta automaticamente dal<br />

sistema se il nome dell’oggetto da trovare non è un nome assoluto (cioè non ha inizio<br />

col carattere ‘/’). Per trovare gli oggetti in memoria, il sistema operativo utilizza il<br />

co<strong>di</strong>ce specifico del file-system in cui i dati sono memorizzati; esso esamina il percorso<br />

del file un componente per volta (le varie componenti <strong>di</strong> un percorso sono separate dal<br />

carattere ‘/’). Se il componente preso in esame è una <strong>di</strong>rectory, allora il componente<br />

successivo sarà cercato all’interno del <strong>di</strong>rettorio appena trovato. Ogni componente che<br />

viene identificato, in<strong>di</strong>pendentemente dal fatto che sia un file o un <strong>di</strong>rettorio, ritorna un<br />

“inode number” che lo identifica univocamente, ed attraverso il quale sono resi<br />

<strong>di</strong>sponibili i contenuti dell’oggetto.<br />

Se eventualmente l’oggetto trovato risultasse essere un collegamento simbolico ad un<br />

altro file, allora il VFS inizierebbe una nuova ricerca <strong>di</strong> file, il cui nome è quello<br />

restituito dal collegamento simbolico. Al fine <strong>di</strong> prevenire cicli <strong>di</strong> ricerca ricorsivi<br />

infiniti, viene posto un limite alla profon<strong>di</strong>tà dei collegamenti simbolici: il Kernel<br />

seguirà soltanto un certo numero <strong>di</strong> collegamenti ricorsivi prima <strong>di</strong> interrompere<br />

l’operazione segnalando un errore.<br />

Solo dopo che il VFS ed il file system hanno recuperato il numero <strong>di</strong> inode dal nome del<br />

file (compito svolto dalla routine namei(), in namei.c) è possibile accedere ai<br />

contenuti dell’inode.<br />

La funzione iget() trova e ritorna al sistema operativo l’inode identificato dall’inode<br />

number che gli viene fornito come parametro; la funzione iput() è invece utilizzata<br />

dal sistema per rilasciare l’accesso ad un inode che non deve più essere utilizzato.<br />

Queste funzioni, in linea <strong>di</strong> principio, sono simili a malloc() e free(), con l’unica<br />

22


<strong>di</strong>fferenza che più processi possono mantenere simultaneamente l’accesso ad uno stesso<br />

inode, inoltre viene mantenuto un conteggio dei processi attivi che fanno uso <strong>di</strong><br />

quest’ultimo, in modo da poter conoscere quando l’inode può essere effettivamente<br />

rilasciato dal file-system.<br />

Il file handle 2 che viene passato al co<strong>di</strong>ce <strong>di</strong> una generica applicazione dopo la richiesta<br />

<strong>di</strong> accesso ad un file, è in realtà un numero <strong>di</strong> tipo integer che in<strong>di</strong>ca l’offset<br />

(<strong>di</strong>stanza dall’inizio <strong>di</strong> tabella) sulla tabella dei file per trovare la voce cercata; la voce<br />

della tabella a cui l’applicazione può accedere in questo modo, contiene l’inode number<br />

che era stato cercato dalla funzione namei() fino a quando il file non viene chiuso dal<br />

processo o è il processo stesso a terminare. Quando un qualsiasi processo fa una<br />

qualsiasi operazione su <strong>di</strong> un file utilizzando i “file handle”, essa va in realtà a<br />

manipolare ed interagire con il relativo inode.<br />

2.5 Operazioni sugli inode<br />

L’inode number e la struttura <strong>di</strong> inode stessa non possono essere creati dal VFS stesso,<br />

esse devono essere fornite dal file system, in quanto è questa ultima parte <strong>di</strong> co<strong>di</strong>ce che<br />

gestisce l’interazione del resto del sistema operativo con i dati presenti in memoria<br />

secondaria.<br />

Di seguito viene riportato in che modo il file system virtuale ottiene il numero <strong>di</strong> inode<br />

partendo dal nome del file a cui si vuole accedere.<br />

Il VFS inizia la ricerca della prima <strong>di</strong>rectory contenuta nel percorso, che dal suo nome<br />

ricava il relativo inode; a questo punto l’inode è utilizzato per trovare il <strong>di</strong>rettorio 3<br />

successivo contenuto nel percorso, e così via fino ad esaurire i componenti del percorso.<br />

Quando in questo modo la ricerca raggiunge la fine, il sistema ricava l’inode relativo al<br />

file o al <strong>di</strong>rettorio che stava cercando. Una ricerca <strong>di</strong> questo tipo può iniziare solo se il<br />

VFS ha a <strong>di</strong>sposizione un inode <strong>di</strong> partenza, cosa che viene fornita al momento del<br />

montaggio del file system dal superblocco, attraverso un puntatore ad inode contenuto<br />

in quest’ultimo chiamato s_mounted, che contiene un riferimento ad una struttura <strong>di</strong><br />

tipo inode per l’intero file system. Questo inode è allocato quando il file system viene<br />

2 Un file handle è, in genere, una struttura dati che permette ad un’applicazione <strong>di</strong> alto livello <strong>di</strong> accedere<br />

ed eventualmente mo<strong>di</strong>ficare i dati presenti in un file; è il meccanismo con cui il co<strong>di</strong>ce del sistema<br />

operativo permette alle applicazioni <strong>di</strong> utilizzare i dati nelle memorie, ed è fornito dal sistema operativo a<br />

seguito della richiesta <strong>di</strong> accesso ad un file da parte <strong>di</strong> un’applicazione.<br />

3 Ve<strong>di</strong> il paragrafo relativo alla gestione dei <strong>di</strong>rettori nel capitolo su Ext3.<br />

23


montato, e rimosso dalla memoria all’atto dello smontaggio. L’inode s_mounted<br />

rappresenta il <strong>di</strong>rettorio <strong>di</strong> root nei file system Linux, e quin<strong>di</strong> anche in Ext2 ed Ext3, e<br />

partendo da questo possono essere ritrovati gli altri inode presenti in memoria.<br />

Ogni inode ha inoltre un puntatore ad una struttura a sua volta composta <strong>di</strong> puntatori a<br />

funzione, struttura che prende il nome <strong>di</strong> inode_operations. L’elemento chiamato<br />

lookup() è quello che viene usato dal VFS per recuperare un altro inode presente sullo<br />

stesso file system. In generale un file system ha soltanto una funzione lookup(), che<br />

viene utilizzata per ogni inode presente su <strong>di</strong> esso, ma è anche possibile avere <strong>di</strong>verse<br />

funzioni <strong>di</strong> lookup() che possono sono utilizzate su un’unica partizione; il file system<br />

proc consente questa molteplicità perché su <strong>di</strong> esso esistono <strong>di</strong>rettori che hanno funzioni<br />

<strong>di</strong>fferenti. La struttura inode_operations è la seguente (definita in linux/fs.h).<br />

struct inode_operations {<br />

struct file_operations * default_file_ops;<br />

int (*create) (struct inode *,const char *,int,int,struct<br />

inode **);<br />

int (*lookup) (struct inode *,const char *,int,struct inode<br />

**);<br />

int (*link) (struct inode *,struct inode *,const char<br />

*,int);<br />

int (*unlink) (struct inode *,const char *,int);<br />

int (*symlink) (struct inode *,const char *,int,const char<br />

*);<br />

int (*mk<strong>di</strong>r) (struct inode *,const char *,int,int);<br />

int (*rm<strong>di</strong>r) (struct inode *,const char *,int);<br />

int (*mknod) (struct inode *,const char *,int,int,int);<br />

int (*rename) (struct inode *,const char *,int,struct inode<br />

*,const char *,int);<br />

int (*readlink) (struct inode *,char *,int);<br />

int (*follow_link) (struct inode *,struct inode<br />

*,int,int,struct inode **);<br />

int (*readpage) (struct inode *, struct page *);<br />

int (*writepage) (struct inode *, struct page *);<br />

int (*bmap) (struct inode *,int);<br />

void (*truncate) (struct inode *);<br />

int (*permission) (struct inode *, int);<br />

int (*smap) (struct inode *,int); };<br />

24


La maggior parte <strong>di</strong> queste funzioni è <strong>di</strong>rettamente riferita ad una precisa chiamata <strong>di</strong><br />

sistema (system call).<br />

In Ext2 e in Ext3, <strong>di</strong>rettori, file e link simbolici hanno <strong>di</strong>fferenti istanze <strong>di</strong><br />

inode_operations (cosa verificata anche in molti altri sistemi operativi), esse sono<br />

reperibili nei seguenti file:<br />

fs/ext2/<strong>di</strong>r.c contiene la struttura ext2_<strong>di</strong>r_inode_operations<br />

fs/ext2/file.c contiene la struttura ext2_file_inode_operation<br />

fs/ext2/symlink.c contiene ext2_symlink_inod_operations<br />

Esistono poi <strong>di</strong>verse system call riguardanti file (e <strong>di</strong>rettori) che non sono comprese<br />

all’interno della struttura inode_operations, e si trovano invece all’interno della<br />

struttura file_operations: struttura che contiene tutte le funzioni che operano<br />

specificamente sui file e sul loro contenuto, piuttosto che sugli inode; la <strong>di</strong>chiarazione<br />

della struttura è riportata <strong>di</strong> seguito.<br />

struct file_operations {<br />

int (*lseek) (struct inode *, struct file *, off_t, int);<br />

int (*read) (struct inode *, struct file *, char *, int);<br />

int (*write) (struct inode *, struct file *, const char *,<br />

int);<br />

int (*read<strong>di</strong>r) (struct inode *, struct file *, void *,<br />

fill<strong>di</strong>r_t);<br />

int (*select) (struct inode *, struct file *, int,<br />

select_table *);<br />

int (*ioctl) (struct inode *, struct file *, unsigned int,<br />

unsigned long);<br />

int (*mmap) (struct inode *, struct file *, struct<br />

vm_area_struct *);<br />

int (*open) (struct inode *, struct file *);<br />

void (*release) (struct inode *, struct file *);<br />

int (*fsync) (struct inode *, struct file *);<br />

int (*fasync) (struct inode *, struct file *, int);<br />

int (*check_me<strong>di</strong>a_change) (kdev_t dev);<br />

int (*revalidate) (kdev_t dev);<br />

};<br />

25


Al <strong>di</strong> là <strong>degli</strong> esempi presentati in questo capitolo, dunque, secondo il modello VFS, il<br />

ruolo <strong>di</strong> uno specifico file system è quello <strong>di</strong> fornire un superblocco, una lista <strong>di</strong> inode<br />

(uno per ogni file presente), e <strong>di</strong> fornire un’implementazione che supporti le operazioni<br />

richieste dal sistema operativo (come appunto apertura, lettura, scrittura su file), cosa<br />

che viene fatta attraverso le strutture e le routine prese in analisi in questo capitolo. Le<br />

strutture file_operations, inode_operations e super_operations, una volta<br />

che il file system viene montato, andranno a contenere tutti i riferimenti che servono al<br />

sistema operativo per reperire le funzioni che implementano le operazioni sui file per<br />

quel determinato tipo <strong>di</strong> file system.<br />

26


3 Il File system Ext3<br />

In questo capitolo sarà preso in analisi il file system ext3, <strong>di</strong>retto <strong>di</strong>scendente <strong>di</strong> ext2,<br />

dal quale ere<strong>di</strong>ta gran parte del suo co<strong>di</strong>ce sorgente e lo mantiene in sostanza immutato.<br />

Questo filesystem, infatti, rappresenta un adeguamento alle necessità o<strong>di</strong>erne <strong>di</strong> un file<br />

system già molto stabile e versatile, a cui sono state aggiunte parti <strong>di</strong> co<strong>di</strong>ce per<br />

aumentarne le funzionalità, la più importante delle quali, come si vedrà in seguito, è<br />

l’implementazione <strong>di</strong> un journaling layer. Il modo con cui ext3 interagisce con i livelli<br />

più alti del sistema operativo è esattamente lo stesso che viene utilizzato in ext2, l’unica<br />

cosa che cambia, ovviamente, sono i <strong>di</strong>rettori in cui i sorgenti dei due file system<br />

possono essere reperiti.<br />

3.1 Struttura fisica<br />

Il file system è costituito <strong>di</strong> tanti gruppi <strong>di</strong> blocchi (block groups) <strong>di</strong> memoria<br />

secondaria, che non hanno però necessariamente una corrispondenza <strong>di</strong>retta con i<br />

blocchi fisici sulla memoria <strong>di</strong> massa, specialmente da quando i <strong>di</strong>schi rigi<strong>di</strong> sono stati<br />

ottimizzati per le operazioni <strong>di</strong> lettura sequenziale e quin<strong>di</strong> tendono a nascondere la loro<br />

struttura fisica al sistema operativo che li gestisce[vfs/ext2].<br />

La struttura fisica del file system è rappresentata nella figura 3.1.<br />

BOOT Sector<br />

Superblocco<br />

Gruppo <strong>di</strong><br />

blocchi 1<br />

Gruppo <strong>di</strong><br />

blocchi 2<br />

Gruppo <strong>di</strong><br />

blocchi 3<br />

Gruppo <strong>di</strong><br />

blocchi 4<br />

Gruppo <strong>di</strong><br />

blocchi n<br />

0 1024<br />

2048<br />

Copia del Descrittore<br />

Superblocco <strong>di</strong> gruppo<br />

Block<br />

Bitmap<br />

Inode<br />

Bitmap<br />

Parte della<br />

inode<br />

Table<br />

Blocchi<br />

Dati<br />

Figura 3.1: Organizzazione della memoria nei file system Ext3.<br />

27


Il punto <strong>di</strong> partenza per il file system è sempre il superblocco, che è una struttura dati <strong>di</strong><br />

1024 bytes allocata ad una <strong>di</strong>stanza <strong>di</strong> 1024 bytes dall’inizio della partizione; il resto<br />

della memoria viene <strong>di</strong>viso in vari gruppi <strong>di</strong> blocchi.<br />

Ogni gruppo <strong>di</strong> blocchi contiene una copia delle informazioni fondamentali <strong>di</strong> controllo<br />

della memoria (superblocco ed i descrittori <strong>di</strong> file system), ed inoltre contiene una parte<br />

del file system: una block bitmap, una inode bitmap, una parte della tabella <strong>degli</strong> inode<br />

(inode table) ed i blocchi dati.<br />

La tabella <strong>degli</strong> inode non viene quin<strong>di</strong> memorizzata tutta in un particolare settore <strong>di</strong><br />

memoria, ma è sud<strong>di</strong>visa nei vari block groups in modo che ogni file si trovi nei limiti<br />

del possibile nello stesso gruppo <strong>di</strong> blocchi del corrispondente inode, così da ridurre al<br />

minimo gli spostamenti delle testine del <strong>di</strong>sco durante le richieste d’accesso ai contenuti<br />

<strong>di</strong> un file.<br />

L’utilizzo dei gruppi <strong>di</strong> blocchi rappresenta un vantaggio anche in termini <strong>di</strong> affidabilità<br />

del sistema, visto che la replicazione delle strutture dati <strong>di</strong> controllo in ogni block group<br />

consente un facile recupero del superblocco nel caso questo risulti essere corrotto o<br />

danneggiato. La presenza della copia del superblocco in ogni gruppo può essere<br />

impostata attraverso l’uso della flag SPARSE_SUPERBLOCK, che a seconda del suo<br />

stato notifica o meno la presenza del superblocco all’interno <strong>di</strong> un block group. In<br />

questo modo è possibile <strong>di</strong>minuire lo spreco <strong>di</strong> memoria evitando <strong>di</strong> mantenere una<br />

copia del superblocco in ogni gruppo, memorizzandola soltanto all’interno <strong>di</strong> alcuni.<br />

Il descrittore <strong>di</strong> gruppo contiene le informazioni riguardanti la struttura del gruppo <strong>di</strong><br />

blocchi a cui appartiene, ed è una struttura dati formata dai seguenti elementi:<br />

Nome campo<br />

bg_block_bitmap<br />

bg_inode_bitmap<br />

tipo <strong>di</strong><br />

dato<br />

ULONG<br />

ULONG<br />

commento<br />

Contiene l’in<strong>di</strong>rizzo del blocco in cui è<br />

immagazzinata la block bitmap per<br />

questo gruppo.<br />

Contiene l’in<strong>di</strong>rizzo in cui è<br />

memorizzata l’inode bitmap per questo<br />

gruppo.<br />

28


g_inode_table<br />

ULONG<br />

Contiene l’in<strong>di</strong>rizzo in cui è<br />

memorizzata la inode table per questo<br />

gruppo.<br />

bg_free_blocks_count USHORT<br />

bg_free_inodes_count USHORT<br />

Conteggio dei blocchi liberi <strong>di</strong> questo<br />

gruppo<br />

Conteggio <strong>degli</strong> inode liberi <strong>di</strong> questo<br />

gruppo<br />

bg_used_<strong>di</strong>rs_count<br />

USHORT<br />

Numero <strong>di</strong> inode del gruppo che sono<br />

<strong>di</strong>rectory<br />

bg_pad USHORT pad<strong>di</strong>ng<br />

bg_reserved ULONG[3] pad<strong>di</strong>ng<br />

La <strong>di</strong>mensione del descrittore può essere calcolata come<br />

(sizeof(Ext3_group)*numero_<strong>di</strong>_gruppi)/<strong>di</strong>mensione_blocco.<br />

La block bitmap è una serie <strong>di</strong> bit che in<strong>di</strong>ca quali blocchi del gruppo sono stati allocati<br />

e quali no (ogni blocco è rappresentato da un bit) ed, analogamente, l’inode bitmap<br />

rappresenta quali voci, della parte <strong>di</strong> inode table presente nel gruppo, sono state allocate<br />

ad inode e quanti “slot” rimangono liberi per l’allocazione <strong>di</strong> nuovi file. La <strong>di</strong>mensione<br />

in byte della block bitmap, per esempio, può essere calcolata nel seguente modo:<br />

(blochi_per_gruppo/8)/<strong>di</strong>mensione_blocco<br />

arrotondando per eccesso entrambe le <strong>di</strong>visioni [spec].<br />

In Ext3 le <strong>di</strong>rectory sono gestite come Linked Lists 4 (liste collegate) <strong>di</strong> lunghezza<br />

variabile: ogni elemento della lista contiene l’inode number, la <strong>di</strong>mensione<br />

dell’elemento stesso, la lunghezza del nome ed il nome del file stesso (in Linux sia file<br />

che <strong>di</strong>rectory vengono trattati dal sistema operativo come files). Utilizzando questo<br />

meccanismo che consente elementi <strong>di</strong> lista <strong>di</strong> lunghezza variabile è possibile utilizzare<br />

lunghi nomi <strong>di</strong> file senza sprecare spazio nelle <strong>di</strong>rectory.<br />

La struttura <strong>di</strong> un elemento presente in una <strong>di</strong>rectory (<strong>di</strong>rectory entry) è mostrata nella<br />

tabella 3.1:<br />

4 Per una spiegazione esaustiva sul significato <strong>di</strong> linked list si vada all’appen<strong>di</strong>ce C.<br />

29


Inode number Dimensione elemento Lunghezza del nome Nome del file<br />

Tabella 3.1: Struttura <strong>di</strong> una <strong>di</strong>rectory entry.<br />

Come esempio, la tabella 3.2 mostrerà la struttura <strong>di</strong> un <strong>di</strong>rettorio contenente i files:<br />

file1, long_file_name, e f2.<br />

i1 16 05 file1<br />

i2 40 14 long_file_name<br />

i3 12 02 f2<br />

Tabella 3.2: esempio <strong>di</strong> <strong>di</strong>rectory Ext3.<br />

La <strong>di</strong>mensione dei blocchi <strong>di</strong> memoria non è fissa, ma può essere mo<strong>di</strong>ficata<br />

dall’amministratore <strong>di</strong> sistema utilizzando l’utility tune2fs, che permette <strong>di</strong> sceglierne la<br />

<strong>di</strong>mensione (tipicamente 1024, 2048 o 4096 bytes). L’utilizzo <strong>di</strong> gran<strong>di</strong> blocchi <strong>di</strong><br />

memoria, come già in precedenza affermato, velocizza le operazioni <strong>di</strong> I/O ma allo<br />

stesso tempo comporta uno spreco <strong>di</strong> spazio su <strong>di</strong>sco, in quanto questo file system non<br />

permette che uno stesso blocco sia utilizzato da più file, lasciando così l’ultimo blocco<br />

assegnato ad ogni file statisticamente mezzo vuoto.<br />

3.2 Gestione delle <strong>di</strong>rectory<br />

Il file system Ext3 ere<strong>di</strong>ta dal suo predecessore una meccanismo <strong>di</strong> gestione dei <strong>di</strong>rettori<br />

basato sulle linked lists, come spiegato nel paragrafo precedente. Un sistema <strong>di</strong> questo<br />

tipo, tuttavia, non consente <strong>di</strong> utilizzare tutti gli algoritmi <strong>di</strong> bilanciamento e<br />

ottimizzazione della ricerca che sarebbero possibili utilizzando una struttura ad albero,<br />

ed allo stesso tempo non rende il file system immune da per<strong>di</strong>te <strong>di</strong> dati dovute alla<br />

possibile corruzione <strong>di</strong> no<strong>di</strong> vicini alla ra<strong>di</strong>ce, che potrebbero rendere irraggiungibile<br />

buona parte dei files presenti in memoria. Per ovviare a questi problemi è stato<br />

sviluppato, e reso <strong>di</strong>sponibile a partire dalla release 2.4 del kernel Linux, un sistema <strong>di</strong><br />

organizzazione ad albero bilanciato per i <strong>di</strong>rettori chiamato “HTree” [HTree].<br />

Questo sistema identifica ogni blocco con una hash key <strong>di</strong> 32 bit, ed ogni chiave fa<br />

riferimento ad una serie d’oggetti immagazzinati in una foglia dell’albero. Il fatto che i<br />

30


iferimenti interni, che sono anch’essi memorizzati all’interno <strong>di</strong> blocchi <strong>di</strong> memoria,<br />

siano <strong>di</strong> lunghezza fissa <strong>di</strong> 8 byte, garantisce un elevato fanout per l’albero (utilizzando<br />

blocchi <strong>di</strong> memoria <strong>di</strong> 4KB possono essere memorizzati più <strong>di</strong> 500 riferimenti per<br />

blocco); e i due livelli <strong>di</strong> no<strong>di</strong>, <strong>di</strong> cui è al massimo composto l’albero, sono sufficienti<br />

per poter memorizzare più <strong>di</strong> 16 milioni <strong>di</strong> file che hanno una lunghezza del nome <strong>di</strong> 52<br />

caratteri.<br />

Questo sistema è stato stu<strong>di</strong>ato in modo da garantire una perfetta compatibilità<br />

all’in<strong>di</strong>etro, e funziona in modo parallelo al sistema d’in<strong>di</strong>rizzamento lineare basato<br />

sulle liste dati; il sistema, infatti, utilizza la struttura HTree soltanto per or<strong>di</strong>nare ed<br />

avere la possibilità <strong>di</strong> fare ricerche veloci all’interno dell’albero, ma per eseguire le<br />

operazioni sui files esso usa il tra<strong>di</strong>zionale riferimento ad inodes e blocchi <strong>di</strong> memoria.<br />

Per rendere tutto ciò possibile, le foglie dell’albero sono state progettate in modo da<br />

essere identiche ai blocchi contenenti le <strong>di</strong>rectory utilizzate nel vecchio meccanismo, ed<br />

i blocchi interni contenenti strutture dati <strong>di</strong> 8 byte appaiono ai kernel che non<br />

supportano gli HTree come <strong>di</strong>rectory entries cancellati.<br />

Un altro vantaggio dovuto a quest’attenzione alla compatibilità viene dal fatto che in<br />

questo modo la struttura ad albero <strong>di</strong>viene estremamente robusta: nel caso in cui uno dei<br />

no<strong>di</strong> interni dovesse essere corrotto, il kernel potrebbe rintracciare i vari files e le<br />

<strong>di</strong>rectory utilizzando il sistema tra<strong>di</strong>zionale basato sulle linked lists.<br />

L’utilizzo <strong>di</strong> questo algoritmo d’or<strong>di</strong>namento ha portato ad un aumento <strong>di</strong> prestazioni,<br />

nell’accesso a <strong>di</strong>rectory contenenti un gran numero <strong>di</strong> files, fino a 50 o 100 volte<br />

rispetto ai tempi impiegati dalle precedenti versioni che non utilizzano questo<br />

meccanismo; e l’unico svantaggio viene notato quando si usa il comando read<strong>di</strong>r(), che<br />

ritorna i file presenti in un <strong>di</strong>rettorio or<strong>di</strong>nati secondo la propria hash key e provoca una<br />

lettura <strong>degli</strong> inode contenenti i dati dei file in or<strong>di</strong>ne del tutto casuale, senza ottimizzare<br />

quin<strong>di</strong> i salti delle testine del <strong>di</strong>sco rigido tra un gruppo <strong>di</strong> blocchi e l’altro.<br />

Una soluzione a questo problema che è tuttora allo stu<strong>di</strong>o è quella <strong>di</strong> associare<br />

<strong>di</strong>rettamente ad ogni inode la relativa hash key, e raggruppare quin<strong>di</strong> gli inodes in<br />

memoria per chiave; più nello specifico, la soluzione a cui si sta lavorando, è quella <strong>di</strong><br />

allocare ogni nuovo inode all’interno <strong>di</strong> un possibile range <strong>di</strong> inode liberi d’ampiezza<br />

<strong>di</strong>pendente dalla <strong>di</strong>mensione del <strong>di</strong>rettorio in cui il file si trova, e quin<strong>di</strong> scegliere un<br />

inode libero per l’allocazione in modo <strong>di</strong>pendente dalla hash key del file.<br />

31


3.3 Attributi estesi ed ACL<br />

In questa sezione si <strong>di</strong>scuterà <strong>di</strong> come sono implementate le access control lists in<br />

Linux, ed in particolare sul file system ext3 [POSIX-ACL].<br />

Per via <strong>di</strong> altre estensioni del kernel che traggono vantaggio dal poter associare parti <strong>di</strong><br />

informazioni ai files, Linux e la maggior parte <strong>degli</strong> altri sistemi UNIX-like implementa<br />

le ACL sottoforma <strong>di</strong> attributi estesi (Extended Attributes, o più brevemente EAs) dei<br />

files.<br />

Un attributo esteso è un pezzo d’informazione relativa ad un file (esterna però al file,<br />

non facente parte del suo contenuto, che è utilizzato dai processi utente) che non viene<br />

memorizzata all’interno del relativo descrittore, ma è allocata in parti ben definite del<br />

<strong>di</strong>sco; più precisamente esso è una coppia nome-valore associata permanentemente ad<br />

un file, simile ad una variabile ambiente <strong>di</strong> un processo.<br />

Per rendere <strong>di</strong>sponibili i vantaggi delle ACL ai processi utente, il sistema operativo<br />

mette a <strong>di</strong>sposizione delle system calls specifiche per gli attributi estesi, che fungono<br />

quin<strong>di</strong> da interfaccia tra kernel e processi.<br />

La scelta <strong>di</strong> progetto più facile e lungimirante per implementare gli attributi estesi, è<br />

quella <strong>di</strong> associare ad ogni file una <strong>di</strong>rectory nascosta contenente un file per ogni<br />

attributo relativo (file che ovviamente contiene il valore dell’attributo), ma una<br />

soluzione <strong>di</strong> questo tipo comporterebbe un grande spreco <strong>di</strong> spazio su <strong>di</strong>sco per<br />

l’allocazione <strong>di</strong> nuovi blocchi per file e <strong>di</strong>rettori, oltre che una per<strong>di</strong>ta <strong>di</strong> tempo dovuta<br />

alle operazioni <strong>di</strong> accesso ad ogni singolo file-attributo; ecco perché la maggior parte<br />

dei sistemi operativi utilizza meto<strong>di</strong> implementativi <strong>di</strong>fferenti.<br />

Come definito nel kernel Linux, ogni inode contiene un campo chiamato i_file_acl;<br />

se questo campo non è uguale a zero, esso contiene allora il numero del blocco <strong>di</strong><br />

memoria in cui sono memorizzati gli attributi estesi associati all’inode: attributi che<br />

in<strong>di</strong>pendentemente dal tipo sono composti da un nome (che identifica l’attributo stesso)<br />

ed un valore associato, inoltre tutti gli attributi estesi associati ad uno stesso inode<br />

devono essere memorizzati all’interno dello stesso blocco. La figura 3.2 mostra in che<br />

modo il sistema associa agli inode gli attributi estesi: <strong>di</strong>versi inode possono con<strong>di</strong>videre<br />

lo stesso blocco attributi (se hanno uguali attributi), ma i blocchi contenenti gli attributi<br />

estesi non si trovano necessariamente nello stesso gruppo <strong>di</strong> blocchi in cui è<br />

memorizzato il relativo inode.<br />

32


I1: i_file_acl= 216<br />

I2: i_file_acl=218<br />

I3: i_file_acl=216<br />

I4 : i_file_acl=NULL<br />

I5:….<br />

Inode Table<br />

Data<br />

Blocks<br />

Blocco dati 216,<br />

che memorizza<br />

attributi estesi<br />

Data<br />

Block<br />

217<br />

Blocco dati 218,<br />

che memorizza<br />

attributi estesi<br />

Data<br />

Blocks<br />

Figura 3.2: Uso <strong>di</strong> i_file_acl per memorizzare il riferimento al blocco contenente gli attributi.<br />

Per ottimizzare l’utilizzo dello spazio su <strong>di</strong>sco, il file system consente a <strong>di</strong>versi inode<br />

che hanno identici attributi estesi <strong>di</strong> con<strong>di</strong>videre lo stesso blocco attributi su <strong>di</strong>sco; il<br />

numero <strong>di</strong> inode che fanno riferimento ad un unico blocco è controllato attraverso un<br />

contatore <strong>di</strong> riferimenti ad inode presente nel blocco stesso. La con<strong>di</strong>visione dei blocchi<br />

attributi è implementata in modo trasparente rispetto all’utente: Ext3 tiene<br />

automaticamente traccia dei blocchi attributi recentemente aperti, ed inoltre utilizza una<br />

tabella che tiene conto dei blocchi or<strong>di</strong>nandoli per numero e per riassunto (checksum)<br />

dei contenuti (tabella che è implementata come una hash table <strong>di</strong> doppie linked lists, per<br />

numero e contenuti). Un singolo blocco attributi può essere con<strong>di</strong>viso da un numero<br />

massimo stabilito <strong>di</strong> 1024 inodes, questo per limitare il danno causato in caso <strong>di</strong><br />

danneggiamento del singolo blocco, che si ripercuoterebbe su <strong>di</strong> un numero limitato <strong>di</strong><br />

files. Quando vengono mo<strong>di</strong>ficati gli attributi <strong>di</strong> un file che utilizza la con<strong>di</strong>visione del<br />

blocco, viene utilizzato un meccanismo <strong>di</strong> copy-on-write per allocare un nuovo blocco<br />

attributi da associare al file in questione; questo salvo che i nuovi attributi del file non<br />

corrispondano a quelli presenti su <strong>di</strong> un blocco già esistente che può essere con<strong>di</strong>viso.<br />

L’implementazione corrente <strong>di</strong> questi attributi richiede che tutti quelli associati ad uno<br />

stesso file siano contenuti all’interno <strong>di</strong> un unico blocco, cosa che va ad influenzare la<br />

<strong>di</strong>mensione <strong>di</strong> un singolo attributo, visto che la <strong>di</strong>mensione dei blocchi, in ext3 può<br />

essere <strong>di</strong> 1, 2, o 4KB.<br />

Il grosso limite <strong>di</strong> questo tipo <strong>di</strong> implementazioni viene alla luce nelle partizioni in cui<br />

tutti i file tendono ad avere set <strong>di</strong>fferenti <strong>di</strong> attributi, all’interno delle quali si ha un<br />

notevole spreco <strong>di</strong> spazio su <strong>di</strong>sco, sia per i blocchi in sè che per memorizzare la tabella<br />

riassuntiva utilizzata dal sistema. Inoltre la memorizzazione <strong>degli</strong> attributi <strong>di</strong> un nuovo<br />

file, in un caso <strong>di</strong> questo tipo, comporterebbe un grosso carico <strong>di</strong> lavoro eseguito dal<br />

sistema per scorrere l’intera hash table per cercare un blocco corrispondente e,<br />

33


eventualmente, allocare una nuova voce in tabella nel caso non si trovassero blocchi da<br />

poter mettere in con<strong>di</strong>visione.<br />

3.4 Quote <strong>di</strong>sco in Ext3<br />

Come già accennato nel capitolo 2, un moderno file system deve provvedere alla<br />

gestione dello spazio su <strong>di</strong>sco tra i vari utenti.<br />

Il kernel Linux definisce un’interfaccia standard, attraverso il VFS, con la quale<br />

interagisce con tutti i file system che supportano le quote <strong>di</strong>sco, che sono in ogni caso<br />

trattate alla stregua <strong>di</strong> tutti gli altri dati in memoria: queste sono memorizzate all’interno<br />

<strong>di</strong> appositi file (appartenenti a blocchi <strong>di</strong> memoria contrad<strong>di</strong>stinti da un apposito header,<br />

descrittore), chiamati quota file, ai quali si può accedere attraverso un inode. Il<br />

superblocco contiene sempre un riferimento ai quota file, in modo che il sistema<br />

operativo possa avviare il meccanismo <strong>di</strong> gestione delle quote al momento<br />

dell’inizializzazione del file system.<br />

I file che il kernel mette a <strong>di</strong>sposizione per gestire questa funzione, si trovano nel<br />

<strong>di</strong>rettorio /include/linux dei co<strong>di</strong>ci sorgenti del kernel, e sono i seguenti:<br />

quota.h: definisce le strutture dati ed i parametri che sono utilizzati nelle<br />

comunicazioni tra il VFS ed i file system, in particolare viene <strong>di</strong>chiarata la<br />

struttura struct dquot_operations che contiene tutte le operazioni<br />

necessarie per la gestione del file quota su <strong>di</strong>sco.<br />

quotaio_v1.h e quotaio_v2.h: definiscono le strutture che il VFS utilizza per<br />

definire il formato del quota file e dei blocchi contenenti i dati. In particolare<br />

sono contenute le strutture che fungono da descrittore dei quota file e dei quota<br />

block.<br />

quotaops.h: contiene le definizioni delle operazioni eseguibili sulle quote <strong>di</strong>sco.<br />

Dopo che il sistema quote è stato configurato, le routine contenute in questa<br />

libreria associano le funzioni <strong>di</strong> gestione delle quote del VFS al corretto co<strong>di</strong>ce<br />

sorgente.<br />

Il file system ext3 definisce il proprio meccanismo <strong>di</strong> gestione delle quote <strong>di</strong>sco<br />

all’interno del file /fs/Ext3/super.c contenuto nei sorgenti del kernel Linux: in esso sono<br />

ridefinite le strutture dquot_operations e quotactl_ops (<strong>di</strong>chiarate anche in<br />

quota.h) in modo da associare ad ogni funzione puntata dalle strutture la specifica<br />

funzione del file system, come riportato <strong>di</strong> seguito:<br />

34


static struct dquot_operations ext3_quota_operations = {<br />

.initialize = ext3_dquot_initialize,<br />

.drop = ext3_dquot_drop,<br />

.alloc_space = dquot_alloc_space,<br />

.alloc_inode = dquot_alloc_inode,<br />

.free_space = dquot_free_space,<br />

.free_inode = dquot_free_inode,<br />

.transfer = dquot_transfer,<br />

.write_dquot = ext3_write_dquot,<br />

.acquire_dquot = ext3_acquire_dquot,<br />

.release_dquot = ext3_release_dquot,<br />

.mark_<strong>di</strong>rty = ext3_mark_dquot_<strong>di</strong>rty,<br />

.write_info = ext3_write_info<br />

};<br />

static struct quotactl_ops ext3_qctl_operations = {<br />

.quota_on = ext3_quota_on,<br />

.quota_off = vfs_quota_off,<br />

.quota_sync = vfs_quota_sync,<br />

.get_info = vfs_get_dqinfo,<br />

.set_info = vfs_set_dqinfo,<br />

.get_dqblk = vfs_get_dqblk,<br />

.set_dqblk = vfs_set_dqblk<br />

};<br />

In linea generale questo file system mette a <strong>di</strong>sposizione per default all’amministratore<br />

<strong>di</strong> sistema (root) il 5% dei blocchi in cui è sud<strong>di</strong>viso il <strong>di</strong>sco, in modo da consentire<br />

all’amministratore <strong>di</strong> poter uscire facilmente da eventuali situazioni in cui i processi<br />

utente riempiono il <strong>di</strong>sco rigido.<br />

Questo tipo <strong>di</strong> gestione delle quote deriva <strong>di</strong>rettamente da UNIX, e per questo motivo<br />

viene utilizzato come criterio base per molti file system Linux [vfs/ext2].<br />

Per gestire le quote <strong>di</strong>sco tra i vari utenti, questo file system utilizza gli strumenti che<br />

sono messi a <strong>di</strong>sposizione dal sistema operativo che, come detto in precedenza, riesce in<br />

questo modo ad utilizzare un sistema <strong>di</strong> <strong>di</strong>alogo con i vari file system del tutto<br />

trasparente agli occhi dell’utente (attraverso il VFS).<br />

35


3.5 Journaling<br />

La vera innovazione <strong>di</strong> questo file system rispetto alla sua versione precedente (ext2)<br />

consiste nell’aver implementato un meccanismo <strong>di</strong> registrazione delle operazioni <strong>di</strong> I/O<br />

su <strong>di</strong>sco all’interno <strong>di</strong> un apposito file che funge appunto da registro (journal, giornale,<br />

<strong>di</strong>ario); in questo modo <strong>di</strong>venta possibile garantire l’integrità del file system in caso <strong>di</strong><br />

crash del sistema operativo o <strong>di</strong> blackout improvvisi [JFS].<br />

Ext3 implementa uno strato (layer) <strong>di</strong> co<strong>di</strong>ce completamene in<strong>di</strong>pendente dal software<br />

sottostante, chiamato journaling layer o JFS, che funziona da interfaccia tra il file<br />

system vero e proprio ed il resto del sistema operativo: esso registra tutte le operazioni<br />

<strong>di</strong> scrittura che avvengono su <strong>di</strong>sco e le raggruppa in una serie <strong>di</strong> transazioni<br />

(transactions) sul proprio registro, che saranno successivamente scritte definitivamente<br />

nel file system. Più in particolare, il file system vero e proprio non comunica con il<br />

journaling layer se non attraverso le transazioni, ciascuna delle quali rappresenta<br />

un’operazione <strong>di</strong> aggiornamento dei dati atomica, cioè un’operazione <strong>di</strong> aggiornamento<br />

che viene eseguita interamente oppure non viene eseguita. La struttura e<br />

l’organizzazione del sistema <strong>di</strong> journaling sono esemplificate nella figura 3.3: il<br />

journaling layer cattura soltanto le operazioni <strong>di</strong> scrittura in memoria, e le raggruppa in<br />

transazioni all'interno del journal file, per poi scriverle in memoria. Le letture da <strong>di</strong>sco,<br />

invece, avvengono in modo tra<strong>di</strong>zionale, senza coinvolgere parti <strong>di</strong> co<strong>di</strong>ce aggiuntive.<br />

Lettura dei dati<br />

Applicativi <strong>di</strong> alto<br />

livello<br />

Scrittura Dati<br />

Riversamento in memoria<br />

delle transazioni<br />

Journaling layer<br />

Scrittura Transazioni<br />

Blocchi dati del File system<br />

Transazione<br />

1<br />

Transazione<br />

2<br />

Transazione<br />

3<br />

Transazione<br />

n<br />

Journal File<br />

Figura 3.3: Organizzazione del sistema <strong>di</strong> journaling <strong>di</strong> Ext3.<br />

L’atomicità delle operazioni <strong>di</strong> scrittura del file system, e quin<strong>di</strong> l’integrità dello stesso,<br />

è garantita dal fatto che il JFS registra al suo interno tutte le operazioni <strong>di</strong> scrittura<br />

richieste dal sistema operativo: in caso <strong>di</strong> blackout quin<strong>di</strong> l’unica parte <strong>di</strong> <strong>di</strong>sco che<br />

36


ischia <strong>di</strong> non essere consistente per il sistema operativo è il registro delle transazioni,<br />

che però viene gestito autonomamente con opportuni algoritmi dal JFS.<br />

L’atomicità della scrittura <strong>di</strong> una singola transazione dal journal al file system è invece<br />

assicurata dal fatto che le transazioni sono progettate in maniera e con una <strong>di</strong>mensione<br />

tale da poter garantire la loro intera scrittura su <strong>di</strong>sco anche in caso <strong>di</strong> blackout: con i<br />

<strong>di</strong>schi rigi<strong>di</strong> attualmente in commercio, anche se venisse a mancare l’alimentazione, la<br />

testina <strong>di</strong> scrittura potrebbe completare l’operazione recuperando l’energia necessaria<br />

dall’inerzia <strong>di</strong> rotazione del supporto <strong>di</strong> memoria.<br />

Ext3 lascia all’utente la possibilità <strong>di</strong> scegliere in che modo memorizzare il registro<br />

transazioni, occupandosi poi della sua gestione una volta scelta la collocazione: il<br />

registro può essere memorizzato come file del file system sfruttando la normale<br />

organizzazione <strong>degli</strong> inode, oppure può trovarsi all’interno <strong>di</strong> un range <strong>di</strong> blocchi <strong>di</strong><br />

memoria prestabilito e ad esso riservati; è inoltre possibile che il journal file si trovi<br />

all’interno del file system che esso gestisce, ma magari su <strong>di</strong> un altro <strong>di</strong>sco rigido o su<br />

una partizione <strong>di</strong>fferente, è anche possibile fare in modo che <strong>di</strong>verse partizioni<br />

formattate in ext3 con<strong>di</strong>vidano il medesimo journal file, della cui gestione si occuperà<br />

soltanto il relativo JFS.<br />

Ogni transazione scritta su registro viene contrad<strong>di</strong>stinta da un apposito tag della<br />

lunghezza <strong>di</strong> 512 byte, che serve ad identificare il blocco <strong>di</strong> memoria in cui si trova<br />

come blocco riservato al journal file, e che contiene inoltre il numero identificativo<br />

della transazione a cui appartiene. I 512 byte che servono da tag vengono scritti solo<br />

dopo che sono stati memorizzati in registro tutti gli altri dati appartenenti alla<br />

transazione, in modo da poter <strong>di</strong>stinguere, in caso <strong>di</strong> blackout o altri imprevisti, le<br />

transazioni che sono state memorizzate correttamente (e sono quin<strong>di</strong> pronte per essere<br />

scritte nel file system) da quelle rimaste incomplete che saranno rimosse dal registro.<br />

Il JFS assegna al file <strong>di</strong> registro uno spazio ben definito su <strong>di</strong>sco, spazio che può essere<br />

ampliato in caso <strong>di</strong> necessità attraverso opportune system call, utilizzando la memoria<br />

virtuale che il sistema operativo mette a <strong>di</strong>sposizione. In linea <strong>di</strong> massima, però, il<br />

numero <strong>di</strong> transazioni che possono essere memorizzate nel journal file non è infinito, ed<br />

occorre un meccanismo che liberi lo spazio necessario per le nuove transazioni da<br />

registrare. Per ovviare a questo problema il journal file è progettato alla stregua <strong>di</strong> un<br />

buffer FIFO (first in first out): quando <strong>di</strong>venta necessario liberare spazio sul file, il<br />

journaling layer scrive su <strong>di</strong>sco le transazioni più vecchie presenti su registro, marca<br />

queste ultime come transazioni eseguite (operazione, questa, necessaria per avere la<br />

37


certezza <strong>di</strong> quali operazioni sono state eseguite in caso <strong>di</strong> arresti improvvisi <strong>di</strong> sistema)<br />

e le cancella dal file, liberando spazio per registrare nuove transazioni; questo processo<br />

prende il nome <strong>di</strong> “checkpointing” delle transazioni.<br />

Per la scrittura su registro <strong>di</strong> file <strong>di</strong> gran<strong>di</strong> <strong>di</strong>mensioni, il JFS spezza l’operazione in<br />

tante transazioni che vengono via via scritte sul file system proprio attraverso il<br />

checkpointing, meccanismo che è del tutta analogo al comando truncate() <strong>di</strong> Linux.<br />

Il sistema <strong>di</strong> journaling deve essere in grado <strong>di</strong> gestire anche operazioni <strong>di</strong><br />

aggiornamento continuo <strong>di</strong> un numero limitato <strong>di</strong> blocchi <strong>di</strong> memoria, per fare questo<br />

esso funge da immagine del file system rispetto al VFS ed al sistema operativo in<br />

generale: in casi come questo non è garantito che ogni singolo aggiornamento <strong>di</strong>venti<br />

effettivo nel file system, ma è il JFS che si prende carico <strong>di</strong> memorizzare ognuno <strong>di</strong><br />

questi ultimi all’interno del registro, e <strong>di</strong> fare da “immagine” della parte <strong>di</strong> memoria<br />

secondaria da aggiornare rispetto al VFS ed al resto del sistema operativo, per poi<br />

riversare nel file system gli aggiornamenti solo perio<strong>di</strong>camente, snellendo così il carico<br />

<strong>di</strong> lavoro del <strong>di</strong>sco rigido e della CPU; l’unico modo per garantire l’or<strong>di</strong>namento e la<br />

sequenzialità <strong>degli</strong> aggiornamenti è quello <strong>di</strong> aprire un file in modalità O_SYNC,<br />

oppure eseguire l’operazione <strong>di</strong> fsync() su <strong>di</strong> esso.<br />

Il journaling layer è inoltre in grado <strong>di</strong> gestire i blackout o gli arresti <strong>di</strong> sistema in tutta<br />

una serie <strong>di</strong> casi particolari come garantire l’aggiornamento dei quota files in seguito ad<br />

una scrittura da parte <strong>di</strong> un determinato utente, garantire la cancellazione <strong>di</strong> un file o <strong>di</strong><br />

una <strong>di</strong>rectory qualsiasi e l’eliminazione dei dead link, che sono quei file che sono stati<br />

cancellati dal <strong>di</strong>sco (hanno un link count uguale a zero) ma sono ancora presenti in<br />

memoria perché utilizzati da uno o più processi al momento dell’arresto <strong>di</strong> sistema.<br />

In definitiva si può affermare che il sistema <strong>di</strong> journaling che ext3 mette a <strong>di</strong>sposizione<br />

rappresenta un grosso vantaggio, perchè garantisce <strong>di</strong> avere un file system sempre<br />

consistente ed evita all’utente l’uso <strong>di</strong> utility come fsck, che per controllare la<br />

consistenza <strong>di</strong> partizioni <strong>di</strong> <strong>di</strong>mensioni sempre maggiori impiegherebbero una quantità<br />

<strong>di</strong> tempo spropositata.<br />

I particolari del journaling layer su cui si sta ancora lavorando riguardano la trasparenza<br />

del JFS rispetto all’utente e lo snellimento del carico <strong>di</strong> lavoro del JFS stesso.<br />

Sul piano della struttura fisica ext3 <strong>di</strong>fferisce dalla sua versione precedente solo per la<br />

presenza del journal file, ed una partizione <strong>di</strong> memoria che utilizza ext3 può essere<br />

rimontata utilizzando ext2 senza che quest’ultimo trovi il file system incoerente, perché<br />

il file <strong>di</strong> registro viene visto come un normale file.<br />

38


Si sta quin<strong>di</strong> lavorando ad una funzione <strong>di</strong> tune2fs che consenta all’utente <strong>di</strong> aggiungere<br />

un inode arbitrario, riservato ad un journal file <strong>di</strong> <strong>di</strong>mensione definita ad una partizione<br />

ext2 esistente, senza che esso appaia come file nel file system, e quin<strong>di</strong> senza che<br />

l’utente possa eliminarlo o mo<strong>di</strong>ficarlo arbitrariamente.<br />

L’altra grossa mo<strong>di</strong>fica che è in corso riguarda il tipo <strong>di</strong> journaling che il JFS<br />

implementa: nella versione attuale i dati che sono inseriti nelle transazioni riguardano<br />

gli aggiornamenti che vengono fatti nell’intero file system, in altre parole il registro<br />

memorizza gli aggiornamenti sia nei dati utente che nei metadati, così da poter eseguire<br />

una semplice operazione <strong>di</strong> write-behind per aggiornare il file system. Un meccanismo<br />

<strong>di</strong> questo tipo, però, comporta carichi <strong>di</strong> lavoro molto elevati per la macchina<br />

(nonostante gli aggiornamenti del file system siano comunque eseguiti nei momenti in<br />

cui la CPU ed il <strong>di</strong>sco non sono in pieno utilizzo), e ci si sta quin<strong>di</strong> muovendo nella<br />

<strong>di</strong>rezione <strong>di</strong> eseguire un aggiornamento soltanto dei metadati, quando la situazione<br />

ovviamente lo permette.<br />

Un esempio <strong>di</strong> metadata-only journaling può essere quello dell’allocazione <strong>di</strong> un blocco<br />

ad un particolare file: nel caso <strong>di</strong> metadata journaling, sul registro verrebbe scritto quale<br />

blocco deve essere contrassegnato come “in uso” e a quale file deve essere aggiunto un<br />

puntatore a quel blocco, transazione che occuperebbe uno spazio <strong>di</strong> pochi kilobyte su<br />

registro, mentre l’attuale implementazione <strong>di</strong> ext3 eseguirebbe un’intera copia dei<br />

blocchi che verrebbero mo<strong>di</strong>ficati.<br />

3.6 Altri particolari <strong>di</strong> Ext3<br />

In aggiunta alle caratteristiche standard messe a <strong>di</strong>sposizione da Unix, Ext3 supporta<br />

alcune estensioni che non sono normalmente presenti nei file system Unix [vfs/ext2].<br />

Questo file system implementa i link simbolici veloci (fast symbolic links), che non<br />

memorizzano il nome del file <strong>di</strong> riferimento all’interno del blocco <strong>di</strong> memoria ma<br />

all’interno dell’inode stesso, evitando sprechi <strong>di</strong> memoria e velocizzando le operazioni<br />

<strong>di</strong> lettura dei link (non c’è bisogno <strong>di</strong> accedere ai blocchi dati per recuperare il file <strong>di</strong><br />

riferimento).<br />

Un tipo <strong>di</strong> file <strong>di</strong> recente introduzione è il file immutabile, un tipo <strong>di</strong> file, cioè, che può<br />

essere soltanto letto: nessun utente è in grado <strong>di</strong> scriverlo o <strong>di</strong> cancellarlo. I file <strong>di</strong> tipo<br />

append-only, invece, possono essere aperti in modalità <strong>di</strong> scrittura, ma ogni dato<br />

aggiunto viene inserito in coda al file, tipo che è molto utile per i file che possono<br />

39


soltanto crescere <strong>di</strong> <strong>di</strong>mensione come i process log; come i file immutabili, questi non<br />

possono essere cancellati o rinominati.<br />

Ext3 si avvantaggia dell’utilizzo della buffer cache per accedere al <strong>di</strong>sco compiendo<br />

delle pre-letture (readaheads): quando un blocco deve essere letto, il co<strong>di</strong>ce del kernel<br />

richiede la lettura <strong>di</strong> <strong>di</strong>versi blocchi <strong>di</strong> memoria ad esso contigui. I readaheads sono<br />

normalmente effettuati durante le letture sequenziali dei dati, e questo file system le<br />

estende anche alla lettura dei <strong>di</strong>rettori.<br />

I gruppi <strong>di</strong> blocchi sono utilizzati dal file system per raggruppare insieme inode e dati: il<br />

co<strong>di</strong>ce del kernel prova sempre ad allocare blocchi dati per un file nello stesso gruppo in<br />

cui è memorizzato il relativo inode; questo per ridurre al minimo gli spostamenti della<br />

testina del <strong>di</strong>sco rigido per ricercare ad accedere ai contenuti del file.<br />

Anche durante le operazioni <strong>di</strong> scrittura, ext3 prealloca fino a 8 blocchi dati a<strong>di</strong>acenti a<br />

quello che deve essere scritto, in modo da tenere i data block appartenenti allo stesso<br />

file i più vicini possibile tra loro. Con questa tecnica si raggiungono hit rate del 75% in<br />

preallocazione anche in partizioni con poco spazio libero residuo, e si velocizzano le<br />

operazioni <strong>di</strong> lettura sequenziale dei dati.<br />

40


4 Il file system ReiserFS<br />

Questo file system, sviluppato dal team <strong>di</strong>retto dal dott. Hans Reiser, presenta nella sua<br />

attuale versione, la quarta, <strong>di</strong>verse caratteristiche implementative che lo rendono molto<br />

efficace nell’adempiere ai requisiti <strong>di</strong>scussi nel capitolo 2 [reiser].<br />

Come per tutti i file system che utilizzano il VFS per relazionarsi con il sistema<br />

operativo, ReiserFS si interfaccia con il co<strong>di</strong>ce <strong>di</strong> livello più elevato attraverso il<br />

superblocco, gli inode ed i file; entità, queste, che sono gestite ai livelli più bassi in<br />

modo autonomo dal co<strong>di</strong>ce del file system. Ogni oggetto che viene creato all’interno<br />

della partizione è gestito da un apposito plugin, che contiene al suo interno tutte le<br />

funzioni necessarie per eseguire le operazioni riguardanti quel tipo <strong>di</strong> oggetto. Esistono<br />

quin<strong>di</strong> file plugins, <strong>di</strong>rectory plugins, e plugin per gestire tutti gli elementi memorizzati;<br />

un’implementazione <strong>di</strong> questo tipo permette <strong>di</strong> aggiornare le funzionalità riguardanti un<br />

singolo aspetto del sistema operativo senza dover stravolgere l’intero co<strong>di</strong>ce, sono poi<br />

messi a <strong>di</strong>sposizione dei controlli che permetteranno <strong>di</strong> scegliere quale plugin utilizzare<br />

per operare su <strong>di</strong> un particolare tipo d'oggetto (cosa che, salvo interventi dell’utente, è<br />

eseguita in modo automatico dal sistema).<br />

4.1 Organizzazione della memoria<br />

ReiserFS <strong>di</strong>vide le partizioni su <strong>di</strong>sco in blocchi <strong>di</strong> 4KB, collegati tra loro attraverso una<br />

struttura ad albero <strong>di</strong> <strong>di</strong>mensione finita. Una <strong>di</strong>visione <strong>di</strong> questo tipo velocizza l’accesso<br />

ai dati da parte delle applicazioni che utilizzano il comando mmap() per accedere<br />

<strong>di</strong>rettamente ad i contenuti dei file in memoria, in quanto questa routine necessita che i<br />

dati letti siano allineati <strong>di</strong> 4KB, rendendo necessarie operazioni <strong>di</strong> allineamento nel caso<br />

i dati fossero <strong>di</strong>visi in modo <strong>di</strong>verso.<br />

Tutti i blocchi vengono poi organizzati in una struttura ad albero finito, in cui solo i<br />

rami <strong>di</strong> livello più basso contengono riferimenti alle foglie, che sono i blocchi che<br />

contengono i dati utente.<br />

Il sistema riesce a recuperare ogni oggetto che viene memorizzato nell’albero tramite<br />

l’uso delle hash key che vengono associate a questi ultimi, e tutti gli oggetti che<br />

vengono memorizzati in un blocco sono or<strong>di</strong>nati al suo interno per chiave.<br />

41


La ricerca <strong>di</strong> un oggetto all’interno dell’albero parte dalla ra<strong>di</strong>ce (da cui possono essere<br />

raggiunti tutti gli altri no<strong>di</strong>), e prosegue nei vari no<strong>di</strong>; man mano che la ricerca si muove<br />

da un nodo all’altro, per scegliere in quale sottoalbero proseguire, il co<strong>di</strong>ce controlla le<br />

chiavi: per ogni puntatore ad un sottoalbero esiste una chiave limite sinistra (left<br />

delimiting key, per sinistra s’intende <strong>di</strong> livello più alto, navigare un albero da sinistra a<br />

destra significa percorrerlo dalla ra<strong>di</strong>ce verso le foglie), ed i puntatori ai sottoalberi e i<br />

sottoalberi stessi sono or<strong>di</strong>nati per chiave limite sinistra, che corrisponde alla chiave più<br />

piccola che identifica gli oggetti presenti nel sottoalbero.<br />

Ogni sottoalbero contiene oggetti la cui chiave limite sinistra corrisponde alla chiave<br />

limite sinistra del relativo puntatore (puntatore al sottoalbero), e riassumendo le<br />

informazioni in questo modo <strong>di</strong>venta possibile scegliere quale sottoalbero esplorare,<br />

senza dover più usare algoritmi <strong>di</strong> navigazione <strong>di</strong> tipo ricorsivo.<br />

In particolare, lo Unix <strong>di</strong>rectory plugin presente nel co<strong>di</strong>ce del file system,<br />

analogamente alle specifiche del VFS, implementa le <strong>di</strong>rectory come una lista <strong>di</strong><br />

“<strong>di</strong>rectory entries” (elementi presenti nella <strong>di</strong>rectory), ciascuna contenente un nome ed<br />

una chiave. Quando viene dato un nome come parametro <strong>di</strong> ricerca da risolvere, il<br />

<strong>di</strong>rectory plugin trova la <strong>di</strong>rectory contenente il nome in questione e <strong>di</strong> conseguenza<br />

ritorna la chiave presente nella relativa <strong>di</strong>rectory entry. A questo punto la chiave può<br />

essere utilizzata dal co<strong>di</strong>ce che gestisce la memorizzazione dei dati nell’albero per<br />

trovare tutte le parti del file che è stato precedentemente nominato.<br />

La chiave limite destra, anch’essa memorizzata all’interno <strong>di</strong> ogni sottoalbero, è la più<br />

grande <strong>di</strong> tutte le chiavi, e riassume il percorso dalla ra<strong>di</strong>ce fino al punto dell’albero a<br />

cui si è arrivati.<br />

Il contenuto <strong>di</strong> ogni nodo nell’albero è or<strong>di</strong>nato per chiave; <strong>di</strong> conseguenza anche<br />

l’intero albero è or<strong>di</strong>nato per chiave, e per una chiave data si conosce subito il percorso<br />

da fare per trovare almeno un elemento da essa contrad<strong>di</strong>stinta. Dato che una chiave<br />

intera non identifica un file ma un particolare byte all’interno <strong>di</strong> quest’ultimo, durante le<br />

ricerche, il co<strong>di</strong>ce ritornerà soltanto la parte <strong>di</strong> chiave necessaria ad identificare il file<br />

nella sua interezza.<br />

Come in ext3, per ragioni <strong>di</strong> efficienza e <strong>di</strong> fanout, nei no<strong>di</strong> interni non vengono<br />

memorizzati dati, ma soltanto puntatori ai sottoalberi con le relative chiavi <strong>di</strong> ricerca, e<br />

relegando i dati utente soltanto all’interno delle foglie dell’albero, come mostrato in<br />

figura 4.1: ciascun nodo dell’albero è composto <strong>di</strong> campi dati preformattati contenenti,<br />

appunto, la hash key ed il riferimento ai blocchi contenenti i dati relativi ad ogni<br />

42


oggetto. I blocchi contenenti i dati (foglie) possono contenere più <strong>di</strong> un oggetto (block<br />

sharing), e ciascuno <strong>di</strong> questi ultimi è inglobato all’interno <strong>di</strong> un proprio “item”,<br />

contenitore formattato, contenente i dati dell’oggetto, la chiave identificativa ed altre<br />

informazioni necessarie alla corretta gestione dei contenuti.<br />

Figura 4.1: Struttura dei no<strong>di</strong> in ReiserFS.<br />

ReiserFS organizza i dati secondo un particolare tipo d’albero bilanciato chiamato<br />

“dancing tree” (albero danzante). In opposizione ai normali alberi bilanciati binari, che<br />

tendono ad essere bilanciati in ogni istante, il dancing tree è progettato in modo da<br />

eseguire le operazioni <strong>di</strong> bilanciamento soltanto quando il carico <strong>di</strong> lavoro per la<br />

macchina è minore, oppure solo in seguito ad un riversamento <strong>di</strong> dati su <strong>di</strong>sco. L’idea<br />

<strong>di</strong>etro a questo sistema è quella <strong>di</strong> velocizzare le operazioni con i file posticipando le<br />

operazioni <strong>di</strong> ottimizzazione dell’albero, e scrivendo i dati su <strong>di</strong>sco solo quando<br />

necessario, visto che scrivere su supporto rigido è un’operazione molto più lenta che<br />

scrivere i dati in RAM. Per questo motivo, le operazioni <strong>di</strong> ottimizzazione sono eseguite<br />

meno <strong>di</strong> frequente, ma d’altra parte possono risultare più pesanti delle ottimizzazioni<br />

che sarebbero eseguite se si applicassero gli algoritmi <strong>di</strong> bilanciamento ad ogni scrittura.<br />

Il dancing tree permette inoltre <strong>di</strong> fondere insieme il contenuto <strong>di</strong> <strong>di</strong>versi no<strong>di</strong> la cui<br />

capacità non è utilizzata appieno; questa funzionalità è utilizzata in risposta ad eventuali<br />

richieste <strong>di</strong> memoria da parte del sistema, ed ogni volta che i dati vengono scritti su<br />

<strong>di</strong>sco come riversamento in memoria <strong>di</strong> una transazione completata.<br />

Con la stessa idea <strong>di</strong> fondo viene utilizzato un altro accorgimento per ridurre il tempo<br />

perso in scritture su <strong>di</strong>sco: la procrastinazione <strong>degli</strong> aggiornamenti su <strong>di</strong>sco. Con questo<br />

metodo si esegue il maggior numero possibile <strong>di</strong> aggiornamenti dei dati <strong>di</strong>rettamente in<br />

RAM, per poi riversare tutto su <strong>di</strong>sco solo quando si ha a <strong>di</strong>sposizione una versione<br />

definitiva <strong>di</strong> quello che sarà il nuovo layout <strong>di</strong> memoria.<br />

43


In ReiserFS ogni oggetto può essere visto sia come file che come <strong>di</strong>rectory allo stesso<br />

tempo: se si accede ad esso come file lo si potrà manipolare come sequenza <strong>di</strong> byte, se<br />

lo si apre come <strong>di</strong>rectory si possono ottenere i files e tutti gli oggetti in esso contenuti;<br />

rendere possibile questi tipi <strong>di</strong> accesso è necessario per poter implementare stream ed<br />

attributi.<br />

Per implementare un file Unix regolare, con la relativi metadati, sono utilizzati un<br />

plugin per il corpo del file, un <strong>di</strong>rectory plugin per trovare i file plugin che verranno<br />

usati per interpretare i metadati.<br />

Altra particolarità <strong>di</strong> questo file system è la possibilità <strong>di</strong> memorizzare file nascosti:<br />

questi sono immagazzinati all’interno dell’albero come normali <strong>di</strong>rectory entries, ma<br />

non sono visibili all’utente con il normale comando read<strong>di</strong>r; questo è molto utile per<br />

permettere alle applicazioni l’accesso ai propri file nascosti, senza <strong>di</strong>sturbare l’utente<br />

permettendogli <strong>di</strong> vedere ed accedere a file <strong>di</strong> applicazione quando questo non è<br />

necessario.<br />

4.2 Struttura fisica<br />

La memorizzazione dei dati sulle partizioni ReiserFS si avvale del concetto <strong>di</strong> item<br />

(elemento). Un item è un contenitore dati <strong>di</strong> formattazione predefinita che è contenuto<br />

all’interno <strong>di</strong> un singolo nodo, e permette in questo modo <strong>di</strong> gestire lo spazio all’interno<br />

dei no<strong>di</strong>. In questo file system ogni item ha una chiave identificativa, un offset da inizio<br />

nodo a cui il corpo dell’item comincia, una lunghezza del corpo ed un pluginID che<br />

identifica il tipo <strong>di</strong> item che è stato memorizzato.<br />

I no<strong>di</strong> che contengono item sono chiamati no<strong>di</strong> formattati (formatted nodes), mentre i<br />

no<strong>di</strong> che non contengono questi elementi sono chiamati foglie non formattate<br />

(unfleaves, unformatted leaves); questi ultimi si possono trovare soltanto a fine albero,<br />

in quanto possono essere utilizzati per memorizzare dati appartenenti ad un file, in<br />

porzioni <strong>di</strong> memoria chiamati extent: un extent è una sequenza <strong>di</strong> blocchi contigui non<br />

formattati che appartengono allo stesso oggetto. Un puntatore ad extent contiene il<br />

numero del primo blocco e la lunghezza totale della sequenza, in modo da non sprecare<br />

spazio per memorizzare riferimenti per ogni singola unfleave; e visto che ciascun extent<br />

appartiene ad un solo oggetto, può essere memorizzata una sola chiave per ognuno <strong>di</strong><br />

essi, per poi calcolare la hash key d’ogni byte all’interno <strong>degli</strong> stessi. Grazie a questo<br />

44


meccanismo s’inizia a risparmiare spazio all’interno dei no<strong>di</strong> per tutti gli extent più<br />

gran<strong>di</strong> <strong>di</strong> due blocchi.<br />

Ritornando sui no<strong>di</strong> formattati, Reiser4 definisce <strong>di</strong>versi tipi <strong>di</strong> item, per contenere i vari<br />

tipi d’informazioni:<br />

static_stat_data: contiene proprietario, permessi, ultimo accesso,<br />

creazione, ultima mo<strong>di</strong>fica, e numero <strong>di</strong> link appartenenti ad un file.<br />

cmpnd_<strong>di</strong>r_item: contiene le <strong>di</strong>rectory entries (elementi contenuti nei<br />

<strong>di</strong>rettori) e le chiavi dei file a cui ogni entry fa riferimento.<br />

Puntatori ad extent: spiegati sopra.<br />

Puntatori a no<strong>di</strong>: spiegati nel paragrafo precedente, contengono anche la chiave<br />

limite del nodo a cui fanno riferimento.<br />

bo<strong>di</strong>es (parti <strong>di</strong> file): contiene gruppi <strong>di</strong> dati appartenenti ad un file che non sono<br />

abbastanza gran<strong>di</strong> da riempire un singolo nodo.<br />

Di seguito è riportato la forma con cui sono definite le strutture dati utilizzate da<br />

ReiserFS:<br />

La foglia non formattata, che è l’unico nodo che non ha un proprio header (descrittore),<br />

è una semplice sequenza <strong>di</strong> byte contigui.<br />

....................................................................................................................................................<br />

La struttura dell’item è la seguente:<br />

Item_Body . . separated . .<br />

Item_Head<br />

Item_Key Item_Offset Item_Length Item_Plugin_id<br />

Una foglia formattata ha la seguente struttura:<br />

Block_Head Item_Body 0 Item_Body 1 - - Item_Body n Free<br />

Space Item_Head n - - Item_Head 1 Item_Head 0<br />

45


Un nodo interno all’albero, appartenente cioè ad un ramo, ha la seguente struttura:<br />

Block_Head Item_Body 0<br />

NodePointer 0 - - -<br />

Item_Body n<br />

NodePointer n Free Space Item_Head n - - - Item_Head 0<br />

I no<strong>di</strong> più esterni, che contengono puntatori ai dati (foglie dell’albero), sono chiamati<br />

“ramoscelli” (twig):<br />

Block_Hea<br />

d<br />

Item_Body 0<br />

NodePointe<br />

r 0<br />

Item_Body 1<br />

ExtentPointe<br />

r 1<br />

Item_Body 2<br />

NodePointe<br />

r 2<br />

Item_Body 3<br />

ExtentPointe<br />

r 3<br />

Item_Body n<br />

NodePointe<br />

r n<br />

Free<br />

Spac<br />

e<br />

Item_Hea<br />

d n<br />

Item_He<br />

ad 0<br />

Come accennato in precedenza, questo file system memorizza i riferimenti ai dati<br />

soltanto nei no<strong>di</strong> <strong>di</strong> livello più basso (twig, che sono gli unici che possono quin<strong>di</strong><br />

contenere extent pointers), questa scelta consente <strong>di</strong> utilizzare algoritmi meno pesanti<br />

nelle operazioni <strong>di</strong> bilanciamento dell’albero.<br />

Altra peculiarità <strong>di</strong> questo file system è la possibilità <strong>di</strong> con<strong>di</strong>videre blocchi tra più<br />

oggetti: un singolo blocco non viene assegnato ad un singolo file, ma può contenere dati<br />

appartenenti a <strong>di</strong>versi oggetti. Questo sistema, che permette <strong>di</strong> ottenere un’efficienza<br />

nell’utilizzo dello spazio su <strong>di</strong>sco del 94%, è utilizzato per i file <strong>di</strong> gran<strong>di</strong> <strong>di</strong>mensioni la<br />

cui coda non riesce a riempire un intero blocco, e per tutti i file la cui <strong>di</strong>mensione è<br />

minore <strong>di</strong> quella <strong>di</strong> un singolo blocco; tutte queste informazioni vengono incapsulate<br />

negli items, in modo da poter essere identificate all’interno <strong>di</strong> ogni blocco. Questa<br />

funzionalità rende però molto lunghe le operazioni d’accesso ad un particolare file<br />

memorizzato in un blocco con<strong>di</strong>viso, in quanto <strong>di</strong>venta necessario eseguire una<br />

scansione del blocco per posizionarsi all’offset a cui iniziano i dati: per questo motivo<br />

Namesys (l’azienda che ha sviluppato questo file system) consiglia <strong>di</strong> <strong>di</strong>sabilitare questa<br />

funzione nel caso si dovessero utilizzare applicazioni che richiedono un utilizzo<br />

intensivo delle risorse della macchina.<br />

46


4.3 Implementazione <strong>di</strong> stream <strong>di</strong> dati e <strong>di</strong> attributi<br />

ReiserFS utilizza soltanto due elementi, con i quali è in grado <strong>di</strong> realizzare gli stream <strong>di</strong><br />

dati e tutti gli attributi associati ad ogni oggetto. Un file è una sequenza <strong>di</strong> byte<br />

contrad<strong>di</strong>stinta da un nome. Una <strong>di</strong>rectory è uno spazio semantico che collega i nomi ad<br />

una serie <strong>di</strong> oggetti presenti nella <strong>di</strong>rectory stessa.<br />

Nella versione precedente <strong>di</strong> questo file system esistevano anche gli attributi dei file,<br />

che erano dati esterni al file, che però ne descrivevano le caratteristiche; un esempio <strong>di</strong><br />

questi attributi sono i permessi d’accesso, le ACL, o la data <strong>di</strong> ultima mo<strong>di</strong>fica. Ogni<br />

attributo richiedeva la sua API, e creare nuovi attributi creava complessità e problemi <strong>di</strong><br />

compatibilità nel co<strong>di</strong>ce.<br />

Dato che in Reiser4 i file possono essere interpretati anche come <strong>di</strong>rectory, <strong>di</strong>venta<br />

possibile implementare i normali attributi come singoli files. Per accedere ad un<br />

particolare attributo <strong>di</strong> un file, ora è necessario avere il nome del file, seguito dal<br />

separatore <strong>di</strong> <strong>di</strong>rectory ‘/’ ed il nome dell’attributo. Un file tra<strong>di</strong>zionale è ora<br />

implementato in modo da possedere alcune caratteristiche proprie dei <strong>di</strong>rettori; esso<br />

contiene dei file al suo interno (se visto come <strong>di</strong>rectory) che sono i file contenenti gli<br />

attributi associati al file stesso.<br />

Gli elementi necessari a comporre stream ed attributi per mezzo <strong>di</strong> file e <strong>di</strong>rectory sono:<br />

Api efficienti per file <strong>di</strong> piccole <strong>di</strong>mensioni.<br />

Meto<strong>di</strong> <strong>di</strong> memorizzazione che siano efficienti anche per piccoli file (tra cui<br />

descrittori <strong>di</strong> <strong>di</strong>mensioni contenute per evitare sprechi <strong>di</strong> memoria dovuti al fatto<br />

che i descrittori possono essere più gran<strong>di</strong> dei file a cui sono associati).<br />

Plugin che siano anche in grado <strong>di</strong> comprimere un file che funge da attributo in<br />

un singolo bit.<br />

File che si comportino anche come <strong>di</strong>rectory quando richiesto.<br />

Associazioni d’appartenenza, inclusa l’aggregazione dei file.<br />

Ere<strong>di</strong>tarietà.<br />

Transazioni.<br />

Directory entries nascosti (file nascosti).<br />

Caratteristiche queste, che non erano presenti nella versione 3, e sono state realizzate in<br />

questa versione.<br />

47


Il file system mette a <strong>di</strong>sposizione la system call Reiser4(), che implementa tutte le<br />

caratteristiche necessarie per accedere alle ACL come file/<strong>di</strong>rectory; queste<br />

caratteristiche comprendono l’apertura e chiusura <strong>di</strong> transazioni, l’effettuare una<br />

sequenza <strong>di</strong> I/O in una singola system call, e la possibilità <strong>di</strong> accedere ai file senza<br />

dover utilizzare il descrittore (cosa, questa, necessaria per I/O <strong>di</strong> piccole <strong>di</strong>mensioni che<br />

siano efficienti).<br />

Il principale svantaggio dovuto a quest’implementazione <strong>degli</strong> attributi consiste nel<br />

fatto che ReiserFS, in questa versione, non rende <strong>di</strong>sponibile la possibilità <strong>di</strong><br />

con<strong>di</strong>videre gli attributi tra i vari file, ed inoltre il file system pone un limite massimo <strong>di</strong><br />

64KB alla <strong>di</strong>mensione <strong>di</strong> ogni file attributo [POSIX-ACL].<br />

4.4 Journaling<br />

Analogamente ad Ext3, anche questo file system implementa un registro al cui interno<br />

memorizzare tutte le operazioni <strong>di</strong> I/O su <strong>di</strong>sco, in modo da garantire l’integrità dei dati<br />

anche dopo arresti improvvisi <strong>di</strong> sistema.<br />

A <strong>di</strong>fferenza del file system preso in analisi nel capitolo precedente, però, ReiserFS<br />

utilizza un journaling soltanto dei metadati e non <strong>di</strong> tutti i dati che dovranno<br />

effettivamente essere scritti in memoria (questo, ovviamente, viene fatto quando la<br />

situazione lo permette).<br />

Anche in questo caso le operazioni <strong>di</strong> I/O sono raggruppate in transazioni memorizzate<br />

in un registro invisibile all’utente; una transazione mantiene intatto il contenuto dei<br />

blocchi che andranno ad essere mo<strong>di</strong>ficati fino al momento in cui essa viene riversata su<br />

<strong>di</strong>sco, ed è una lista <strong>di</strong> operazioni <strong>di</strong> scrittura che dovranno essere eseguite in sequenza<br />

sui blocchi del file system interessati.<br />

I blocchi che vengono letti da <strong>di</strong>sco per poi essere mo<strong>di</strong>ficati sono chiamati <strong>di</strong>rty<br />

(sporchi), e sono <strong>di</strong>visi in due parti, relocate ed overwrite, ognuna delle quali viene<br />

gestita in maniera <strong>di</strong>fferente.<br />

I blocchi appartenenti al relocate set sono quelli che saranno scritti in una nuova<br />

posizione in memoria, senza che i vecchi dati siano quin<strong>di</strong> sovrascritti da quelli<br />

aggiornati.<br />

I blocchi appartenenti all’overwrite set sono quelli che devono necessariamente<br />

essere scritti nella loro posizione originale, e che non appartengono al relocate<br />

set. In pratica questo set raggruppa tutti i blocchi che hanno un corrispondente in<br />

48


memoria che si vuole venga sovrascritto dai nuovi dati, più tutti quei blocchi per<br />

cui la sovrascrittura è la migliore politica <strong>di</strong> layout nonostante il costo della<br />

doppia scrittura su <strong>di</strong>sco (deve essere aggiornato anche il riferimento nel nodo<br />

superiore a quello preso in considerazione). Notare che il superblocco è<br />

posizionato nell’albero dei <strong>di</strong>rettori come ra<strong>di</strong>ce della struttura (che contiene un<br />

puntatore al nodo <strong>di</strong> root utente), e come i blocchi che contengono le bitmap<br />

dell’utilizzo del <strong>di</strong>sco, esso non ha riferimenti ad un ramo più alto, e sarà quin<strong>di</strong><br />

sempre parte dell’overwrite set.<br />

La scelta <strong>di</strong> adottare una politica <strong>di</strong> journaling <strong>di</strong> questo tipo <strong>di</strong>pende dal fatto che, in<br />

genere, sovrascrivere blocchi dati già precedentemente allocati è più <strong>di</strong>spen<strong>di</strong>oso in<br />

termini <strong>di</strong> operazioni necessarie rispetto allo scrivere i dati in blocchi “puliti”. Inoltre<br />

bisogna ricordare che il co<strong>di</strong>ce non valuta la <strong>di</strong>mensione dei dati che andranno ad essere<br />

scritti in memoria. Nel caso <strong>di</strong> overwrite dei blocchi con una mole <strong>di</strong> dati aggiornati<br />

maggiore dell’originale, quin<strong>di</strong>, si dovrebbe riscrivere e riempire il blocco originale per<br />

poi creare comunque un riferimento ad un blocco libero che ospiterà la rimanente parte<br />

dei nuovi dati da memorizzare.<br />

Un’esemplificazione del meccanismo <strong>di</strong> <strong>di</strong>visione dei dati relocate/overwrite è mostrata<br />

in figura 4.2.<br />

Applicativi <strong>di</strong> alto<br />

livello<br />

Aggiornamento<br />

Chiave e riferimento<br />

Nodo interno<br />

Creazione riferimento<br />

Scrittura Dati<br />

Journaling Layer<br />

Foglia<br />

Contenente i<br />

dati originali<br />

Blocco dati<br />

vuoto<br />

Relocate Set<br />

Overwrite Set<br />

Figura 4.2: Funzionamento del journaling layer <strong>di</strong> ReiserFS.<br />

L’ultima azione eseguita dal co<strong>di</strong>ce quando viene completata la memorizzazione <strong>di</strong> una<br />

transazione in registro, è quella <strong>di</strong> creare un riferimento ad essa all’interno del<br />

49


superblocco, così che anche in caso <strong>di</strong> crash, alla lettura del superblocco, la transazione<br />

ultimata sia riversata nel file system.<br />

Reiser4 utilizza alcuni espe<strong>di</strong>enti per ottimizzare il journaling per evitare, quando<br />

possibile, un numero eccessivo <strong>di</strong> scritture su <strong>di</strong>sco; questi accorgimenti sono il copyon-capture,<br />

e lo steal-on-capture.<br />

Il copy-on-capture permette <strong>di</strong> ottenere una copia <strong>di</strong> un blocco <strong>di</strong> memoria appartenente<br />

ad una transazione, in modo da permettere all’applicazione richiedente <strong>di</strong> avere a<br />

<strong>di</strong>sposizione i dati aggiornati per poter eseguire le proprie elaborazioni. Questo<br />

meccanismo permette <strong>di</strong> accedere ai dati che stanno per essere aggiornati senza dover<br />

accedere due volte al file system e dover attendere che quest’ultimo sia aggiornato.<br />

Lo steal-on-capture è basato sull’idea scrivere in memoria soltanto l’ultimo <strong>di</strong> tutti gli<br />

aggiornamenti che devono essere fatti su <strong>di</strong> una seria <strong>di</strong> blocchi, evitando tutte le<br />

scritture interme<strong>di</strong>e che sarebbero poi riaggiornate. Questo sistema esclude da<br />

riversamento su <strong>di</strong>sco tutte le transazioni che hanno in corso <strong>di</strong> completamento, o già<br />

presente su registro, una transazione più recente che deve andare ad aggiornare gli stessi<br />

blocchi <strong>di</strong> memoria.<br />

Una ottimizzazione che riguarda il carico <strong>di</strong> lavoro della CPU è invece chiamata<br />

“Encryption on commit”, realizzata per velocizzare le operazioni <strong>di</strong> scrittura su <strong>di</strong>sco <strong>di</strong><br />

dati in modo criptato. Si è deciso <strong>di</strong> ridurre il numero <strong>di</strong> volte in cui la CPU deve usare<br />

gli algoritmi <strong>di</strong> cifratura, facendo sì che scrivesse dati criptati non ad ogni invocazione<br />

<strong>di</strong> write(), ma soltanto all’atto del riversare in memoria i contenuti delle transazioni.<br />

Evitando <strong>di</strong> scrivere le informazioni criptate già all’interno del registro transazioni, si<br />

risparmiano le operazioni necessarie alla CPU per criptare informazioni che non<br />

saranno riversate in memoria perché riaggiornate all’interno del journal dal meccanismo<br />

<strong>di</strong> steal-on-capture, velocizzando così i tempi d’elaborazione. Questo sistema <strong>di</strong><br />

co<strong>di</strong>fica dei dati è automaticamente attivato quando si va a scrivere dati in un nodo che<br />

ha impostato il flag CONTAINS_ENCRYPTED_DATA come attivo.<br />

4.5 Repacking<br />

Un altro modo per velocizzare le operazioni d’accesso ai file, che permette <strong>di</strong> tirarsi<br />

fuori dal conflitto <strong>di</strong> prestazioni dovuto al tempo richiesto per le operazioni <strong>di</strong><br />

bilanciamento in proporzione al tempo guadagnato nelle operazioni <strong>di</strong> ricerca, è quello<br />

<strong>di</strong> utilizzare un repacker. Un repacker è un programma <strong>di</strong> utilità che esegue una<br />

50


deframmentazione del file system ed è inoltre in grado <strong>di</strong> eseguire un or<strong>di</strong>namento<br />

dell’albero in entrambi i sensi <strong>di</strong> percorrenza (quello messo a <strong>di</strong>sposizione da Namesys<br />

cambia alternativamente verso <strong>di</strong> percorrenza ad ogni avvio). In generale l’uso <strong>di</strong> questo<br />

tipo <strong>di</strong> programmi può essere utile perché, in me<strong>di</strong>a, l’80% dei dati presenti in una<br />

partizione <strong>di</strong> memoria rimane inalterato per lunghi perio<strong>di</strong> <strong>di</strong> tempo; ed una volta<br />

ottimizzata questa parte <strong>di</strong> dati, si restringono le operazioni <strong>di</strong> I/O su <strong>di</strong> una porzione<br />

più ristretta <strong>di</strong> memoria, velocizzando le stesse.<br />

4.6 Future caratteristiche del file system<br />

Per quanto riguarda l’organizzazione della memoria secondaria, sono in corso <strong>di</strong><br />

sviluppo due principali caratteristiche. La prima <strong>di</strong> queste è la possibilità <strong>di</strong> specificare<br />

un tipo d’or<strong>di</strong>namento dei nomi attraverso una funzione arbitraria fornita o specificata<br />

dall’utente, già al momento della lettura dei dati, in modo da non dover richiamare una<br />

<strong>di</strong>versa routine per or<strong>di</strong>nare i dati che sono letti su <strong>di</strong>sco. L’altra caratteristica che deve<br />

essere implementata riguarda la compressione delle chiavi memorizzate all’interno dei<br />

no<strong>di</strong>, così da poter aumentare ulteriormente il fanout <strong>di</strong> albero, visto che si<br />

restringerebbe lo spazio occupato da ciascun riferimento memorizzato nei no<strong>di</strong>.<br />

Le innovazioni riguardanti la sicurezza delle informazioni saranno, anche in questo<br />

caso, principalmente due: la possibilità <strong>di</strong> permettere ad un utente <strong>di</strong> mo<strong>di</strong>ficare i dati<br />

entro certi limiti predefiniti, e la possibilità <strong>di</strong> aggregare i file che hanno le stesse<br />

caratteristiche <strong>di</strong> sicurezza.<br />

La prima caratteristica sarà implementata attraverso due plugin che implementeranno le<br />

limitazioni <strong>di</strong> scrittura. Una limitazione sarà invocata prima della scrittura <strong>di</strong> un file; se<br />

il co<strong>di</strong>ce non ritornerà errori, allora sarà consentito l’aggiornamento dei dati. Il primo<br />

plugin che implementerà questa funzionalità sarà sottoforma <strong>di</strong> funzione del kernel,<br />

caricabile in memoria come modulo, che permetterà la scrittura solo se il file sarà<br />

contrad<strong>di</strong>stinto dalle stringhe “secret” o “sensitive”, ma non “top-secret”. L’altro plugin,<br />

che eseguirà le stesse operazioni, sarà però sottoforma <strong>di</strong> programma perl residente in<br />

un file eseguito all’interno dello spazio utente; questo per poter aggirare la scarsa<br />

flessibilità delle funzioni <strong>di</strong> kernel. Saranno quin<strong>di</strong> implementati limiti <strong>di</strong> scrittura che<br />

possono essere eseguiti sia dal kernel che dallo spazio utente, sottoforma <strong>di</strong><br />

applicazioni; in particolare, i controlli <strong>di</strong> scrittura eseguibili dall’utente saranno dei<br />

51


plugin che eseguono la procedura <strong>di</strong> controllo contenuta in un file specificato come una<br />

normale applicazione, e passeranno come input a quest’ultima i dati che devono essere<br />

scritti, come output <strong>di</strong> processo, nel caso l’applicazione non riportasse errori.<br />

L’aggregazione <strong>di</strong> file che hanno le stesse caratteristiche <strong>di</strong> sicurezza è importante per<br />

garantire flessibilità nella loro gestione: può succedere che sia presente in memoria un<br />

gran numero <strong>di</strong> file che rappresentano logicamente una sola entità nei confronti della<br />

gestione della loro sicurezza (per esempio sono soggetti agli stessi controlli d’accesso e<br />

scrittura), e sarebbe quin<strong>di</strong> desiderabile avere un singolo punto <strong>di</strong> controllo per la<br />

gestione complessiva dei loro attributi <strong>di</strong> sicurezza.<br />

Un'altra caratteristica non ancora implementata riguarda l’au<strong>di</strong>ting: si realizzerà una<br />

“infrastruttura” <strong>di</strong> supporto per plugin che siano in grado <strong>di</strong> notificare<br />

all’amministratore o ad altri utenti quando, ad esempio, viene effettuato un accesso ad<br />

un particolare file, così da poter aumentare ulteriormente il livello <strong>di</strong> sicurezza su<br />

software proprietario o su file riservati. ReiserFS fornirebbe quin<strong>di</strong> il supporto per la<br />

gestione <strong>di</strong> una libreria <strong>di</strong> plugin che operano in questo campo, in modo da rendere più<br />

snella la loro realizzazione e più semplice il loro utilizzo.<br />

52


5 WinFS<br />

Nella recente versione <strong>di</strong> Microsoft Windows, avente il nome non ancora ufficiale <strong>di</strong><br />

“Longhorn”, Microsoft presenta un nuovo file system chiamato WinFS, che è basato sul<br />

vecchio file system Microsoft NTFS per quanto riguarda la parte <strong>di</strong> memorizzazione dei<br />

dati. Nonostante la scarsità <strong>di</strong> documentazione pubblicata sull’argomento, e la <strong>di</strong>fficoltà<br />

nel reperire i sorgenti del file system, in quanto software protetto da copyright, nei<br />

seguenti paragrafi saranno esposte le caratteristiche principali <strong>di</strong> questo sistema, che<br />

implementa meccanismi <strong>di</strong> gestione dati, come la possibilità <strong>di</strong> memorizzare<br />

informazioni non all’interno <strong>di</strong> file, che sono del tutto nuove in questo campo [WFS].<br />

WinFS, oltre a sod<strong>di</strong>sfare i requisiti <strong>di</strong> un normale file system, estende ai file i servizi<br />

d’in<strong>di</strong>cizzazione, or<strong>di</strong>namento, streaming dei dati e <strong>di</strong> ricerca, che sono comuni ai<br />

database; i primi stu<strong>di</strong> per una gestione dei dati <strong>di</strong> questo tipo, sono stati eseguiti dagli<br />

sviluppatori Apple per i sistemi operativi MAC OS X, e questo file system, che amplia<br />

<strong>di</strong> molto queste funzionalità, rappresenta un proseguimento <strong>degli</strong> stu<strong>di</strong> in questo campo.<br />

WinFS estende le potenzialità delle API messe a <strong>di</strong>sposizione, rendendole in grado <strong>di</strong><br />

operare su dati puri, non incapsulati all’interno <strong>di</strong> files, come registrazioni au<strong>di</strong>o,<br />

appuntamenti, contatti, persone, documenti, immagini, e-mail, ecc. I dati che sono<br />

memorizzati possono anche essere strutturati o semi-strutturati; il file system mette a<br />

<strong>di</strong>sposizione una “infrastruttura software” per organizzare, cercare e con<strong>di</strong>videre tutte<br />

queste informazioni.<br />

Per organizzare le informazioni su <strong>di</strong>sco, invece del tra<strong>di</strong>zionale albero gerarchico,<br />

WinFS utilizza un grafo <strong>di</strong>retto aciclico (<strong>di</strong>rect acyclic graph, DAG) che è un insieme <strong>di</strong><br />

oggetti immagazzinati e relazioni tra essi, che sono memorizzati come un database<br />

relazionale, in grado <strong>di</strong> fornire supporto per memorizzare ogni tipo <strong>di</strong> relazione,<br />

gerarchica e non.<br />

In questo modo <strong>di</strong>venta possibile trovare oggetti usando come parametro <strong>di</strong> ricerca il<br />

valore <strong>di</strong> una loro proprietà, o anche il valore <strong>di</strong> una proprietà <strong>di</strong> un altro oggetto ad essi<br />

collegato da una qualsiasi relazione. Nella figura 5.1 viene riportata l’organizzazione e<br />

la struttura del file system WinFS: la gestione dei dati a livello più basso è basata sul<br />

file system NTFS; i dati su <strong>di</strong>sco vengono poi gestiti da un motore <strong>di</strong> database<br />

relazionale, che tratta ogni oggetto secondo uno specifico modello dati. A fianco dei<br />

53


modelli dati viene messo il co<strong>di</strong>ce che implementa tutte le operazioni ed i servizi<br />

necessari al resto del sistema operativo. Tutti i dati in memoria <strong>di</strong> massa, una volta<br />

classificati secondo i modelli dati predefiniti vengono passati ad una serie <strong>di</strong> servizi <strong>di</strong><br />

livello più alto, che li tratta e li classifica secondo le entità e le regole definite in<br />

appositi schemi dati. Al livello superiore WinFS mette a <strong>di</strong>sposizione tre API per<br />

utilizzare i dati come oggetti canonici, oppure per trattarli usando gli standard T/SQL o<br />

XML.<br />

Figura 5.1: Struttura ed organizzazione gerarchica dei componenti <strong>di</strong> WinFS.<br />

5.1 Memorizzazione dei dati: il modello NTFS<br />

Come detto precedentemente WinFS utilizza l’organizzazione e la sud<strong>di</strong>visione del<br />

<strong>di</strong>sco già adottata da NTFS, in quanto questa rappresenta <strong>di</strong> per sé una base stabile su<br />

cui costruire l’infrastruttura per una gestione dei dati <strong>di</strong> tipo relazionale [NT].<br />

Una partizione dati formattata in NTFS è composta, come mostrato in figura 5.2, da un<br />

settore <strong>di</strong> boot, dalla tabella generale dei file (Master File Table, MFT), che contiene le<br />

informazioni riguardanti tutti i file e le <strong>di</strong>rectory presenti in memoria, da <strong>di</strong>versi file<br />

utilizzati dal sistema per gestire il file system e dallo spazio assegnato ai file.<br />

Figura 5.2: Struttura generale delle partizioni NTFS.<br />

54


Il settore <strong>di</strong> boot inizia sempre dal settore 0 e può essere lungo fino a 16 settori, esso<br />

contiene principalmente il superblocco e le istruzioni in linguaggio macchina per le<br />

prime operazioni d’avvio del sistema. Il superblocco, in questo file system contiene<br />

anche l’in<strong>di</strong>rizzo a cui è allocata la MFT, e l’in<strong>di</strong>rizzo a cui è allocata la sua immagine<br />

(da utilizzare nel caso la prima copia risulti corrotta), in quanto NTFS consente <strong>di</strong><br />

memorizzare la file table ad in<strong>di</strong>rizzi <strong>di</strong>versi per evitare <strong>di</strong> dover scrivere questi dati su<br />

settori danneggiati.<br />

La tabella 5.1 mostra la struttura del settore <strong>di</strong> boot: il primo record contiene l'istruzione<br />

<strong>di</strong> jump per la lettura del co<strong>di</strong>ce necessario ad inizializzare il sistema, mentre l'extended<br />

BPB rappresenta un'estensione del superblocco, ed insieme a quest'ultimo contiene le<br />

informazioni riguardanti la struttura fisica della partizione.<br />

Byte Offset Lunghezza record Nome record<br />

0x00 3 bytes Jump Instruction<br />

0x03 LONGLONG OEM ID<br />

0x0B 25 bytes BPB (superblocco)<br />

0x24 48 bytes Extended BPB<br />

0x54 426 bytes Bootstrap Code<br />

0x01FE WORD End of Sector Marker<br />

Tabella 5.1: Organizzazione del settore <strong>di</strong> boot.<br />

La tabella 5.2 mostra invece la struttura del superblocco.<br />

Byte Lunghezza Valore<br />

Offset record esemplificativo<br />

Nome del record<br />

0x0B WORD 0x0002 Bytes Per Settore<br />

0x0D BYTE 0x08 Settori Per Cluster<br />

0x0E WORD 0x0000 Settori Riservati<br />

0x10 3 BYTES 0x000000 always 0<br />

0x13 WORD 0x0000 non utilizzato da NTFS<br />

0x15 BYTE 0xF8 Me<strong>di</strong>a Descriptor<br />

0x16 WORD 0x0000 always 0<br />

0x18 WORD 0x3F00 Settori Per Traccia<br />

0x1A WORD 0xFF00 Numero <strong>di</strong> testine<br />

0x1C DWORD 0x3F000000 Settori Nascosti<br />

55


0x20 DWORD 0x00000000 non utilizzato da NTFS<br />

0x24 DWORD 0x80008000 non utilizzato da NTFS<br />

0x28 LONGLONG 0x4AF57F0000000000 Settori Totali<br />

Logical Cluster Number for<br />

0x30 LONGLONG 0x0400000000000000 the file $MFT (file<br />

contenente la MFT)<br />

0x38 LONGLONG 0x54FF070000000000<br />

Logical Cluster Number for<br />

the file $MFTMirr (imagine<br />

della MFT)<br />

0x40 DWORD 0xF6000000<br />

Clusters per Segmento<br />

riservato ai filr<br />

0x44 DWORD 0x01000000 Clusters Per Index Block<br />

0x48 LONGLONG 0x14A51B74C91B741C Numero <strong>di</strong> serie <strong>di</strong> Volume<br />

0x50 DWORD 0x00000000<br />

Tabella 5.2: Struttura del superblocco NTFS.<br />

Questa tabella mostra come la master file table e la relativa immagine siano<br />

memorizzate in appositi file posizionati ad un offset predefinito in un cluster specificato<br />

nei campi <strong>di</strong> superblocco.<br />

Ogni file in una partizione NTFS è rappresentato da un record nella MFT. NTFS riserva<br />

i primi 16 record <strong>di</strong> questa tabella per informazioni speciali. Il primo record descrive,<br />

infatti, la MFT stessa, ed il secondo contiene un riferimento all’immagine della tabella,<br />

in modo da poter recuperare le informazioni sulla sua struttura anche nel caso il primo<br />

record risulti corrotto. L’in<strong>di</strong>rizzo a cui recuperare l’immagine della MFT è specificato<br />

nel settore <strong>di</strong> boot, ed una copia <strong>di</strong> quest’ultimo viene sempre memorizzata nel centro<br />

logico del <strong>di</strong>sco. Il terzo record della tabella è il file <strong>di</strong> registro utilizzato dal sistema <strong>di</strong><br />

journaling, che anche questo file system adotta. I record dal <strong>di</strong>ciassettesimo in poi sono<br />

assegnati ad ogni file e <strong>di</strong>rectory presente in partizione (anche in NTFS le <strong>di</strong>rectory<br />

sono un particolare tipo <strong>di</strong> file). La struttura della MFT è rappresentata in figura 5.3: i<br />

record relativi ai file <strong>di</strong> gran<strong>di</strong> <strong>di</strong>mensioni hanno sempre uno o più riferimenti ai blocchi<br />

<strong>di</strong> memoria contenenti i dati. La MFT contiene anche un riferimento alla propria<br />

immagine, da usare in caso <strong>di</strong> corruzione della prima.<br />

56


Figura 5.3: Struttura della Master File Table <strong>di</strong> NTFS .<br />

La MFT alloca una certa quantità <strong>di</strong> spazio per ogni record in essa presente, e gli<br />

attributi <strong>di</strong> un file sono sempre scritti all’interno del record stesso. File e <strong>di</strong>rectory <strong>di</strong><br />

piccole <strong>di</strong>mensioni (tipicamente non più gran<strong>di</strong> <strong>di</strong> 1500 bytes, questo però <strong>di</strong>pende<br />

anche dagli attributi ad essi associati), possono però essere interamente memorizzati<br />

all’interno della tabella, velocizzando così le operazioni <strong>di</strong> I/O su <strong>di</strong> essi nel caso siano<br />

<strong>di</strong> <strong>di</strong>mensioni tali da poter essere contenuti in un solo record assieme ad attributi e<br />

descrittore.<br />

Le <strong>di</strong>rectory <strong>di</strong> gran<strong>di</strong> <strong>di</strong>mensioni, non potendo essere contenute in un singolo record<br />

sono organizzate in alberi bilanciati: ogni record ha dei riferimenti a dei blocchi <strong>di</strong><br />

memoria che contengono gli elementi presenti nella <strong>di</strong>rectory che non possono essere<br />

memorizzati nel record stesso.<br />

Tutta questa organizzazione della memoria funge da impalcatura su cui poggiano le<br />

parti più astratte del file system, che saranno descritte nei paragrafi successivi.<br />

57


5.2 Modelli dei dati<br />

Per poter implementare tutte queste funzionalità, il file system necessita <strong>di</strong> conoscere i<br />

tipi <strong>di</strong> dati che deve gestire, e realizza ciò attraverso i modelli <strong>di</strong> dati.<br />

Un modello <strong>di</strong> dati deve fornire i concetti per descrivere strutture dati e la loro<br />

organizzazione. Esempi <strong>di</strong> modelli <strong>di</strong> dati sono i modelli Entity-Relationship e<br />

l’organizzazione dei file system tra<strong>di</strong>zionali; analogamente, il modello dati <strong>di</strong> WinFS<br />

fornisce i seguenti concetti per descrivere le strutture dati e le organizzazioni che<br />

implementa:<br />

Tipi e sottotipi<br />

Proprietà e campi<br />

Relazioni<br />

Gerarchizzazione ed ere<strong>di</strong>tarietà<br />

Estensibilità<br />

5.3 Tipi e sottotipi<br />

In questo file system, un tipo <strong>di</strong> dato rappresenta un modello per un’istanza d’oggetto,<br />

ed è composto da una o più proprietà (campi dati): per esempio il tipo “Persona” può<br />

avere un campo denominato “Nome”, e “Peter” può essere un’istanza del tipo<br />

“Persona”. D’altra parte, però, un tipo <strong>di</strong> dato <strong>di</strong> WinFS può avere un supertipo ed uno o<br />

più sottotipi che esprimono le relazioni <strong>di</strong> ere<strong>di</strong>tarietà e <strong>di</strong> or<strong>di</strong>namento gerarchico, così<br />

come il tipo “Documento” ha come supertipo il tipo “Oggetto” e come sottotipo, tra gli<br />

altri, il tipo “Me<strong>di</strong>a”. Ogni istanza <strong>di</strong> un tipo <strong>di</strong> dato viene serializzata all’interno del file<br />

system, e ad essa è associato un apposito spazio semantico (namespace) che definisce in<br />

che modo questa istanza può essere ricercata e identificata. Tutti i tipi <strong>di</strong> dato <strong>di</strong> WinFS<br />

sono derivati dal tipo “Base.Item”, ed è permessa soltanto l’ere<strong>di</strong>tarietà singola tra i<br />

tipi: quando un tipo <strong>di</strong> dato è derivato da un altro, esso ere<strong>di</strong>ta tutti i campi dati del<br />

secondo, ed il file system non consente <strong>di</strong> ere<strong>di</strong>tare campi dati da altri tipi <strong>di</strong> dato.<br />

Gli elementi che compongono i tipi <strong>di</strong> dato sono chiamati elementi innestati (nested<br />

elements, o anche elementi annidati) e sono ciò che più si avvicina alle semplici<br />

strutture dati o ai record. Tutti gli elementi innestati devono essere derivati dal tipo<br />

“Base.NestedElement”, e possono soltanto essere utilizzati come campi dati <strong>di</strong> un<br />

tipo WinFS.<br />

58


5.4 Proprietà <strong>degli</strong> oggetti e campi dati<br />

Come già detto, tutti i tipi <strong>di</strong> dato sono composti da uno o proprietà e campi. Un campo<br />

o una proprietà possono essere o un tipo <strong>di</strong> dato scalare definito da WinFS oppure un<br />

oggetto <strong>di</strong> tipo “Nested Element”. Il punto <strong>di</strong> partenza del modello dati <strong>di</strong> questo file<br />

system sono i tipi scalari, e la tabella 5.3 mostra tutti i tipi che sono supportati, con il<br />

loro tipo corrispondente SQL e .NET:<br />

Tabella 5.3: Corrispondenza tra i dati scalari <strong>di</strong> tipo WinFS, SQL e CLR.<br />

Il sistema <strong>di</strong> definizione dei tipi permette che ad un singolo campo dati siano associati<br />

più valori (collezioni), così che, per esempio, una persona possa avere più in<strong>di</strong>rizzi <strong>di</strong><br />

casa o più <strong>di</strong> un recapito telefonico.<br />

Tutte le proprietà ed i campi devono esprimere dei vincoli riguardanti il loro contenuto,<br />

e questo viene implementato attraverso le restrizioni, che per esempio possono<br />

esprimere che il valore <strong>di</strong> un campo può o non può essere NULL.<br />

59


5.5 Linguaggio <strong>di</strong> definizione <strong>degli</strong> schemi<br />

Questo file system introduce il concetto <strong>di</strong> linguaggio <strong>di</strong> definizione <strong>degli</strong> schemi, che<br />

serve per definire tutti i tipi <strong>di</strong> dato da esso utilizzati. Come parte dell’implementazione<br />

<strong>di</strong> WinFS, il sistema operativo “Longhorn” fornisce una serie <strong>di</strong> schemi predefiniti che<br />

definiscono alcuni tipi d’oggetto e NestedElement <strong>di</strong> base. Ogni tipo <strong>di</strong> dato è definito<br />

all’interno <strong>di</strong> uno schema, e <strong>di</strong> seguito viene riportato un esempio <strong>di</strong> come si può<br />

definire il tipo “In<strong>di</strong>rizzo” (Address):<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

...<br />

<br />

60


Per definire un nuovo tipo <strong>di</strong> dato si può definire un nuovo schema oppure si può<br />

estendere uno schema esistente; questi due meccanismi sono alla base dell’estensibilità<br />

dei dati supportata da questo file system.<br />

5.6 Relazioni<br />

WinFS utilizza il concetto <strong>di</strong> relazione per esprimere il collegamento <strong>di</strong> un oggetto con<br />

un altro, e tutte le relazioni sono definite da un tipo origine ed un tipo obiettivo (source<br />

e target type); questi tipi vengono poi traslati nelle istanze <strong>degli</strong> oggetti che devono<br />

essere collegati come parte dell’istanza <strong>di</strong> relazione. Tutte le relazioni sono <strong>di</strong>pendenti<br />

dal tipo sorgente, ed una relazione, per esistere, richiede per forza l’esistenza <strong>di</strong> un tipo<br />

<strong>di</strong> dato sorgente; se l’istanza <strong>di</strong> tipo <strong>di</strong> dato che funge da sorgente viene eliminata, allora<br />

anche la relazione sarà eliminata. Una relazione senza istanza obiettivo, infatti, può<br />

esistere, e viene chiamata relazione <strong>di</strong> tipo “dangling”.<br />

In questo file system sono definiti due tipi <strong>di</strong> relazioni: hol<strong>di</strong>ng (<strong>di</strong> mantenimento) e<br />

reference (<strong>di</strong> riferimento).<br />

Le relazioni <strong>di</strong> tipo hol<strong>di</strong>ng non possono essere dangling, e controllano la vita del loro<br />

obiettivo: l’istanza obiettivo coinvolta nella relazione continua ad esistere fin quando<br />

rimane obiettivo <strong>di</strong> almeno una relazione qualsiasi. La relazione hol<strong>di</strong>ng maggiormente<br />

degna <strong>di</strong> nota è la relazione “membro <strong>di</strong> una <strong>di</strong>rectory” tra un tipo file (target) ed un<br />

<strong>di</strong>rettorio (source): in WinFS uno stesso file può essere contenuto in <strong>di</strong>verse <strong>di</strong>rectory,<br />

quin<strong>di</strong> ci possono essere <strong>di</strong>fferenti relazioni che hanno quest’ultimo come obiettivo. Se<br />

si elimina una <strong>di</strong> queste relazioni, l’istanza del file non esisterà più nella <strong>di</strong>rectory che<br />

rappresenta la sorgente della relazione appena eliminata, ma il file continuerà ad essere<br />

presente in memoria fino a quando rimarrà in relazione con un’altra istanza <strong>di</strong> <strong>di</strong>rectory.<br />

Le relazioni <strong>di</strong> riferimento non controllano la vita dell’istanza che hanno come<br />

obiettivo, e possono essere stabilite anche tra istanze residenti su <strong>di</strong>schi o partizioni<br />

<strong>di</strong>verse.<br />

Così come tutti i tipi <strong>di</strong> dato, le relazioni possono avere proprietà e campi che possono<br />

essere <strong>di</strong> tipo scalare o “NestedElement”: per esempio una relazione tra una persona ed<br />

un’istanza <strong>di</strong> ufficio può avere come campi la data d’inizio e la data <strong>di</strong> fine, o anche<br />

l’orario <strong>di</strong> lavoro.<br />

61


5.7 Usare il modello dati <strong>di</strong> WinFS<br />

L’API messa a <strong>di</strong>sposizione dal file system permette <strong>di</strong> utilizzare gli oggetti attraverso il<br />

linguaggio <strong>di</strong> tipo CLR oppure in linguaggio <strong>di</strong> tipo COM nativo, così da permettere<br />

agli sviluppatori <strong>di</strong> aggiungere potenzialità come query, navigazione ed eventi dato alle<br />

loro applicazioni.<br />

Tutti i tipi <strong>di</strong> Windows definiti come schema dati WinFS hanno una loro classe<br />

corrispondente nell’API, chiamata “data class”; in Longhorn sono definiti 28 schemi<br />

dati con relative classi nell’application program interface <strong>di</strong> WinFS. Possono essere<br />

inoltre definiti nuovi schemi o estendere quelli già esistenti per poi generare le classi<br />

equivalenti <strong>di</strong> tipo .NET e COM per mettere a <strong>di</strong>sposizione delle applicazioni questi<br />

nuovi tipi.<br />

Per potenziare le capacità <strong>di</strong> ricerca, il sistema utilizza il linguaggio OPATH, un<br />

linguaggio <strong>di</strong> ricerca ed or<strong>di</strong>namento <strong>degli</strong> oggetti che permette <strong>di</strong> eseguire ricerche<br />

complesse all’interno delle partizioni <strong>di</strong> memoria; linguaggio che per fare questo<br />

implementa la possibilità <strong>di</strong> usare filtri che utilizzano la corrispondenza <strong>di</strong> particolari<br />

proprietà o campi <strong>di</strong> un oggetto.<br />

Nei tra<strong>di</strong>zionali linguaggi <strong>di</strong> programmazione, le relazioni tra gli oggetti venivano<br />

espresse tramite i campi <strong>di</strong> una classe; con WinFS <strong>di</strong>venta possibile, per esempio,<br />

ricercare gli autori dei documenti attraverso query che coinvolgono le relazioni tra gli<br />

oggetti.<br />

5.8 ADO.NET e WinFS<br />

Oltre al linguaggio OPATH, questo file system fornisce anche un’API per accedere ai<br />

dati in memoria con query <strong>di</strong> tipo OLEDB e ADO.NET. Il componente principale <strong>di</strong><br />

quest’API per l’accesso relazionale ai dati è il linguaggio T-SQL, che permette <strong>di</strong><br />

effettuare query basate su SQL all’interno della base <strong>di</strong> dati (partizione <strong>di</strong> memoria) <strong>di</strong><br />

WinFS. Utilizzando questa funzione si possono ricercare ed utilizzare i dati come<br />

all’interno <strong>di</strong> un database relazionale, e si mette a <strong>di</strong>sposizione delle applicazioni un<br />

maggior controllo sui dati presenti in memoria secondaria ed una maggiore capacità <strong>di</strong><br />

ricerca.<br />

62


5.9 Notificazioni<br />

Questo file system memorizza un gran numero d’oggetti tra loro correlati, e in certi casi<br />

può essere utile poter controllare le mo<strong>di</strong>fiche che sono eseguite su alcuni <strong>di</strong> essi; per<br />

fare ciò WinFS implementa un meccanismo capace <strong>di</strong> notificare all’utente quando sono<br />

eseguite delle mo<strong>di</strong>fiche o quando viene eliminato un oggetto. Le notificazioni <strong>di</strong> questo<br />

file system sono analoghe a quelle <strong>di</strong> tipo CLR, e la classe principale per poter utilizzare<br />

questo sistema è la classe “ItemWatcher”.<br />

63


6 Confronto Prestazionale<br />

Di seguito sono riportati i risultati <strong>di</strong> due test, che sono stati pubblicati sul sito internet<br />

<strong>di</strong> Namesys (www.namesys.com), per mettere a confronto le prestazioni <strong>di</strong> Ext3 e<br />

Reiser4. Non verranno trattate le prestazioni <strong>di</strong> WinFS per via della <strong>di</strong>fficoltà a reperire<br />

il file system (che al momento della stesura <strong>di</strong> questa tesi non è ancora stato rilasciato in<br />

una versione ufficiale, ma è ancora in fase <strong>di</strong> test), ed anche perché mettendolo a<br />

confronto con altri file system tra<strong>di</strong>zionali, per avere termini <strong>di</strong> paragone comuni, si<br />

dovrebbero prendere in considerazione solo le prestazioni riguardanti l’I/O <strong>di</strong> generici<br />

file, senza valutare le performance fornite da tutte le altre funzionalità che WinFS<br />

implementa, come per esempio la ricerca <strong>di</strong> oggetti utilizzando il modello<br />

Entity/Relationship, o i servizi d’in<strong>di</strong>cizzazione.<br />

I risultati sotto riportati sono stati ottenuti utilizzando due benchmark <strong>di</strong>versi, uno per<br />

valutare le prestazioni nel gestire molti file <strong>di</strong> piccole <strong>di</strong>mensioni, chiamato Mongo, ed<br />

uno, lo Slow benchmark, per confrontare i file system nella scrittura <strong>di</strong> grossi file in<br />

memoria.<br />

Entrambi i programmi possono essere reperiti all’in<strong>di</strong>rizzo<br />

http://www.namesys.com/benchmarks/.<br />

Dei due programmi che sono stati utilizzati il primo, Mongo, è stato scritto da Namesys,<br />

e la scelta <strong>di</strong> utilizzare un benchmark scritto dagli sviluppatori <strong>di</strong> Reiser4 è dovuta alla<br />

completezza del programma, che in un’unica esecuzione può valutare le prestazioni nel<br />

compiere tutte le normali operazioni sui file che un file system si trova normalmente a<br />

dover affrontare. L’altro benchmarck, invece, è un programma standard scritto da Jon<br />

Burgess, che viene utilizzato come termine <strong>di</strong> confronto anche con altri file systems.<br />

6.1 Mongo Benchmark<br />

Mongo è uno script per Linux che funziona nel modo seguente:<br />

1. All’avvio dello script vengono create n partizioni <strong>di</strong> altrettanti file system<br />

<strong>di</strong>versi, che vengono specificati a riga <strong>di</strong> comando. Una volta eseguito ciò, il<br />

programma si compone <strong>di</strong> 8 fasi <strong>di</strong>verse, in ciascuna delle quali sono valutate le<br />

prestazioni dei file system creati.<br />

64


2. La prima fase consiste nella creazione <strong>di</strong> un albero <strong>di</strong> lunghezza casuale che<br />

viene riempito da files la cui <strong>di</strong>mensione varia da 0 ad un valore massimo<br />

stabilito in modo <strong>di</strong>pendente da una funzione <strong>di</strong> <strong>di</strong>stribuzione predefinita. In<br />

altre parole, tra tutti i file che si andranno a creare, il numero maggiore <strong>di</strong> essi<br />

avrà una <strong>di</strong>mensione che ricade attorno ad un valore prestabilito, mentre gli altri<br />

che si scostano da questo valore saranno in percentuale sempre minore man<br />

mano che ci si allontana dal valore <strong>di</strong> riferimento.<br />

3. La fase successiva è quella <strong>di</strong> copia: finita la creazione dell’albero vengono<br />

confrontati i tempi impiegati dai vari file system per la copia dei file creati.<br />

Questa operazione non è eseguita creando un semplice riferimento ad inode già<br />

esistenti, ma viene effettuata copiando ed allocando “ex-novo” in blocchi liberi<br />

<strong>di</strong> memoria i file precedentemente creati. Una copia <strong>di</strong> questo tipo comporta la<br />

lettura (e scrittura) non soltanto <strong>degli</strong> inode ma anche <strong>di</strong> tutti i blocchi dati<br />

appartenenti ai file.<br />

4. Nella fase <strong>di</strong> append lo script legge i nomi dei file creati ed aggiunge in coda ad<br />

essi un numero <strong>di</strong> byte calcolato come <strong>di</strong>mensione_del_file *<br />

fattore_standard_<strong>di</strong>_aggiunta. Queste operazioni possono essere eseguite anche<br />

utilizzando l’opzione fsync().<br />

5. La fase successiva è quella <strong>di</strong> mo<strong>di</strong>fy: il programma legge tutti i file e mo<strong>di</strong>fica<br />

in essi un numero <strong>di</strong> byte calcolato come <strong>di</strong>mensione_del_file *<br />

fattore_standard_<strong>di</strong>_mo<strong>di</strong>fica, partendo da una posizione casuale all’interno del<br />

file. Anche questa operazione può essere effettuata usando fsync().<br />

6. La fase <strong>di</strong> overwrite, che segue quella <strong>di</strong> mo<strong>di</strong>fica, sovrascrive i file utilizzando<br />

la routine impiegata nella fase precedente, utilizzando però in<br />

fattore_standard_<strong>di</strong>_mo<strong>di</strong>fica=1, andando quin<strong>di</strong> a mo<strong>di</strong>ficare (sovrascrivere)<br />

tutti i byte <strong>di</strong> ciascun file.<br />

7. La fase <strong>di</strong> read, poi legge tutti i file creati in un or<strong>di</strong>ne prestabilito, e mette a<br />

confronto i tempi impiegati.<br />

8. La fase <strong>di</strong> stats, invece, valuta la velocità d’esecuzione del comando “find –<br />

type f” su tutte le partizioni create. In altre parole in questa fase viene valutata<br />

la velocità nella ricerca <strong>di</strong> tutti i file che sono stati creati, in quanto il comando<br />

“find –type f” cerca tutti i file regolari memorizzati nella partizione.<br />

9. L’ultima fase eseguita è quella <strong>di</strong> delete, che valuta la velocità nel rimuovere,<br />

tramite il comando “rm –r”, tutti i file creati.<br />

65


Lo script può poi eseguire, se specificato, due fasi speciali che richiedono l’opzione<br />

DD_MBCOUNT, che sono la scrittura e lettura <strong>di</strong> file <strong>di</strong> grosse <strong>di</strong>mensioni.<br />

Nel test eseguito con questo programma l’81% dei file creati era <strong>di</strong> <strong>di</strong>mensione tra 0 e<br />

10KB, mentre la <strong>di</strong>mensione del restante 19% apparteneva all’intervallo 10-100KB.<br />

La macchina su cui è stato eseguito lo script ha le seguenti caratteristiche:<br />

Processore Intel Xeon 2.40 GHz con 256 MB <strong>di</strong> RAM e 1 MB <strong>di</strong> cache, sul quale<br />

viene eseguita la versione 2.6.8.1 del Kernel <strong>di</strong> Linux.<br />

La tabella 6.1 riporta i risultati ottenuti.<br />

Ad ogni lettera corrisponde un tipo <strong>di</strong> file system, e più precisamente:<br />

A corrisponde a Reiser4<br />

B corrisponde a Reiser4 montato con l’opzione <strong>di</strong> solo utilizzo dei blocchi non<br />

formattati per contenere i dati appartenenti ad uno stesso file (estensione dei<br />

blocchi, o extent).<br />

C corrisponde a Reiser3<br />

D corrisponde a Ext3 con l’opzione <strong>di</strong> mount data=writeback, cioè con<br />

l’opzione <strong>di</strong> journaling rapido (writeback continuo, appunto,e minori garanzie <strong>di</strong><br />

consistenza dei dati).<br />

E corrisponde a Ext3 con l’opzione <strong>di</strong> mount data=journal, cioè ad Ext3 con<br />

journaling completo <strong>di</strong> dati utente e metadata.<br />

F corrisponde a Ext3 con l’opzione <strong>di</strong> mount data=ordered, in altre parole<br />

Ext3 con metadata-only journaling.<br />

La tabella mostra la misurazione dei tempi <strong>di</strong> Reiser4 nella prima colonna, mentre nelle<br />

altre è mostrato il valore del rapporto tra i tempi <strong>di</strong> questo file system rispetto a quelli<br />

impiegati dagli altri misurati. I valori in rosso sono quelli in cui il tempo impiegato da<br />

Reiser4 è inferiore rispetto a quello dell'altro filesystem (rapporto maggiore <strong>di</strong> 1),<br />

mentre quelli in verde identificano le prove in cui i tempi impiegati da Reiser4 sono<br />

maggiori <strong>di</strong> quelli del file system usato come confronto.<br />

66


REAL_TIME<br />

A B/A C/A D/A E/A F/A<br />

CREATE 3,796 0,686 1983,000 2592,000 3010,000 2256,000<br />

COPY 9,128 0,672 1674,000 2241,000 2105,000 1819,000<br />

READ 7,815 1007,000 1617,000 1282,000 1295,000 1250,000<br />

STATS 1,008 0,672 1162,000 0,655 0,655 0,655<br />

DELETE 156.84 0,690 0,162 1264,000 1270,000 1216,000<br />

CPU_TIME<br />

A B/A C/A D/A E/A F/A<br />

CREATE 1,301 0,670 0,574 2577,000 2529,000 2802,000<br />

COPY 2,253 0,651 0,550 1694,000 2004,000 1860,000<br />

READ 38.61 1002,000 0,494 0,427 0,432 0,427<br />

STATS 0,480 0,656 0,498 0,459 0,468 0,457<br />

DELETE 2,212 0,651 0,306 0,145 0,149 0,149<br />

CPU_UTIL<br />

A B/A C/A D/A E/A F/A<br />

CREATE 0,960 0,681 0,243 0,549 0,513 1000,000<br />

COPY 0,667 0,692 0,319 0,460 0,583 0,618<br />

READ 0,545 0,691 0,306 0,361 0,359 0,370<br />

STATS 1,032 0,674 0,408 0,486 0,491 0,484<br />

DELETE 0,766 0,658 1758,000 0,109 0,111 0,116<br />

DISK_USAGE<br />

A B/A C/A D/A E/A F/A<br />

CREATE 1978440,000 1000,000 1088,000 1108,000 1108,000 1108,000<br />

COPY 3956708,000 1000,000 1088,000 1108,000 1108,000 1108,000<br />

READ 3956708,000 1000,000 1088,000 1108,000 1108,000 1108,000<br />

STATS 3956708,000 1000,000 1088,000 1108,000 1108,000 1108,000<br />

DELETE 4,000 1000,000 0.000 0.000 0.000 0.000<br />

Tabella 6.1: Confronto prestazionale utilizzando lo script Mongo.<br />

I risultati mostrati in tabella 6.1 sono stati ottenuti utilizzando il parametro<br />

file_size=8192. Questo parametro viene passato al generatore casuale dell’albero,<br />

che lo utilizza per elaborare la <strong>di</strong>stribuzione della <strong>di</strong>mensione dei file che andranno ad<br />

essere creati.<br />

La prima cosa che salta all’occhio dai risultati dei test è il grande risparmio <strong>di</strong> spazio su<br />

<strong>di</strong>sco che si ottiene grazie al sistema <strong>di</strong> block sharing <strong>di</strong> Reiser4, che è circa <strong>di</strong> tre or<strong>di</strong>ni<br />

<strong>di</strong> grandezza. Dalle altre tabelle, invece, si evince che Reiser4 impiega dei tempi<br />

67


inferiori 5 rispetto agli altri file system per compiere le comuni operazioni sui file, ma<br />

risulta essere un co<strong>di</strong>ce più pesante <strong>degli</strong> altri, in quanto occupa la CPU per tempi più<br />

lunghi e la sfrutta più intensivamente <strong>degli</strong> altri file system usati come paragone: solo<br />

per le operazioni <strong>di</strong> creazione e copia <strong>di</strong> un file, infatti, Resiser4 segna tempi d’utilizzo<br />

del processore <strong>di</strong> circa tre or<strong>di</strong>ni <strong>di</strong> grandezza inferiori rispetto a Ext3. Proprio il fatto<br />

che Reiser4 impieghi meno tempo “utente” per eseguire le proprie operazioni<br />

nonostante sfrutti per più tempo la CPU, può essere in<strong>di</strong>ce <strong>di</strong> una maggiore integrazione<br />

tra le sue varie componenti, che interagiscono tra loro utilizzando interfacce e co<strong>di</strong>ce<br />

meno complesso, rendendo l’intero co<strong>di</strong>ce più uniforme e meno stratificato <strong>di</strong> quello<br />

che può essere quello <strong>di</strong> Ext3.<br />

6.2 Slow Benchmark<br />

I test eseguiti per confrontare le prestazioni <strong>di</strong> lettura e scrittura <strong>di</strong> gran<strong>di</strong> quantità <strong>di</strong><br />

dati sono stati fatti con il programma slow.c. Questo programma esegue la scrittura e la<br />

lettura <strong>di</strong> uno stream <strong>di</strong> dati <strong>di</strong> <strong>di</strong>mensione specificata, ed una volta terminate queste<br />

operazioni, le ripete per testare le prestazioni in lettura/scrittura <strong>di</strong> 2, 4 e 8 stream dello<br />

stesso tipo in parallelo. La <strong>di</strong>mensione del file che è stato utilizzato per eseguire la<br />

prova è <strong>di</strong> 1 GB, e nei grafici seguenti è riportato il valore me<strong>di</strong>o <strong>di</strong> throughput per i<br />

vari filesystem testati. Di seguito è riportata la configurazione hardware del computer su<br />

cui sono stati eseguiti i test, e nei grafici vengono mostrati i risultati delle prove.<br />

La configurazione della macchina su cui è stato eseguito il benchmark è la seguente:<br />

Processore AMD Athlon 1,40 GHz con 256 Kb <strong>di</strong> cache, sul quale viene eseguitala<br />

versione 2.6.5 del Kernel <strong>di</strong> Linux, e per eseguire il test si è scelto <strong>di</strong> scrivere file<br />

della <strong>di</strong>mensione <strong>di</strong> 1 GB ciascuno (questo esplicitandolo a riga <strong>di</strong> comando,<br />

lanciando il programma con il comando ./slow foo 1000).<br />

test : ./slow foo 1000<br />

5 Questo in termini <strong>di</strong> tempo reale, cioè il tempo che il file system impiega, considerando l’utilizzo<br />

concorrente della CPU da parte <strong>degli</strong> altri applicativi <strong>di</strong> sistema, per compiere un’operazione agli occhi<br />

dell’utente.<br />

68


TEST DI SCRITTURA<br />

MB/s<br />

30<br />

25<br />

20<br />

15<br />

10<br />

5<br />

0<br />

Write 1 Write 2 Write 4 Write 8<br />

Numero <strong>di</strong> stream paralleli<br />

Ext2<br />

Reiser4<br />

Reiser4 Extents Only<br />

Ext3 Ordered<br />

Ext3 Journal<br />

Reiser3<br />

Resier4 no tail<br />

Figura 6.1: Risultati del test <strong>di</strong> scrittura effettuati con slow.c.<br />

TEST DI LETTURA<br />

MB/s<br />

30<br />

25<br />

20<br />

15<br />

10<br />

5<br />

0<br />

Read 1 Read 2 Read 4 Read 8<br />

Numero <strong>di</strong> stream paralleli<br />

Ext2<br />

Reiser4<br />

Reiser4 Extents Only<br />

Ext3 Ordered<br />

Ext3 Journal<br />

Reiser3<br />

Resier4 no tail<br />

Figura 6.2: Risultati del test <strong>di</strong> lettura effettuato con slow.c.<br />

Le figure 6.1 e 6.2 evidenziano chiaramente il gap prestazionale tra Ext3 e Reiser4, che<br />

si manifesta soprattutto quando i file system devono trattare simultaneamente <strong>di</strong>versi<br />

stream <strong>di</strong> dati, mentre si nota che nel test <strong>di</strong> lettura <strong>di</strong> un solo flusso dati, per volta tutti i<br />

file system hanno dato più o meno le stesse prestazioni.<br />

7 Conclusioni<br />

In conclusione, si può <strong>di</strong>re che tutti i file system presi in analisi in questo elaborato<br />

sod<strong>di</strong>sfano ampiamente le esigenze ed i requisiti illustrati nel secondo capitolo,<br />

nonostante ciascuno <strong>di</strong> essi abbia caratteristiche implementative molto <strong>di</strong>verse dagli<br />

altri. Ext3 ha in sè tutti i pregi e i <strong>di</strong>fetti <strong>di</strong> essere stato sviluppato molto più <strong>di</strong> ReiserFS<br />

sfruttando l’open source: da un lato può vantare <strong>di</strong> essere un file system stabile e<br />

69


maturo, nonché forse il più <strong>di</strong>ffuso tra gli utenti Linux, ma d’altra parte paga in termini<br />

<strong>di</strong> prestazioni lo scotto <strong>di</strong> essere stato sviluppato in modo <strong>di</strong>somogeneo da molte<br />

persone, magari neanche in contatto tra loro. ReiserFS, pur essendo un software libero,<br />

può vantare dalla sua parte il fatto <strong>di</strong> essere stato sviluppato principalmente da un unico<br />

team <strong>di</strong> programmatori (Namesys) fin dalle sue prime versioni, e questo si manifesta in<br />

una maggiore integrazione tra le varie parti del software, in modo da ottimizzare al<br />

massimo i tempi <strong>di</strong> esecuzione del maggior numero possibile <strong>di</strong> operazioni che il co<strong>di</strong>ce<br />

può eseguire. Questo maggior grado <strong>di</strong> integrazione ed ottimizzazione lo si nota anche<br />

dai risultati dei benchmark: il tempo utente impiegato nell’esecuzione <strong>di</strong> molte<br />

operazioni è minore rispetto a quello impiegato da Ext3 nonostante l’impiego della CPU<br />

sia maggiore <strong>di</strong> quest’ultimo (sintomo, questo, <strong>di</strong> una maggiore complessità <strong>di</strong> co<strong>di</strong>ce).<br />

Il file system che, tra i tre presi in analisi, fornisce all’utente il maggior numero <strong>di</strong><br />

funzionalità è sicuramente WinFS: la gran parte delle nuove funzioni, secondo la<br />

documentazione finora rilasciata, è stata costruita ad un livello superiore rispetto a<br />

quelli <strong>di</strong> gestione della memoria fisica e della sua organizzazione, e questo,<br />

probabilmente, si tradurrà in un maggior carico <strong>di</strong> lavoro per la CPU; inoltre, visto che<br />

il file system è ancora in fase <strong>di</strong> test e quin<strong>di</strong> soggetto a continue correzioni, non si è<br />

ancora in grado <strong>di</strong> valutare appieno in che modo tutte le funzioni messe a <strong>di</strong>sposizione<br />

interagiscono col resto del sistema operativo. Anche se WinFS risulta essere ancora un<br />

software molto immaturo, che probabilmente subirà molte mo<strong>di</strong>fiche, c’è da <strong>di</strong>re che<br />

questo file system prosegue ed amplia la strada della gestione dei dati su <strong>di</strong>sco alla<br />

stregua <strong>di</strong> un database relazionale, che è stata recentemente sperimentata anche<br />

nell’ultima versione del sistema operativo Apple, primo software a sperimentare ed<br />

implementare una gestione dei dati <strong>di</strong> questo tipo. In futuro, infatti, ci sarà da aspettarsi<br />

che molti altri file system implementino questo modo <strong>di</strong> archiviare le informazioni, che,<br />

pur essendo ancora da affinare, può fornire una flessibilità nel gestire i dati molto<br />

maggiore rispetto a sistemi <strong>di</strong> tipo tra<strong>di</strong>zionale. Un nuovo obiettivo per i team <strong>di</strong><br />

sviluppo dei file system, può essere sicuramente quello <strong>di</strong> costruire una piattaforma per<br />

gestire le informazioni su <strong>di</strong>sco come una base <strong>di</strong> dati (anche implementando il modello<br />

relazionale), che sia la più snella e compatta possibile e che, questo soprattutto per i<br />

sistemi UNIX-like, sia il più possibile portabile e trasparente, in modo da dover<br />

effettuare il minor numero possibile <strong>di</strong> mo<strong>di</strong>fiche al co<strong>di</strong>ce <strong>di</strong> livello superiore, VFS in<br />

primis.<br />

70


Appen<strong>di</strong>ce A – Gli Alberi Di Dati<br />

L’or<strong>di</strong>namento dei dati in file e cartelle con cui l’utente si trova a dover operare, è<br />

implementato nella maggior parte dei sistemi operativi, Linux compreso, attraverso<br />

strutture dati chiamate alberi.<br />

71


A.1. Definizioni<br />

Un albero è una struttura gerarchica formata da <strong>di</strong>versi no<strong>di</strong>, ognuno dei quali può<br />

contenere dati e riferimenti ad eventuali altri no<strong>di</strong> ad esso collegati; ogni albero ha un<br />

solo nodo <strong>di</strong> partenza dal quale è possibile raggiungere tutti gli altri no<strong>di</strong> dell’albero<br />

scendendo <strong>di</strong> livello man mano che si attraversa la struttura dati. Una definizione <strong>di</strong><br />

precisa <strong>di</strong> albero può essere la seguente:<br />

1. Un albero è un insieme <strong>di</strong> no<strong>di</strong> organizzati in un nodo ra<strong>di</strong>ce, e in zero o più<br />

ad<strong>di</strong>zionali insiemi <strong>di</strong> no<strong>di</strong> chiamati sottoalberi.<br />

2. Ognuno dei sottoalberi è esso stesso un albero<br />

3. Nessun nodo dell’albero contiene riferimenti alla ra<strong>di</strong>ce, ed esattamente un<br />

riferimento da un nodo nell’albero punta ad ogni altro nodo che non sia ra<strong>di</strong>ce<br />

dell’albero.<br />

4. Il nodo ra<strong>di</strong>ce contiene un riferimento ad ognuno dei suoi sottoalberi,<br />

riferimento che è un puntatore alla ra<strong>di</strong>ce del sottoalbero [reiser].<br />

Figura A.1: Un esempio <strong>di</strong> albero bilanciato <strong>di</strong> altezza=4 e fanout=3<br />

La figura A.1 mostra un classico esempio d’albero dati, che può essere utilizzato per<br />

fornire alcune altre definizioni:<br />

Sarà chiamato ra<strong>di</strong>ce dell’albero il nodo <strong>di</strong> livello più alto della struttura.<br />

Verranno chiamati foglia (leaf) dell’albero tutti i no<strong>di</strong> che non<br />

contengono riferimenti a sottoalberi (non fungono cioè da ra<strong>di</strong>ce <strong>di</strong> un<br />

eventuale sottoalbero).<br />

72


Sarà chiamato altezza dell’albero il numero complessivo <strong>di</strong> livelli che la<br />

struttura sviluppa a partire dalla sua ra<strong>di</strong>ce.<br />

Verrà chiamato fanout <strong>di</strong> un nodo il numero <strong>di</strong> riferimenti ad altri no<strong>di</strong><br />

che esso contiene; analogamente può essere definito il fanout dell’albero,<br />

che <strong>di</strong>venta il numero <strong>di</strong> riferimenti ad altri no<strong>di</strong> che ciascun nodo<br />

contiene (foglie escluse), nel caso in cui tutti no<strong>di</strong> dell’albero contengano<br />

lo stesso numero <strong>di</strong> riferimenti.<br />

Un’eventuale ricerca <strong>di</strong> dati all’interno dell’albero <strong>di</strong> figura A.1 partirebbe dal nodo<br />

ra<strong>di</strong>ce (l’unico nodo interno <strong>di</strong> livello 4), attraverserebbe due livelli <strong>di</strong> no<strong>di</strong> interni, e<br />

terminerebbe su <strong>di</strong> una foglia (che non ha figli) che contiene il dato da ricercare.<br />

A.2. Chiavi<br />

Nei sistemi informatici, ad ogni oggetto che viene immagazzinato all’interno <strong>di</strong> questa<br />

struttura è assegnata una chiave identificativa, che sarà utilizzata per ritrovare l’oggetto<br />

stesso durante ogni ricerca. Considerando che nei file-system un nodo è composto da<br />

uno o eventualmente più blocchi <strong>di</strong> memoria, le chiavi <strong>degli</strong> oggetti saranno dei numeri<br />

univoci <strong>di</strong> <strong>di</strong>mensione predefinita, che possono contenere al loro interno più o meno<br />

informazioni sull’oggetto a cui fanno riferimento a seconda della loro lunghezza. In<br />

molti file system, la generazione delle chiavi per gli oggetti avviene attraverso algoritmi<br />

che cercano <strong>di</strong> includere nella chiave stessa il maggior numero possibile d’informazioni,<br />

riguardanti l’oggetto che devono riferire, proce<strong>di</strong>mento che comunque aiuta alla<br />

generazione <strong>di</strong> set <strong>di</strong> chiavi tutte <strong>di</strong>fferenti tra loro 6 .<br />

La scelta <strong>di</strong> identificare gli oggetti presenti nell’albero attraverso delle chiavi offre<br />

sicuramente il vantaggio <strong>di</strong> velocizzare le operazioni <strong>di</strong> ricerca, ma d’altro canto va ad<br />

influire sul fanout dell’albero stesso: a parità <strong>di</strong> <strong>di</strong>mensione <strong>di</strong> blocco, infatti, maggiore<br />

sarà la <strong>di</strong>mensione delle chiavi che il blocco contiene, minore sarà il numero <strong>di</strong> oggetti a<br />

cui il nodo può riferire.<br />

6 Maggiore sarà il numero <strong>di</strong> informazioni contenute nella chiave, maggiori saranno le possibilità <strong>di</strong><br />

descrivere tutti i particolari <strong>di</strong> un oggetto, in modo così da poter generare due chiavi <strong>di</strong>verse per due<br />

oggetti che <strong>di</strong>fferiscono anche <strong>di</strong> poco tra loro per forma e contenuti.<br />

73


A.3. Bilanciamento della struttura<br />

Passiamo ora ad analizzare quali meccanismi possono essere utilizzati per velocizzare le<br />

ricerca <strong>degli</strong> oggetti all’interno dell’albero.<br />

Esistono due principi <strong>di</strong> gestione e <strong>di</strong> organizzazione delle strutture dati che sono<br />

comunemente utilizzati in tutti i file system, questi principi sono:<br />

Bilanciamento in altezza dell’albero: un albero bilanciato in altezza è una<br />

struttura tale che ogni percorso <strong>di</strong> ricerca che inizi della ra<strong>di</strong>ce e termini in una<br />

qualsiasi delle foglie, ha esattamente la stessa lunghezza (dove per lunghezza<br />

s’intende il numero <strong>di</strong> no<strong>di</strong> attraversati dalla ra<strong>di</strong>ce fino alla foglia). In altre<br />

parole questo principio afferma che più si riesce ad uniformare l’altezza delle<br />

foglie (che nei file system sono generalmente gli elementi che contengono i dati<br />

utente), più i tempi d’accesso ai dati saranno me<strong>di</strong>amente uniformi, evitando<br />

così (nei casi più sfavorevoli) <strong>di</strong> dover attendere molto più tempo per accedere a<br />

dati utilizzati <strong>di</strong> frequente, rispetto a quello che servirebbe per accedere a dati<br />

non tanto utilizzati ma memorizzati all’interno <strong>di</strong> un nodo molto vicino alla<br />

ra<strong>di</strong>ce.<br />

Bilanciamento del fanout (ampiezza) dell’albero: un albero bilanciato in<br />

ampiezza è una struttura tale che ogni suo nodo contiene esattamente lo stesso<br />

numero <strong>di</strong> riferimenti a sottoalberi. Una struttura <strong>di</strong> questo tipo accelera<br />

notevolmente le operazioni <strong>di</strong> ricerca, specialmente quando i file system<br />

utilizzano algoritmi <strong>di</strong> ricerca <strong>di</strong> tipo non ricorsivo, che navigano l’albero<br />

confrontando nodo per nodo le chiavi che questi contengono, fino a risalire<br />

all’oggetto da ricercare (a <strong>di</strong>fferenza <strong>degli</strong> algoritmi ricorsivi, che ad ogni nodo<br />

istanziano tante nuove funzioni <strong>di</strong> ricerca quanti sono i riferimenti che il nodo<br />

contiene, in<strong>di</strong>pendentemente dal fatto che il percorso porti o no a raggiungere il<br />

dato cercato). Con un bilanciamento <strong>di</strong> questo tipo si va quin<strong>di</strong> ad uniformare il<br />

tempo me<strong>di</strong>o <strong>di</strong> ricerca <strong>degli</strong> oggetti, in quanto si va a stabilire che la macchina<br />

impiegherà lo stesso tempo per scegliere quale percorso seguire<br />

in<strong>di</strong>pendentemente dal nodo dell’albero in cui si trova.<br />

74


Figura A.2: Esempio d’albero non bilanciato.<br />

Confrontando la figura A.1 con la figura A.2 balza subito all’occhio la <strong>di</strong>fferenza tra<br />

alberi bilanciati e non bilanciati: non tutti i no<strong>di</strong> dell’albero <strong>di</strong> figura A.2 hanno lo<br />

stesso fanout, e non tutte le sue foglie hanno la stessa <strong>di</strong>stanza dalla ra<strong>di</strong>ce.<br />

Nella figura A.3 è mostrato un esempio <strong>di</strong> come un albero sbilanciato possa rallentare le<br />

operazioni <strong>di</strong> ricerca dei dati.<br />

A<br />

B<br />

Albero 1 Albero 2<br />

C<br />

Figura A.3: Esempi <strong>di</strong> albero bilanciato e fortemente sbilanciato.<br />

D<br />

I due alberi <strong>di</strong> figura A.3 hanno entrambi lo stesso numero <strong>di</strong> no<strong>di</strong> (15), il primo, però<br />

ha altezza=4 e fanout=2, mentre il secondo ha altezza massima=8 e fanout me<strong>di</strong>o=2<br />

(non tutti i blocchi hanno lo stesso numero <strong>di</strong> riferimenti ad altri blocchi sottostanti). Se<br />

si dovesse compiere una ricerca all’interno delle due strutture, supponendo che ogni<br />

nodo contenga sia dati che chiavi e riferimenti, si avrebbe che:<br />

Per reperire un qualsiasi dato all’interno dell’albero 1 bisognerebbe effettuare un<br />

massimo <strong>di</strong> tre accessi a sottoalberi e foglie.<br />

75


Per recuperare le informazioni nell’albero 2 si potrebbe eseguire anche un solo<br />

accesso (da A a B), ma nel peggiore dei casi (per leggere un dato contenuto nel<br />

blocco D o nell’altro dello stesso livello) bisognerebbe effettuare 7 accessi ai<br />

blocchi sottostanti; e già per accedere ai dati contenuti nel blocco C sarebbe<br />

necessario un numero <strong>di</strong> letture maggiore del numero massimo possibile nel<br />

primo albero.<br />

Tutto ciò che è stato detto in precedenza, trova una <strong>di</strong>retta corrispondenza nel modo in<br />

cui i file system gestiscono l’organizzazione dei dati in file e cartelle. La ra<strong>di</strong>ce<br />

dell’albero viene in genere rappresentata come la <strong>di</strong>rectory <strong>di</strong> più alto livello, dalla<br />

quale si <strong>di</strong>ramano tutte le altre e, generalmente, i riferimenti (ed eventualmente anche i<br />

dati) che sono contenuti all’interno dei no<strong>di</strong> sono rappresentati come tutti gli elementi<br />

che una cartella contiene.<br />

La scelta <strong>di</strong> relegare i dati soltanto nelle foglie, riservando così ai no<strong>di</strong> il solo scopo <strong>di</strong><br />

contenere il riferimento ad altri dati, solo <strong>di</strong> recente è <strong>di</strong>ventata comune alla maggior<br />

parte dei file system, in quanto rappresenta un ottimo modo per aumentare il fanout (e<br />

quin<strong>di</strong> il massimo numero d’oggetti memorizzabili in un <strong>di</strong>rettorio) ma allo stesso<br />

tempo va ad aumentare l’altezza dell’albero. Questo tipo d’implementazione della<br />

struttura prende il nome <strong>di</strong> B+Tree, ed è usata da tutti i file system <strong>di</strong> recente sviluppo<br />

(Ext3 e ReiserFS compresi), in quanto risponde alla necessità <strong>di</strong> poter realizzare<br />

<strong>di</strong>rettori che contengono un numero sempre maggiore <strong>di</strong> file.<br />

Un altro accorgimento per alleggerire il carico <strong>di</strong> lavoro della macchina, è quello <strong>di</strong><br />

eseguire le operazioni <strong>di</strong> bilanciamento dell’albero soltanto quando la CPU non è<br />

occupata da altri applicativi, oppure solo quando strettamente necessario: un sistema <strong>di</strong><br />

questo tipo prende il nome <strong>di</strong> Dancing Tree o B*Tree, e va a memorizzare le<br />

informazioni senza perseguire una politica <strong>di</strong> mantenimento del bilanciamento, e relega<br />

tutte le operazioni per ribilanciare la struttura in un secondo momento (per questo<br />

motivo può capitare che il carico <strong>di</strong> lavoro per eseguire queste operazioni sia molto<br />

pesante, quando, invece, bilanciando l’albero ad ogni singola scrittura, si hanno<br />

molteplici operazioni sull’albero ma circa tutte con lo stesso peso). Il Dancing Tree, tra<br />

i file system presi in analisi, è adottato solo da ReiserFS, in quanto gli altri eseguono il<br />

bilanciamento ad ogni scrittura dell’albero.<br />

76


Appen<strong>di</strong>ce B: Access Control Lists (ACLs)<br />

Di seguito sarà illustrato il metodo con cui i file system implementano la gestione dei<br />

permessi d’accesso ai file; questo metodo è un ampliamento ed una generalizzazione<br />

<strong>degli</strong> standard precedenti, e prende il nome <strong>di</strong> Access Control List (ACL) [POSIX-<br />

ACL].<br />

B.1. Evoluzione dei permessi d’accesso nei file system<br />

Tra<strong>di</strong>zionalmente tutti i sistemi che supportano la famiglia <strong>di</strong> standard POSIX (portable<br />

operating system interface, adottata anche da UNIX e Linux), implementano un sistema<br />

<strong>di</strong> permessi molto semplice ma altrettanto potente: ogni oggetto presente nel file system<br />

è associato a tre set <strong>di</strong> permessi che ne definiscono i criteri d’accesso per le classi<br />

proprietario (owner), gruppo proprietario (owning group) ed altri utenti (others), ed ogni<br />

set contiene i permessi <strong>di</strong> lettura (r), scrittura (w) ed esecuzione (x). Uno schema <strong>di</strong><br />

questo tipo viene implementato usando soltanto nove bit più alcuni altri per gestire<br />

funzioni speciali, ed è sufficiente per gestire la maggior parte dei casi d’uso <strong>degli</strong><br />

oggetti presenti in memoria. Con l’evoluzione delle tecnologie software si è resa<br />

necessaria una maggiore flessibilità <strong>di</strong> quella permessa da un sistema <strong>di</strong> questo tipo, e<br />

sono stati sviluppati programmi in grado <strong>di</strong> aggirare le sue numerose limitazioni,<br />

producendo d’altro canto un notevole aumento della complessità <strong>di</strong> questi programmi e<br />

un conseguente aumento dei bachi in esso presenti. Un classico esempio <strong>di</strong> limitazione<br />

imposta dal tra<strong>di</strong>zionale sistema <strong>di</strong> permessi è il fatto che soltanto agli amministratori <strong>di</strong><br />

sistema è consentito creare e mo<strong>di</strong>ficare i gruppi, e la maggior parte delle applicazioni<br />

sviluppate per consentire ai normali utenti <strong>di</strong> effettuare mo<strong>di</strong>fiche dei permessi sui<br />

propri documenti contiene bug che, se sfruttati, possono compromettere la sicurezza <strong>di</strong><br />

tutti i dati in memoria.<br />

La necessità <strong>di</strong> sviluppare un sistema <strong>di</strong> permessi più sicuro e flessibile ha portato alla<br />

realizzazione delle Access Control Lists (ACLs), che sono definite come strumento<br />

standard nella famiglia POSIX 1003.<br />

77


B.2. Struttura <strong>di</strong> una ACL<br />

Una Access Control List è costituita da una serie <strong>di</strong> voci (entry), ciascuna<br />

rappresentante una classe <strong>di</strong> utenti, che definisce per ogni classe i tra<strong>di</strong>zionali permessi<br />

<strong>di</strong> lettura, scrittura ed esecuzione (-r-w-x) sull’oggetto. Le ACL contenenti soltanto le<br />

tre classi base owner, group e other sono dette ACL in forma minima (minimal ACL), e<br />

sono equivalenti ai permessi d’accesso <strong>di</strong> vecchio stampo, mentre quelle contenenti<br />

altre voci sono dette ACL estese (extended ACL) e contengono una maschera (mask) e<br />

un qualsiasi numero <strong>di</strong> utenti o gruppi d’utenti, ciascuno definito da un nome. La tabella<br />

B.1 mostra la tipica struttura <strong>di</strong> una ACL estesa.<br />

Tipo <strong>di</strong> Voce<br />

Proprietario (owner)<br />

Singolo Utente Definito<br />

Gruppo Proprietario (owning group)<br />

Singolo Gruppo Definito<br />

Maschera (mask)<br />

Altri Utenti (other)<br />

Tabella B.1: Struttura <strong>di</strong> una ACL estesa.<br />

Forma Testuale<br />

user::rwx<br />

user:nomeutente:rwx<br />

group::rwx<br />

group:nomegruppo:rwx<br />

mask::rwx<br />

other::rwx<br />

Le voci <strong>di</strong> singolo utente definito e <strong>di</strong> singolo gruppo definito sono assegnate alla classe<br />

gruppo proprietario, a cui appartiene già la voce “owning group”, presente<br />

obbligatoriamente in tutte le ACL. Per processarle si fa uso della voce mask: quando un<br />

utente registrato nella ACL come Utente Definito o Gruppo Definito, richiede <strong>di</strong><br />

eseguire un’operazione sul relativo oggetto, il sistema <strong>di</strong> controllo dell’ACL permette<br />

soltanto le operazioni che compaiono sia nella maschera che nella voce utente/gruppo,<br />

come nell’esempio mostrato in tabella B.2.<br />

Tipo <strong>di</strong> Voce Forma Testuale Permessi<br />

Singolo Utente Definito user:GiorgioRomani:r-x r-x<br />

Maschera mask::rw- rw-<br />

Permessi Effettivi r--<br />

Tabella B.2: Uso della voce “mask” per definire i <strong>di</strong>ritti finali delle voci "Singolo Utente Definito" e<br />

"Singolo Gruppo Definito".<br />

78


B.3. Algoritmo <strong>di</strong> controllo <strong>degli</strong> accessi<br />

Quando un processo richiede l’accesso ad un oggetto presente nel file system viene<br />

eseguito un algoritmo composto <strong>di</strong> due passi, il primo dei quali è selezionare la voce<br />

che più identifica il processo richiedente. Le varie voci <strong>di</strong> lista sono lette nel seguente<br />

or<strong>di</strong>ne: proprietario, utenti definito, gruppi (proprietario e gruppi definiti), altri utenti<br />

(other), e solo una <strong>di</strong> queste voci sarà utilizzata per l’accesso.<br />

Il secondo passo controlla se la voce trovata contiene sufficienti permessi d’accesso per<br />

il processo richiedente. Un singolo processo può appartenere ad uno o più gruppi,<br />

quin<strong>di</strong> più <strong>di</strong> una voce <strong>di</strong> gruppo può essere usata per gestire i permessi; se qualcuno dei<br />

possibili gruppi contiene i permessi richiesti, allora il sistema permetterà l’accesso<br />

all’oggetto, se invece nessuna delle possibili voci identificate permette l’accesso, allora<br />

al processo non sarà permesso <strong>di</strong> accedere ai contenuti richiesti.<br />

L’algoritmo <strong>di</strong> controllo <strong>degli</strong> accessi può essere descritto in pseudo-co<strong>di</strong>ce come<br />

segue:<br />

Se l’userID del processo richiedente corrisponde all’userID del proprietario,<br />

allora verranno utilizzati i permessi del proprietario (owner).<br />

Altrimenti se l’userID del processo corrisponde all’userID <strong>di</strong> un utente definito<br />

nella lista, allora la relativa voce nell’ACL ne determinerà i permessi.<br />

Altrimenti se una delle groupID del processo corrisponde con l’ID dell’owner<br />

group, e la relativa voce dell’ACL contiene i permessi richiesti, allora l’accesso<br />

è consentito<br />

Altrimenti se una delle groupID del processo corrisponde con l’ID <strong>di</strong> uno dei<br />

gruppi definiti nella lista, e la relativa voce dell’ACL contiene i permessi<br />

richiesti, allora l’accesso è consentito.<br />

Altrimenti se una delle groupID del processo corrisponde con l’ID <strong>di</strong> uno dei<br />

gruppi definiti nella lista o con il gruppo proprietario, e nessuna delle voci<br />

corrispondenti nella lista contiene il permesso richiesto, allora l’accesso viene<br />

negato.<br />

Altrimenti la voce “other” determina i permessi d’accesso.<br />

Se la voce selezionata nella lista è la “owner” o “other”, e contiene i permessi<br />

richiesti, allora l’accesso viene garantito.<br />

79


Altrimenti se la voce selezionata nella lista corrisponde ad un utente o gruppo<br />

definito nella stessa, allora si esegue il confronto con i permessi definiti nella<br />

maschera: se sia la voce corrispondente che la maschera contengono il permesso<br />

richiesto, allora l’accesso è garantito.<br />

Altrimenti l’accesso viene negato.<br />

Con l’utilizzo <strong>di</strong> questo strumento <strong>di</strong>venta quin<strong>di</strong> possibile definire permessi <strong>di</strong> accesso<br />

specifici per singoli utenti, cosa che risulta molto utile per gran<strong>di</strong> sistemi multiutente<br />

con <strong>di</strong>versi livelli gerarchici, e <strong>di</strong>venta anche possibile fornire il potere <strong>di</strong> gestire i<br />

permessi <strong>di</strong> accesso a determinati oggetti ad utenti <strong>di</strong>versi dall’amministratore <strong>di</strong><br />

sistema.<br />

80


Appen<strong>di</strong>ce C – Linked Lists<br />

Questo tipo <strong>di</strong> struttura dati permette <strong>di</strong> memorizzare collezioni <strong>di</strong> uno stesso tipo <strong>di</strong><br />

dato, in<strong>di</strong>pendentemente dalla lunghezza finale della collezione stessa.<br />

Una lista collegata (linked list) è formata da una serie <strong>di</strong> tanti elementi dello stesso tipo,<br />

ai quali si può accedere soltanto sequenzialmente, collegati tra loro attraverso dei<br />

riferimenti.<br />

Ed è proprio l’utilizzo dei riferimenti ad altri elementi che <strong>di</strong>fferenzia le linked list dagli<br />

array <strong>di</strong> dati: questi ultimi sono memorizzati all’interno <strong>di</strong> un blocco unico contenente<br />

tutti i dati ad essi appartenenti; e per navigarli basta spostarsi <strong>di</strong> un offset 7 <strong>di</strong> byte<br />

predefinito, così da trovare in questo modo l’elemento successivi o precedente a quello<br />

appena letto nell’array.<br />

Gli elementi appartenenti ad una stessa linked list non sono necessariamente<br />

memorizzati in zone <strong>di</strong> memoria tra loro a<strong>di</strong>acenti, e per accedere a ciascuno <strong>di</strong> essi<br />

bisogna spostarsi all’in<strong>di</strong>rizzo <strong>di</strong> memoria al quale è memorizzato l’elemento<br />

precedente o successivo; all’interno d’ogni elemento appartenente alla lista <strong>di</strong>venta<br />

quin<strong>di</strong> necessario inserire un campo dati aggiuntivo contenente l’in<strong>di</strong>rizzo <strong>di</strong> memoria<br />

al quale può essere recuperato l’elemento successivo della lista.<br />

Un esempio <strong>di</strong> <strong>di</strong>chiarazione <strong>di</strong> linked list è riportato <strong>di</strong> seguito:<br />

struct linkedlist {<br />

/*<br />

Dichiarazione dei tipi <strong>di</strong><br />

Dato appartenenti alla<br />

Struttura<br />

*/<br />

struct linkedlist *prossimo_elemento;<br />

struct linkedlist *elemento_precedente;<br />

};<br />

7 Per leggere l’elemento successivo <strong>di</strong> un array, è necessario spostarsi avanti in memoria, facendo un salto<br />

(offset) <strong>di</strong> <strong>di</strong>mensione pari alla grandezza del singolo elemento memorizzato nell’array, in quanto ogni<br />

elemento è memorizzato in una zona a<strong>di</strong>acente al suo precedente e successivo.<br />

81


Per navigare una linked list è poi necessario utilizzare un puntatore alla struttura dati nel<br />

quale viene memorizzato il riferimento all’elemento <strong>di</strong> partenza, che è utilizzato per<br />

accedere agli altri elementi della lista attraverso un rein<strong>di</strong>rizzamento:<br />

struct linkedlist *puntatore;<br />

puntatore=puntatore->prossimo_elemento;<br />

Quando viene allocato un nuovo elemento appartenente ad una linked list, è necessario<br />

identificare quell’elemento come coda della lista, cosa che viene <strong>di</strong> solito eseguita<br />

ponendo il valore del puntatore all’elemento successivo ad un valore nullo. Altra cosa<br />

necessaria, quando si aggiunge un elemento in coda alla lista, è assegnare un riferimento<br />

alla nuova coda nell’elemento precedente, cambiando il valore del puntatore da NULL<br />

all’in<strong>di</strong>rizzo a cui è memorizzata la nuova struttura.<br />

Come si può ben notare, è la presenza dei puntatori all’elemento successivo e<br />

precedente che permette una navigazione della lista in entrambe le <strong>di</strong>rezioni, ma in<br />

molti casi uno dei puntatori può essere omesso nella <strong>di</strong>chiarazione: nel caso risulti<br />

necessaria la navigazione in un solo senso basterà includere assieme al puntatore<br />

all’elemento successivo un riferimento alla testa della lista (primo elemento), in modo<br />

da poter ricominciare a scorrere la lista dall’inizio in caso <strong>di</strong> necessità.<br />

La figura C.1 mostra la struttura <strong>di</strong> due linked lists, una con riferimenti all’elemento<br />

successivo e precedente, l’altra con riferimenti solo agli elementi successivi.<br />

Elemento1<br />

Variabile 1<br />

Variabile 2<br />

Variabile 3<br />

Elemento 2<br />

Variabile 1<br />

Variabile 2<br />

Variabile 3<br />

Elemento 3<br />

Variabile 1<br />

Variabile 2<br />

Variabile 3<br />

NULL<br />

Elemento 1<br />

Variabile1<br />

Variabile2<br />

Varibile3<br />

Elemento 2<br />

Variabile1<br />

Variabile2<br />

Varibile3<br />

Elemento 3<br />

Variabile1<br />

Variabile2<br />

Varibile3<br />

NULL<br />

NULL<br />

Figura C.1: esempi <strong>di</strong> linked lists con riferimenti agli elementi successivi e precedenti, e con<br />

riferimenti solo agli elementi successivi.<br />

Nella figura C.1 risulta subito evidente che queste strutture dati consentono soltanto una<br />

loro navigazione <strong>di</strong> tipo sequenziale: per accedere, cioè, all’n-esimo elemento <strong>di</strong> lista<br />

82


isognerà prima accedere agli altri n-1 elementi; una volta avuto accesso ad un<br />

elemento, per accedere all’elemento ad esso a<strong>di</strong>acente, bisogna leggere e posizionarsi<br />

all’in<strong>di</strong>rizzo <strong>di</strong> memoria contenuto nel relativo puntatore. Per contrassegnare un certo<br />

elemento come capo o coda della lista, si assegna all'apposito puntatore il valore NULL,<br />

mentre i puntatori <strong>degli</strong> elementi interni contengono sempre l’in<strong>di</strong>rizzo dell’elemento<br />

precedente o successivo.<br />

83


Riferimenti Bibliografici<br />

[HTree]<br />

Tratto da Directory Indexing of ext2/3 - Mingming Cao 26-7-2005<br />

http://ext2.sourceforge.net/2005-ols/paper-html/node3.html<br />

[JFS]<br />

Tratto da EXT3, Journaling Filesystem<br />

Conferenza del 20/7/2000 tenuta dal Dr. Stephen Twee<strong>di</strong>e<br />

http://olstrans.sourceforge.net/release/OLS2000-ext3/OLS2000-ext3.html<br />

[MSDN]<br />

Progettazione <strong>di</strong> applicazioni <strong>di</strong>stribuite con Visual <strong>Stu<strong>di</strong></strong>o .NET - Controllo <strong>di</strong> Accesso.<br />

Tratto da<br />

http://msdn.microsoft.com/library/ita/default.aspurl=/library/ITA/vsent7/html/vxconac<br />

cesscontrol.asp<br />

[NT]<br />

New Technology File System, designed for Windows NT, 2000, XP<br />

http://www.ntfs.com/ntfs-partition-boot-sector.htm<br />

http://www.ntfs.com/ntfs-mft.htm<br />

[POSIX-ACL]<br />

Tratto da POSIX Access Control Lists on Linux, <strong>di</strong> Andreas Grunbacher - SuSE Labs,<br />

SuSe Linux AG<br />

www.suse.com<br />

[reiser]<br />

Reiser4 Overview, tratto da www.namesys.com<br />

[vfs/ext2]<br />

Tratto da Design and Implementation of the Second Extended Filesystem <strong>di</strong>:<br />

Rémy Card, Theodore Ts'o, Stephen Twee<strong>di</strong>e<br />

Pubblicato agli atti del primo simposio internazionale su Linux in Olanda.<br />

ISBN 90-367-0385-9<br />

http://web.mit.edu/tytso/www/linux/ext2intro.html<br />

[spec]<br />

Tratto da John's spec of the second extended filesystem<br />

http://uranus.it.swin.edu.au/~jn/explore2fs/es2fs.htm<br />

84


[VFS]<br />

A Tour of the Linux VFS - 1996 Michael K. Johnson, johnsonm@redhat.com<br />

http://www.tldp.org/LDP/khg/HyperNews/get/fs/vfstour.html<br />

[WFS]<br />

WinFS Data Model - Jesús Rodríguez<br />

http://www.c-sharpcorner.com/Longhorn/WinFS/WinFSDataModel.asp<br />

85

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!