Capitolo 4
Gestione dei file
I file sono normalmente memorizzati su una memoria di massa come dischi rigidi, floppydisk, CDrom e nastri. Il sistema operativo Theos attualmente funziona su una scheda slave su cui non è possibile accedere a nessun dispositivo periferico come quelli appena menzionati.
Tuttavia è stato simulato un disco virtuale utilizzando la memoria RAM. È stato allocato uno spazio di un Mbyte su cui all'avvio vengono memorizzati i dati inviati dalla scheda master la quale a sua volta controlla un disco rigido (per dettagli sull'hardware utilizzato si veda l'appendice A.
4.1 Architettura del file system
Il disco virtuale è stato organizzato con un'unica directory in cui sono inclusi tutti i file. L'unità del disco è costituita da 2048 blocchi da 512 byte ciascuno. Ciascun file occupa un certo numero di blocchi adiacenti e non sono ammessi blocchi liberi fra un file e il successivo. I file vengono memorizzati a partire dal primo blocco della directory.
Esiste un indice della directory le cui informazioni vengono memorizzate a partire dall'ultimo blocco (il numero 2047) e cresce verso blocchi di numero inferiore (si veda la figura 4.1).

Figura 4.1: La struttura del disco virtuale
L'indice della directory memorizza per ogni file le seguenti informazioni:
- il nome
- l'identificatore dell'utente proprietario
- i permessi d'accesso
- il numero del blocco iniziale
- il numero di blocchi occupati
- la presenza o meno nel cestino
- il numero di byte occupati
Nei blocchi di indice possono essere contenute le informazioni relative a 16 file in quanto le informazioni relative a ciascun file occupano 32 byte. Il primo spazio dell'indice non si riferisce ad un file ma memorizza dei dati complessivi riguardanti tutta la directory e cioè il nome della directory, il primo blocco dati libero e il numero di file presenti.
Non esiste nessun carattere di terminazione dei file in quanto i file possono essere sia di testo che binari.
Le definizioni di queste strutture in C utilizzano la direttiva union e sono riportate di seguito.
#define DiskDim 2048 /* nr. blocchi del disco da 1Mb RAM */
#define BlkDim 512 /* dimensione dei blocchi */
#define DirPerBlk 16 /* numero file per blocco indice */
Block RAMDisk[DiskDim]; /* il disco virtuale */
typedef union {
Blkdir DIR[DirPerBlk];
char DAT[BlkDim];
} Block;
typedef union {
Startdisk BLK0;
Directory BLKF;
} Blkdir;
struct Startdisk {
char name[NameDim+2];
unsigned short ini, /* blocco d'inizio */
free, /* numero di blocchi liberi */
flag; /* flag */
unsigned files; /* numero di file */
};
typedef struct Startdisk Startdisk;
struct Directory {
char name[NameDim];
unsigned char owner, /* proprietario del file */
access; /* permessi d'accesso */
unsigned short ini, /* blocco di inizio */
lun, /* numero di blocchi */
flag; /* flag per la cancellazione */
unsigned dim; /* numero di byte */
};
typedef struct Directory Directory;
4.2 Allocazione e cancellazione dei blocchi
Un file per essere creato deve occupare almeno un blocco dati che viene preso allocando il primo blocco libero dopo i blocchi già utilizzati per i dati. Inoltre si deve cercare se nell'ultimo blocco di indice (quello con numero più basso) sono registrati meno di sedici file, altrimenti si deve allocare un nuovo blocco di indice e a questo punto si memorizzano le informazioni relative al file appena creato. Queste operazioni vengono realizzate dalla funzione creat:
int creat (path, flag)
/* Crea un file vuoto di nome path e con proprieta' flag. */
{
cerca la prima posizione vuota nell'indice
cerca se il file esiste gia' e in caso lo cancella
alloca un blocco, setta la lunghezza a zero byte
aggiorna l'indice.
}
Per poter allungare il contenuto di un file si deve verificare se c'è spazio sufficiente nell'ultimo blocco dati utilizzato dal file. Se non dovesse essere sufficiente si deve verificare se il blocco successivo è libero (ciò può capitare solo se il file in questione è l'ultimo della directory). Altrimenti il file non può essere allungato; questo ostacolo può essere aggirato se si ha una copia del contenuto di tutto il file, in questo caso si può cancellare il file e ricrearlo subito dopo allocando un numero superiore di blocchi.
La politica adottata dall'editor (vedi sezione 7.1) è leggermente più complicata: viene prima registrata la nuova versione del file con un nome provvisorio (del tipo "noname12"), poi viene cancellata la copia vecchia ed infine viene cambiato il nome alla versione nuova in modo da rimetterci il nome di partenza. Per quanto questo non sia il modo più efficiente per registrare un file da la garanzia che anche in caso di interruzione del processo almeno una copia del file rimane sul disco.
La cancellazione definitiva di un file comporta che si liberino dei blocchi. Tuttavia questo file system non ammette la frammentazione esterna e cioè che ci siano dei blocchi inutilizzati inseriti fra blocchi dati utilizzati.
Ogni volta che si libera uno o più blocchi dati si devono ricopiare tutti i blocchi dati successivi in modo che non resti nessun buco. Per fare ciò si utilizza la funzione compact. Questa funzione scandisce a partire dalla fine l'indice della directory e cerca i file per cui il campo flag indica che sono da eliminare (assume il valore F_WASTED o in alternativa F_KILLED). Il compattamento procede in due fasi: la prima prevede lo spostamento di tutti i blocchi dati seguenti l'ultimo occupato dal file da cancellare di una quantità pari alla lunghezza dello stesso; la seconda fase prevede la compattazione dell'indice dopo l'eliminazione dei dati riguardanti il file cancellato e l'aggiornamento delle informazioni globali della directory.
COMPACT (cmid,pun)
/* compatta il contenuto del disco virtuale */
{
se sintassi non corretta
stampa aiuto;
altrimenti {
chiama down (DISK);
per tutti i file nell'indice della directory {
se il flag di cancellazione e' attivo {
spostamento dei blocchi di dati;
aggiornamento dell'indice;
aggiornamento del disco;
}
}
chiama up (DISK);
}
}
4.3 La gestione della directory
Le funzioni che si occupano della gestione della directory sono chiamate principalmente dai comandi builtin e pertanto vengono descritte nel capitolo L'interfaccia utente (Shell).
4.3.1 Il file password
Tutti i file compresi nella directory sono trattati nello stesso modo per quanto riguarda le modalità di accesso, lettura, modifica, ecc. L'unica eccezione è rappresentata dal file password. Questo file è l'unico a cui non può accedere nessun utente né possono essere cambiate le proprietà d'accesso. Viene gestito esclusivamente tramite i seguenti comandi della shell: login, passwd, users.
Il file password può ospitare un massimo di 256 utenti accreditati. Per ciascun utente vengono utilizzati 40 byte, la lunghezza totale del file è quindi di 10 Kbyte. Per ciascun utente vengono utilizzati 20 byte per memorizzarne il nome (username) e gli altri 20 per memorizzare la parola riservata (password). Le password non vengono criptate né viene richiesta alcuna condizione (lunghezza minima, impiego di cifre, ecc). Il numero d'ordine in cui i singoli utenti sono memorizzati nel file password viene utilizzato come identificatore dell'utente (UID). Il superuser si trova nella prima posizione e quindi il suo UID è zero. A questo UID corrispondono tutti i privilegi. Gli altri utenti sono trattati tutti nello stesso modo.
4.3.2 Permessi d'accesso
I permessi d'accesso prevedono solo due categorie: il proprietario e tutti gli altri utenti (non esiste il gruppo). Un file può essere concesso in lettura (R), scrittura (W) o esecuzione (X). Per memorizzare i permessi vengono utilizzati 6 bit, di conseguenza sono lecite tutte le possibili combinazioni. I permessi d'accesso vengono verificati dalla funzione di basso livello access. Tutti gli utenti, tranne il superuser, per accedere a un file devono soddisfare alle condizioni previste dai permessi; il superuser, invece, può accedere a tutti i file tranne ai propri file per cui è negato il permesso al proprietario e non dispone del permesso di esecuzione per i file non eseguibili. Per inciso si noti che il file password è di proprietà del superuser ed ha tutti i permessi negati: nessuno vi può accedere se non tramite le opportune routine di sistema.
int access (path, mode)
/* algoritmo di basso livello */
/* verifica le proprieta' di accesso ad un file */
{
se il file path esiste {
calcola l'UID attuale
se il file e' nel cestino ritorna -1
carica il proprietario e le proprieta' del file
ripeti per tutte le proprieta' di accesso
se la proprieta' e' richiesta ma non concessa ritorna -2
ritorna TRUE;
}
}
4.3.3 Ricerca di un file
La funzione Search ricerca un file di nome specificato. La ricerca viene svolta in modo sequenziale sull'indice della directory. Viene restituito il numero d'ordine del file cercato.
int Search (nomefile)
/* algoritmo di basso livello */
/* cerca l'indice di un file */
{
per tutti i file nell'indice del disco {
se nomefile coincide con nome dell'i-esimo file
ritorna i;
}
ritorna 0;
}
Back to Lucio's Home page.