2.13 Lezione 13 - follen99/ArchitetturaDeiCalcolatori GitHub Wiki
Storicamente ogni processore aveva il proprio file system, che non poteva essere cambiato.
Ai giorni d'oggi, l'interfaccia è pressocchè la stessa per ogni sistema, ma ci sono in realtà diversi tipi di file system. Infatti, per ogni unità di archiviazione ha un diverso tipo di file system, in modo che si abbia il FS più ottimizzato per ogni occasione.
La differenza fondamentale tra le directory ed un file normale. è che le directory vengono scritte solo dal sistema operativo.
-
Ricerca di un file
-
Creare un file
-
Eliminare un file
-
"listare" (list) una directory : mostrare tutti i file al suo interno
-
Rinominare un file
-
"traversare" (traverse) il file system
Tutti i sistemi operativi utilizzano un file system strutturato ad albero.
Solitamente è possibile accedere ai files con un pathname assoluto o relativo; un path assoluto è un tipo di path che parte dalla /root, ovvero l'inizio del file system, ed arriva al files; termina con il nome del file: /root/pianosotto/altropiano/nomefile.txt
Come faccio a cercare, all'interno di un FS ad albero, un file?
Fondamentalmente quello che bisogna fare è una ricerca in profondità o ampiezza (BFS o DFS) finchè non trovo il file.
Da linea di comando un file viene ricercato con il comando find:
$ find . -iname '*foo*'
Ricerca di un file non case-sensitive
Che succede quando questo albero "non è più un albero"?
Un albero è un particolare grafo non avente percorsi chiusi; cosa succede se invece si verificano dei percorsi chiusi?
Questi particolari percorsi si verificano quando voglio dare un doppio nome allo stesso file; pur sembrando una pratica strana, è molto utilizzata:
Quando abbiamo uno shortcut di un programma (ad esempio sul desktop), abbiamo un perfetto esempio di questo fenomeno. Questo collegamento non è altro che un link al programma originale.
Nel momento in cui un singolo file è visibile da più directory, potrebbe esserci un problema in cui si verificano dei percorsi chiusi.
Il problema è che gli algoritmi di BFS e DFS funzionano su grafi aciclici, e di conseguenza in presenza di un loop l'algoritmo rimarrebbe bloccato all'interno del loop. I sistemi operativi possono agire in due modi per difendersi da questa eventualità:
-
Impedire la creazione di link che creerebbero loop; un chiaro esempio è linux.
-
Uscire dal loop nonappena ci si accorge di aver ciclato più di una volta.
Il metodo storico di UNIX per creare un link, era quello di usare il comando ln (link); il comando "ln" senza parametri, permetteva di stabilire un hard link: anche se ci troviamo in directory diverse, con files a nomi diversi, in realtà questi puntano allo stesso file (spazio su disco). Questo è possibile se ci troviamo sullo stesso volume del file originale.
Per evitare che il file si trovi sullo stesso volume, sono stati aggiunti i cosiddetti link simbolici:
Entriamo in una dir, e al suo interno troviamo semplicemente un collegamento che non è altro che la traduzione di un pathname che ci conduce da un'altra parte.
I link simbolici si creano con il comando
ln -s {parametri}
I link di windows sono proprio dei link simbolici; Windows permette gli hard link, ma non fornisce strumenti per crearli (perchè non viene utilizzato dagli utenti babbani).
Nel momento in cui creiamo un hard link, lo stesso file può essere aperto da due dir diverse. Se il file viene eliminato da una delle due dir, cosa succede?
Evidentemente, non è possibile eliminare il file completamente, se ci sono dei link che puntano ad esso. Bisogna conservare, tra le varie informazioni del file, il numero di puntatori (link) che puntano ad esso. Solo quando il numero di link che puntano al file diventa zero, il file viene fisicamente eliminato.
Tutto questo avviene nel momento in cui abbiamo degli hard link
Nel caso dei link simbolici, invece, non viene tenuto alcun conteggio, e se viene eliminato il file originale, il link dirà qualcosa del tipo "file non trovato"
In windows, ogni volta che viene inserita una nuova unità, ci si trova un disco rimovibile, o qualcosa del genere.
Nel mondo UNIX, tutte le Dir vengono incastrate in un unico albero, mediante delle operazioni di montaggio (mount).
A differenza del mondo Windwos, dove abbiamo delle unità logiche (come C:, D:, ecc), in UNIX abbiamo una directory principale chiamata /root (solitamente indicata con "/"), dopodichè ci sono delle dir, solitamente vuote, dette punti di montaggio (mount points); tutto ciò che viene "montato" è presente sotto la directory "mnt", in Mac chiamato "/volumes".
Storicamente, le periferiche andavano montate e smontate a mano, usando il comando:
mount {} //montare l'unità
umount {} // smontare l'unità spostando tutti i dati nelle caches nell'unità
Per adeguarsi ad un mondo dove non tutte le persone sono in grado di compiere queste operazioni, la maggior parte degli OS hanno dei deamons (programmi di sistema) che appena rilevano la presenza di una nuova unità la montano automaticamente.
Nei sistemi come windows è possibile rimuovere le chiavette (esempio classico) senza espellerle; questo perchè windows, abituato ad essere utilizzato da utenti non "studiati", ha deciso di non usare alcuna tecnica di caching;
Nei sistemi più seri, come linux, mac os, ecc, viene utilzzata la tecnica del caching, quindi se rimuoviamo delle periferiche senza espellerle, dei dati potrebbero non essere stati ancora spostati sull'unità, avendo quindi una perdita di dati, o peggio una corruzione della chiavetta.
Possiamo montare delle directory appartenenti ad unità remote ed utilizzarle come se fossero delle dir locali.
Ci sono dei protocolli ben precisi per questi tipi di scambi:
-
NFS protocollo di condivisione UNIX
-
CIFS protocollo standard windows (anche se probabilmente SAMBA è più usato)
Resta il problema della consistenza, perchè ovviamente se abbiamo più utenti che si connettono ad un unità remota, le modifiche di un utente potrebbero essere viste in ritardo da un altro, ed altre problematiche del genere.
Che l'accesso sia locale o remoto, se ci sono più accessi contemporanei, e più modifiche, evidentemente bisogna gestire opportunamente queste modifiche.
La semantica classica di UNIX è: Nonappena le modifiche vengono applicate, tutti gli utenti le visualizzano; questa politica funziona molto bene in locale, ma qualora si avesse un file in remoto (rete) non è la scelta migliore. Questo perchè tramite la rete, il sistema operativo di ogni utente crea, senza che l'utente se ne accorga, una copia del file, che poi modifica in locale.
Come si proteggono i files?
Il creatore del file o il suo proprietario, vorrebbero dichiarare quali operazioni possono essere effettuate sul file. Il metodo classico di UNIX (DA CONOSCERE!) fa utilizzo di 9 bit.
Distingue gli accessi di:
-
accesso del Proprietario
-
Accesso del gruppo del proprietario
-
tutto il resto
Per visualizzare il proprietario di un file si digita in shell:
ls -l (filename)
E' possibile stabilire dei gruppi in cui si garantisce l'accesso ad un intero gruppo, e non un singolo utente.
Si utilizzano i bit per garantire l'accesso nel seguente modo:
R | W | X | |
---|---|---|---|
accesso del proprietario | 1 | 1 | 1 |
accesso del gruppo | 1 | 1 | 0 |
Accesso pubblico | 0 | 0 | 1 |
R: Read
W: Write
X: Execution
La "X" in unix è comunemente usata per dire che un qualche file è eseguibile. La X può comparire anche sotto delle directory (non indica che essa è eseguibile).
I permessi possono essere cambiati dal proprietario tramite un comando chiamato chmod
Valore binario (rwx ) |
Valore decimale | Permessi |
---|---|---|
111 | 7 | lettura, scrittura ed esecuzione |
110 | 6 | lettura e scrittura |
101 | 5 | lettura ed esecuzione |
100 | 4 | solo lettura |
011 | 3 | scrittura ed esecuzione |
010 | 2 | solo scrittura |
001 | 1 | solo esecuzione |
000 | 0 | nessuno |
Esempi:
chmod 734 nomefile
assegna tutti i permessi all'utente, scrittura ed esecuzione per il gruppo e solo lettura per tutti gli altri.
chmod 777 nomefile
assegna tutti i permessi all'utente corrente, al suo gruppo ed anche a tutti gli altri.
chmod -R 777 nomedirectory
come il precedente ma riguarda una directory e tutti i file esistenti all'interno della stessa.
Se ad esempio creiamo un file con
touch prova.txt
Il file viene creato con le impostazioni di default, ovvero scrittura/lettura-lettura-lettura.
Per cambiare uno dei parametri, ad esempio la sua eseguibilità usiamo chmod:
chmod +x prova.txt
Il file è ora eseguibile.
Per eseguirlo si usa il comando:
./prova.txt
Ovviamente un .txt non può esssere eseguito.
I file che iniziano con il carattere "." sono comunemente dei file nascosti, ad esempio i file .lock sono dei file che servono a mettere un lock su un qualcosa per evitare accessi concorrenti.
Essi possono essere visualizzati con il comando:
ls -la
I file che iniziano con ". ." sono degli hard link assegnati alla directory presente e a quella precedente.
Questo metodo non è molto selettivo, ed il metodo più serio, implementato anche nei sistemi più importanti, è il sistema access-control list.
Con questo metodo, posso dire per ogni file, quali sono gli utenti che possono accedervi e quali operazioni possono compiere.
"FINE" CAPITOLO 13
Un file è un contenitore logico contenente delle informazioni. Solo il creatore del file è a conoscenza del suo contenuto ed il formato utilizzato.
Sul disco un file è tipicamente spezzettato in blocchi che corrispondono ai blocchi degli hard disk. In un SSD viene emulata una disposizione dei file simile agli HD, per uniformità. I vari blocchi sono solitamente di 512 bytes.
A bassissimo livello il SO fa utilizzo dei Device Drivers che gestiscono l'interfaccia del disco.
-
Programmi applicativi
-
File system logico
-
Modulo di organizzazione dei file
-
File System base
-
Controllo I/O
-
dispositivi
6 è il livello più a basso livello
I drivers a bassissimo livello sono in grado di dare e ricevere comandi del tipo "prendi questo blocco", che storicamente veniva indicato con 3 coordinate: CHS (cylinder head sector);
Attualmente si utilizza una tecnica di accesso a blocchi del tipo LBA: Questo perchè da alcuni anni, con la crescita del consumo dei dischi, questi vengono visti come un unico array di blocchi con un indirizzo logico; piuttosto che dire "prendi un blocco presente su questo cilindro, utilizzando questa testina, e poi quel particolare settore" (quindi tre coordinate), viene detto semplicemente "prendi il blocco n";
dopo questo comando è direttamente il controllo del disco che converte il comando in azioni da compiere (e quindi le coordinate).
- Boot control block: software che viene lanciato per effettuare il boot, qualora il disco in questione è quello scelto per il boot.
Quando apro un file vengono create delle strutture per processo per sistema, che mi permettono di accedere a quel file; gestiscono sopratutto il puntatore all'interno del file.
Se più processi, che non hanno nulla a che vedere tra loro, tentano di aprire lo stesso file, possono farlo, ma deve esserci un puntatore separato per ognuno dei processi che lo sta aprendo.
Processi che sono in relazione di parentela padre-figlio, devono invece avere lo stesso puntatore; in sistemi UNIX, in pratica la cosa funziona nel seguente modo:
C'è una tabella "open-file table" dove vengono specificati i file aperti per processo. Questa tabella punta ad una tabella di sistema dei file aperti. Il puntatore al file è nella tabella di sistema, se ci sono processi che aprono lo stesso file (per conto proprio) hanno due entry nella tabella di sistema, e quindi hanno puntatori separati.
Se invece c'è una relazione di parentela padre-figlio, il figlio eredita la stessa tabella del padre (tabella per-process) e quindi utilizza lo stesso puntatore del padre, che è lo stesso puntatore al file.
-
Lista lineare: Scelta poco intelligente
-
Tabella hash, ovvero la soluzione adottata ai giorni d'oggi per maggiore efficienza, siccome le liste potrebbero diventare particolarmente enormi.
Il FS potrebbe essere tenuto ordinato mediante delle particolari strutture dati.
Nel disco ho milioni di blocchi, alcuni liberi altri occupati. Per creare un file, quali blocchi utilizzo?
Sicuramente dovrò utilizzare dei blocchi liberi, altrimenti potrei cancellare files di altri processi o utenti.
La soluzione a cui si potrebbe pensare è la seguente: mi scelgo un bel pezzo continuo di blocchi liberi, e vado a salvare al suo interno il mio file. SBAGLIATO! questa tecnica non funziona per il semplice fatto che i files funzionano ad accrescimento; il file, al momento della creazione, è vuoto. Quando inizio a scrivere al suo interno le sue dimensioni aumentano sempre di più, quindi se non prendo delle opportune misure, questo tipo di allocazione contigua crea i soliti problemi della gestione dinamica della memoria.
🏁 05-27 1:00
L'allocazione contigua sarebbe la soluzione perfetta, se solo funzionasse; sarebbe infatti un metodo di allocazione molto veloce, proprio per la sua natura, visto che i byte sarebbero disposti in maniera sequenziale.
Come funzina?
Non chiedo che il file sia tutto contiguo; so solo che il file è composto da vari "blocchi" posti in vari posizioni della memoria. In ogni blocco, viene posto un puntatore al blocco successivo, in modo tale che nella directory dove è posizionato il file è presente il puntatore che punta al primo blocco, dopodichè gli altri blocchi vengono prelevati seguendo i vari link
Questa soluzione non funziona tanto bene, per i vari motivi:
-
Se devo accedere al decimo blocco, sono costretto ad effettuare ad effettuare tutte le nove operazioni di lettura prima di arrivare al blocco che mi interessa.
-
Motivi di affidabilità: se per caso uno dei blocchi "viene distrutto", non perdo solo i dati contenuti in quel blocco, ma anche tutti i contenuti successivi.
Questa è una variante del sistema precedente; è adottata dai sistemi Microsoft, non tanto per i sistemi operativi, quanto per le unità rimovibili.
Anche questa è una tecnica di allocazione linkata, ma sono stati rimossi i problemi maggiori: i link non sono separati e presenti uno per blocco, ma sono salvati esternamente.
Esiste quindi una tabella chiamata FAT, dove entro (con il blocco di testa), e trovo tutti i puntatori ai blocchi successivi.
Purtroppo non tanto bene: sono costretto a seguire la catena dei puntatori, ma se non altro questa tabella, al momento della prima lettura, viene caricata in memoria, quindi l'accesso (seppur sequenziale) è più veloce.
Per quanto riguarda l'affidabilità, questa tabella non deve essere mai distruttura, il che significa che vengono effettuate delle copie multiple della tabella.
Questo metodo di allocazione non è particolarmente veloce, e non va assolutamente bene per i dischi grandi (motivazioni ovvie). Per questo tipo di dischi viene utilizzato NTFS.
Nei sistemi UNIX viene utilizzato questo tipo di allocazione.
Quando viene formattato il disco (gergo windows), o "make the disk" (gergo unix, usando il comando mkfs
), viene creato un File System dove non sono presenti files, dove sono presenti delle informazioni di riferimento ed una **directory di testa **(vuota).
Queste informazioni sono dei blocchi detti blocchi indice, che conterranno tutte le informazioni relative ai files. Quando eseguiamo il comando mkfs il disco immediatamente diventa più piccolo: ad esempio abbiamo un disco di 1GB e dopo aver usato il comando diventa di 800Mb.
Nei sistemi UNIX nelle directory è presente solo il nome(visto dall'esterno) ed un numero che è il numero del blocco indice corrispondente a quel file.
Se il file pippo.txt corrisponde all'indice 3, vado nel blocco indice 3 ed in quella locazione sono presenti tutte le informazioni del file, come proprietario, permessi, creazione, ecc.
Se abbiamo in due directory separate due file con nomi diversi, ma aventi lo stesso numero del blocco indice, abbiamo (in pratica) un hard link. Basta mettere lo stesso numero del blocco indice per creare un alias per quel file; nel blocco indice sarà riportato che sono presenti due alias per quel file.
Quindi, nel blocco indice sono presenti vari blocchetti, che corrispondono ai file, dove dove sono presenti i puntatori alle locazioni di memoria che contengono i file.
Questo metodo permette l'accesso diretto, perchè basta capire qual è il blocco relativo al file, vi accedo (dove sono presenti i puntatori alle locazioni di memoria contenenti il file), capisco quale puntatore mi serve e vi accedo.
Come si fa nei sistemi UNIX ad avere in un blocco molto piccolo, i puntatori a molti blocchi?
Si utilizzano i livelli.
Sono presenti tutte le informazioni sul file, solitamente chiamati metadati, un piccolo numero di puntatori diretti a blocchi (direct blocks), dei blocchi che puntano ad un altro blocco contenente i dati (single indirect blocks), blocchi con doppia indirezione, blocchi con tripla indirezione e così via.
Cosa significa??
Sui file piccoli, questo sistema è particolarmente veloce, perchè sfruttando i direct blocks il sistema va direttamente a prelevare i dati.
Via via che il file diventa più grande, sono costretto ad effettuare sempre più accessi sul disco per prelevare i puntatori che puntano ai dati.
Questo metodo è molto veloce sui file piccoli (maggiormente usati) e diventa via via più lento sui file grandi; d'altra parte permette di avere files di dimensioni elevatissime.
Con questi tre metodi di allocazione (contiguo-ed estensioni, linkato-o con fat, con indice) abbiamo capito che nella maggior parte dei casi i files sono "spezzettati" sul disco.
Lavorare con un disco particolarmente pieno, significa inevitabilmente avere delle prestazioni inferiori.
I programmi che effettuano la compattazione (che spostano tutti i dati da una parte lasciando una parte contigua di memoria libera) effettuano uno spostamento di blocchi in modo che ogni file sia allocato in maniera contigua.
Quindi, dopo aver effettuato questa operazione, per un lasso di tempo molto piccolo, avremo un disco dove i files non sono assolutamente frammentati, e lo spazio libero è tutto da una parte.
Ovviamente i blocchi liberi non sono diversi da quelli pieni, quindi serve una struttura dati che tiene traccia dei blocchi liberi e pieni.
Un modo potrebbe essere quello di tenere una lista (UNIX), o più comunemente, si utilizza un vettore di bit.
In questa soluzione, abbiamo un lungo vettore, dove ogni bit rappresenta un blocco e ci dice se quel blocco è libero o occupato; questo è uno dei metodi più comunemente utilizzato.
Ovviamente, anche questo vettore di bit deve essere posizionato sul disco, andando a sottrarre spazio ai files; quando creiamo il FS, viene anche creato spazio per questo vettore di bit.
🏁 05-27 1:26
Questo tipo di utilities sono destinate a dischi allo stato solido (SSDs).
Gli SSD sono fatti con sistemi NAND e non è possibile accedere ad una singola locazione di memoria ma tipicamente l'accesso è a pagine (tipo la ram); inoltre questo tipo di sistemi, quando dobbiamo modificare i contenuti di un file, bisogna:
-
Prendere l'intera pagina
-
Cancellarla
-
Riscriverla interamente
Quindi, anche se abbiamo modificato un solo byte di un file, siamo costretti a riscrivere interamente la pagina (che è abbastanza grande).
Inoltre, gli accessi in memoria a stato solido, devono essere distribuiti, proprio perchè dopo un certo numero di accessi alla stessa locazione, la memoria potrebbe essere danneggiata.
Alcune macchine hanno il supporto TRIM abilitato per ottenere le migliori prestazioni per i dischi SSD.
Ovvio.
Se accediamo ad una directory per la prima volta dopo aver acceso il computer (desktop) noteremo che (solitamente) il led HDD si accende; questo indica che stiamo operando sul disco.
Se vi accediamo poco tempo dopo, la luce HDD non si accende, il che significa che i dati a cui abbiamo fatto accesso, sono stati salvati nelle cache.
I dispositivi, quindi, utilizzano tutta la memoria RAM disponibile per effettuare caching, e quindi velocizzare l'accesso al disco.
Dinamicamente viene utilizzato tutto lo spazio disponibile per velocizzare l'accesso al disco; via via che apriamo nuovi programmi questo spazio si riduce per fare spazio ai processi.
Questo tipo di caching viene chiamato buffer caching.
Nei sistemi moderni la buffer cache è unica e viene usata sia per la memoria virtuale che per gli accessi al disco, in modo da ottimizzare il tutto.
Si potrebbe pensare di usare un unico grande file usato per la gestione della memoria virtuale.
Questo sistema non è particolarmente efficiente, perchè per accedere alla memoria virtuale devo entrare nella directory del file unico, ed usare i metodi di accesso del FS.
Se sono a conoscenza che le mie pagine di memoria sono di 4Kb, pechè non formattare a blocchi direttamente la memoria, visto che a differenza dei files le pagine non variano?
Quando installiamo linux, ci viene chiesto di creare una partizione per lo swap; oltre al disco normale usato per salvare i vari files, viene creata una piccola partizione sul disco, che serve unicamente per la gestione della memoria virtuale, usando una tecnica di formattazione diversa, opportunamente ideata per questa operazione (molto efficiente).
Solitamente la grandezza di questa memoria di swap andrebbe scelta come il doppio della memoria fisica RAM disponibile. LINUX consente, quando la partizione swap non basta, di aggiungere dei file di swap (più lenti) che però aiutano il processo della memoria virtuale.
Questo tipo di cache è diversa dalle altre cache nel momento in cui l'accesso è sequenziale: nella maggior parte dei casi, l'accesso ai file è proprio sequenziale (come ad esempio la visione di un file). In questi casi la cache non tiene conto dei dati già visualizzati (ad esempio siamo al minuto 25 di un film, la cache non tiene in memoria il minuto 5 del film), proprio perchè leggiamo in sequenza.
Il comportamento della cache, in questo caso, è del tipo free behind e read ahead, ovvero svuota i dati già letti, e possibilmente inzia a leggere in anticipo il pezzo successivo (ad esempio viene caricato in cache il minuto 26 nel momento in cui vediamo il minuto 25 di un film).
Ogni tanto, i dischi vengono corrotti, come i politici; e non ci possiamo fare nulla.
Il caso più semplice è quello della chiavetta che viene staccata al volo senza essere espulsa.
In questi casi il FS si porta in uno stato inconsistente, dove, ad esempio, dei blocchi, che erano stati marcati come occupati, sono in realtà vuoti perchè non abbiamo avuto il tempo materiale per scriverci.
Servono quindi delle utility che ripristinino il file system. Queste utility scansionano tutte le directory e le esaminano, in modo da verificare la loro consistenza. Come conseguenza abbiamo che queste utility funzionano molto male su dischi di grandi dimensioni.
La maggior parte dei FS sono basati sul log. Questo vuol dire che prima di eseguire una qualsiasi operazione, viene scritto in un log che il FS sta tentando di eseguire quella specifica operazione, e poi la esegue.
Se qualcosa "va storto", resta il log per verificare delle operazioni non compiute o compiute male; questo permette di rieseguire tutte le operazioni che non sono state completate, in modo da non corrompere nulla.
FINE CAPITOLO 14