UNIX Dateisysteme
|
|
| Home | Publikationen | Heise (c't/iX) | | Blog | Sitemap | Suchen | Webmaster | |
Den Anfang eines Dateisystem bildet der sogenannte Superblock, der das Dateisystem selbst beschreibt. Er enthält Geometriedaten der Platte, gibt an, wieviele Blöcke das Dateisystem enthält und welche davon Verwaltungsinformationen und welche Daten enthalten.
Das Bild zeigt eine I-Node mit ihren Verweisen auf die Datenblöcke der
Datei.
Bild 1: Am Anfang des Dateisystems steht der
Superblock. Er enthält alle Metainformationen, die das Dateisystem
beschreiben. Der "vordere" Teil des Dateisystems enthält I-Nodes,
Dateiköpfe, die alle Metainformationenen über eine Datei speichern.
In den Datenblöcken sind dann die eigentlichen Nutzdaten untergebracht.
struct dinode
{
/* Typ Feldname Byte-Offset: Beschreibung */
u_short ino_mode; /* 0: Dateityp und Zugriffsrechte */
short ino_nlink; /* 2: Anzahl der Namen der Datei */
uid_t ino_uid; /* 4: Benutzernummer Dateieigentümer */
gid_t ino_gid; /* 6: Gruppennummer Dateieigentümer */
off_t ino_size; /* 8: Größe in Bytes */
time_t ino_atime; /* 16: Zeit des letzten Lesezugriffs */
long ino_atspare; /* in Sekunden seit 1.1.1970, 0 Uhr */
time_t ino_mtime; /* 24: Zeit des letzten Schreibzugriffs */
long ino_mtspare;
time_t ino_ctime; /* 32: Zeit der letzten Statusänderung */
long ino_ctspare;
daddr_t ino_db[NDADDR]; /* 40: Blocknummern der ersten 12 Datenblöcke */
daddr_t ino_ib[NIADDR]; /* 88: Blocknummern der 3 indirekten Datenblöcke */
long ino_blocks; /* 100: Größe der Datei in Blöcken */
long ino_gen; /* 104: Generationsnummer (NFS) */
};
Bild 2: Aufbau einer I-Node eines modernen UNIX-Dateisystems. Die
Datenstruktur paßt in ein Feld von 128 Bytes, sodaß ein
Hardware-Plattenblock 8 I-Nodes halten kann. Sie enthält alle
Metainformationen über eine Datei mit Ausnahme der Namen der Datei.
Untersucht man die durchschnittliche Länge von Dateien in einem
Dateisystem, dann stellt man fest, das kurze Dateien relativ häufig
auftreten. Daher versucht UNIX, die Blocknummern der ersten paar
Plattenblöcke einer Datei direkt in der I-Node zu speichern. In der
I-Node aus Bild 2 werden die ersten 12 Datenblöcke einer Datei
im Feld ino_db[] abgelegt. Wenn über ihre I-Nodenummer auf diese
Datei zugegriffen wird, stehen die 12 direkten Datenblöcke der Datei
also ohne weitere Leseoperation zur Verfügung.
Für große Dateien ist dieses Verfahren natürlich nicht praktikabel, denn die I-Node würde dann sehr groß werden. Wächst eine Datei über die Größe von 12 Datenblöcken hinaus, besorgt UNIX einen freien Datenblock und trägt diesen als ersten indirekten Datenblock einer Datei ein. In diesem indirekten Datenblock werden jetzt die Blocknummern der weiteren Datenblöcke einer Datei abgelegt. Bei einer angenommenen Blockgröße von einem Kilobyte können in einem indirekten Block 256 Blocknummern gespeichert werden, von denen jede einen Datenblock von einem Kilobyte adressiert. Zusammen mit den direkten Datenblöcken können also Dateien bis zu einer Größe von 266 KB angelegt werden, ohne daß mehr als eine Ebene der Indirektion durchlaufen werden muß. Modernere Dateisysteme, die mit einer Blockgröße von 8 Kilobyte arbeiten, bringen 2048 Blocknummern in einem Block unter und können so bis zu 16 Megabyte große Dateien mit einem einzigen indirekten Block verwalten.
Für noch größere Dateien sieht UNIX doppelt indirekte Blöcke vor, die die Blocknummern von einfach indirekten Blöcken enthalten. Diese wiederum zeigen dann endlich auf die Daten. Bei einer Blockgröße von 8 KB kann man mit diesem Schema schon mehr als die vier Gigabyte verwalten, die sich im Größenfeld ino_size einer I-Node verwalten lassen. Bei Dateisystemen mit einer Blockgröße von einem Kilobyte muß dagegen ab einer Dateigröße von 64 MB von einem dreifach indirekten Block Gebrauch gemacht werden (Bild 3). Zum Glück sind zum Laden eines solchen Datenblockes aber keine vier Plattenzugriffe notwendig, denn alle UNIX-Versionen haben einen Plattencache, der häufig benötigte Daten im RAM präsent hält.
Bild 3: Von der I-Node zu den Datenblöcken einer Datei
Bei der Belegung von Plattenblöcken für eine Datei ist UNIX sehr
effizient. Nur diejenigen Blöcke einer Datei, die schon einmal
beschrieben wurden, belegen auch wirklich Platz auf der Platte. Wird
beispielsweise begonnen, einen doppelt indirekten Block zu verwenden,
so wird für diesen zunächst der erste einfach indirekte Block
beschafft und belegt. Die Blocknummern der anderen indirekten Blöcke
werden dagegen einfach auf Null gesetzt. Das führt zu interessanten
Effekten bei Dateien, die nicht durchgehend beschrieben werden: Legt
man unter UNIX eine neue Datei an und bewegt dann den Dateizeiger
irgendwo in die oberen Megabytes, um dort ein einziges Byte zu
beschreiben, dann wird nur der eine Datenblock belegt, der notwendig
ist, um dieses Byte zu speichern (plus der möglicherweise notwendigen
indirekten Blöcke, die notwendig sind, um den Block zu erreichen).
Die Blocknummer aller anderen, nicht verwendeten Blöcke bleiben auf
Null stehen und es werden auch keine Datenblöcke zwischen dem
gespeicherten Byte und dem Dateianfang angefordert. Es entsteht eine
Datei, die in der Verzeichnisausgabe viele Megabytes groß erscheint,
in Wirklichkeit aber nur wenige Kilobytes belegt. Eine solche Datei
nennt man in UNIX eine dünn besetzte Datei (sparse file). Beim Lesen
einer solchen Datei werden für die nicht vorhandenen Blöcke
entsprechend viele Nullbytes zurückgemeldet.
Das kann beim Kopieren oder Sichern solcher Dateien natürlich zu seltsamen Effekten führen, wenn man nicht aufpaßt: Beim naiven Kopieren wird eine Datei von vorne nach hinten durchgelesen und die gelesenen Daten werden in die Zieldatei geschrieben. Während eine dünn besetzte Quelldatei also möglicherweise nur wenige Blöcke wirklich belegt, wird die Zieldatei von vorne nach hinten beschrieben und belegt dann wirklich so viel Platz, wie im Verzeichnis angegeben. Dateien, die ein Abbild eines häufig ebenfalls dünn besetzten Prozeßadreßraumes darstellen, sind oft sparse files: Die shared libraries in einem Linux-System oder Speicherabzüge von gecrashten Programmen unter SunOS sollten tunlichst nicht naiv kopiert werden.
Es ist in UNIX ohne weiteres möglich, mehr als einen Dateinamen für eine Datei zu vergeben. Dazu wird einfach mit der Systemfunktion link() in einem weiteren Verzeichnis ein Namenseintrag gemacht, der dieselbe I-Nodenummer hat wie der erste Name der Datei. Im Feld ino_nlink einer I-Node wird die Anzahl der Namen einer Datei gezählt. Beide Namen einer Datei sind gleichberechtigt und nicht voneinander zu unterscheiden: Man kann nicht sagen, welcher von zwei Namen einer Datei der erste und welcher der zweite Name der Datei war. Anstatt eine Datei zu löschen kann man in UNIX nur die Anzahl ihrer Namen um Eins vermindern. Sobald die Anzahl der Namen einer Datei Null wird, gibt das Betriebssystem dem Plattenplatz frei, der zu einer Datei gehört.
Im BSD Dateisystem sind längere Dateinamen erlaubt. Um die
Platzverschwendung zu minimieren, ist die Struktur eines Verzeichnisses
etwas komplizierter, aber das Prinzip der Zuordnung eines Namens zu einer
I-Nodenummer wird nicht verletzt.
Bild 4: Das alte System V UNIX Dateisystem behandelt Verzeichnisse als
gewöhnliche Dateien mit einer festen Satzstruktur von 16 Byte. Die ersten
beiden Byte enthalten die I-Node Nummer einer Datei, die folgenden 14 Bytes
stellen den Namen der Datei (mit Nullbytes aufgefüllt) dar.
In Bild 5 ist zu sehen, was bei der Auflösung eines Pfadnamens wie "/bin/ls" passiert: Weil der Pfadname mit einem "/" beginnt, durchsucht namei() das Hauptverzeichnis des Prozesses, der den Systemaufruf getätigt hat, nach einem Eintrag für "bin". Sobald die I-Nodenummer für das "bin"-Verzeichnis gefunden ist, kann es nach einem Eintrag für "ls" durchsucht werden. Erst wenn die I-Nodenummer von "ls" bekannt ist, kann die Datei geöffnet oder geladen werden. Bei der Auflösung von Pfadnamen ergibt sich also eine wechselseitige Verkettung von Datenblöcken und I-Nodes: I-Nodes enthalten Zeiger auf Datenblöcke und die Datenblöcke eines Verzeichnisses enthalten Zeiger auf I-Nodes.
Bild 5: Zugriffe beim Auflösen eines Pfadnamens
Da Verzeichnisse auch Dateien sind, haben auch sie diese Zugriffsrechte. In Zusammenhang mit Verzeichnissen werden sie jedoch etwas anders interpretiert: r-Recht an einem Verzeichnis erlaubt einem Benutzer, die Namensliste eines Verzeichnisses zu lesen. w-Recht an einem Verzeichnis gestattet es ihm, Dateien anzulegen oder zu löschen. x-Recht schließlich ist notwendig, um auf die Dateien in einem Verzeichnis zuzugreifen. In Bild 5ist zu sehen, an welchen Stellen welche Zugriffsrechte geprüft werden, wenn die Datei "/bin/ls" zum Lesen geöffnet werden soll: Zunächst einmal muß am Hauptverzeichnis x-Recht vorhanden sein, damit auf die Datei "bin" zugegriffen werden kann, die im Hauptverzeichnis enthalten ist. Danach wird auf ein x-Recht am "bin"-Verzeichnis geprüft, um auf die Datei "ls" zugreifen zu können. Und schließlich muß an der Datei "ls" selbst noch r-Recht vorhanden sein, damit auf die Datenblöcke der Datei lesen zugegriffen werden darf.
x-Recht an einem Verzeichnis ist also immer dann erforderlich, wenn namei() einem Zeiger aus diesem Verzeichnis in die I-Nodetabelle folgen muß. In Bild 5 sind diese aufwärts führenden Pfeile etwas dicker hervorgehoben.
Bild 6: Zwei Dateienden in einem fragmentierten Block
Auf diese Weise werden die Dateienden verschiedener Dateien praktisch
in einem Block zusammenkomprimiert und der Verlust an Plattenplatz
durch nicht ausgenutzte Blöcke wird reduziert. Trotzdem kann das
Dateisystem die meiste Zeit mit vollständigen, großen Blöcken
arbeiten und so hohe Geschwindigkeiten erreichen. Der Nachteil dieses
Systems ist, daß ein Dateiende unter Umständen mehrfach umkopiert
werden muß, wenn eine Datei wächst: Zunächst paßt das Dateiende noch
in sein Fragment hinein, wenn es wächst, muß jedoch ein größeres
Fragment gesucht werden, das jedoch durch weitere Schreiboperationen
sofort wieder überläuft, bis die Datei endlich den nächsten
vollständigen Block füllt. Solche Kopieroperationen lassen sich
vermeiden, wenn Anwendungsprogramme ihre Schreiboperationen der
Blockgröße des Dateisystems anpassen. Da so etwas sehr unbequem ist,
wenn man es selber programmieren muß, nimmt die C-Standardbibliothek
in einem UNIX-System einem diese Arbeit ab: Sie fragt die Blockgröße
des unterliegenden Dateisystems ab und stimmt ihre Schreibzugriffe so
ab, daß mit maximaler Geschwindigkeit geschrieben werden kann.
Weitere Geschwindigkeitsvorteile lassen sich erzielen, wenn man dafür sorgen kann, daß Dateien möglichst hintereinanderliegende Blöcke auf einer Platte belegen. Die Platte kann in diesem Fall ihren internen Cache füllen und die Daten schneller abliefern. Außerdem entfallen Bewegungen des Schreib-/Lesekopfes der Platte. Wie man am Beispiel von DOS sehen kann, ist es leider ist es nicht damit getan, die Blöcke einer Datei hintereinander anzuordnen. Man muß außerdem auch Platz lassen, damit Dateien wachsen können.
Bild 7: Durch unkluge Anordnung von Daten zerstückelt MS-DOS
Dateien in kleine, nicht zusammenhängende Fragmente.
Moderne UNIX-Dateisysteme unterteilen eine Festplatte deswegen in
Streifen von einigen Megabyte Größe, sogenannte cylinder groups.
Jeder dieser Streifen enthält eine eigene kleine I-Nodetabelle und
seinen Anteil an Datenblöcken. Neue Dateien werden in derjenigen
Zylindergruppe angelegt, die im Verhältnis am meisten freie
Datenblöcke aufzuweisen hat. Dadurch wird sichergestellt, daß solche
Dateien gerade nicht direkt hintereinander liegen, sondern genügend
freien Platz haben, um zu wachsen. Bei sehr langen Dateien wird
außerdem nach dem Schreiben von jeweils einem Megabyte an Daten ein
Wechsel der Zylindergruppe erzwungen: Man geht davon aus, daß man
sehr lange Dateien sowieso nicht in einem Stück zusammenhängend
lagern kann. Stattdessen versucht man, die einzelnen Stücke möglichst
groß zu machen.
Bild 8: Das BSD Fast Filing System unterteilt die Platte in Streifen von
einigen MB Größe. Das Betriebssystem versucht durch verschiedene Verfahren,
das Verhältnis von belegten Datenblöcken zu belegten I-Nodes in allen cylinder
groups einer Platte in etwa ausgewogen zu halten. Dadurch ist das
Dateisystem effektiv selbstdefragmentierend.
Außerdem würde eine sehr lange Datei alle
Datenblöcke, aber nur eine einzige I-Node in einer Zylindergruppe
belegen. Die Daten zu den I-Nodes aller weiteren Dateien in derselben
Zylindergruppe müßten dann auf andere Zylindergruppen verlagert
werden, was dort wiederum das ausgewogene Verhältnis zwischen freien
I-Nodes und freiend Datenblöcken stören würde. Dadurch, daß man
jeweils ein Megabyte einer sehr großen Datei auf eine andere
Zylindergruppe verlager, wird der Platz auf der Platte gleichmäßig
verbraucht. Man verhindert, daß andere Dateien dann nicht mehr
günstig auf der Platte angeordnet werden können.
Das UNIX-Dateisystem ist dem nur halb so alten, aber wesentlich weniger effektiv organisierten MS-DOS Dateisystem in Sachen Geschwindigkeit, Zugriffsschutz und Benutzerfreundlichkeit weit überlegen. Die Ideen der UNIX-Entwickler waren letztlich so überzeugend, daß sie sich letztendlich im OS/2 HPFS und schließlich auch im Dateisystem von Windows NT wiederfinden.
| Top | Geändert:01-Jan-1970 01:00:00 Url: http://kris.koehntopp.de/artikel/dateisysteme/index.html |