Dateisysteme und Persistenz
Wie aus einer langen Reihe nummerierter Blöcke Ordner und Dateien werden, warum „gelöscht“ selten weg heißt – und wie ein Dateisystem einen Stromausfall mitten im Schreiben übersteht.
Eine SSD ist für das Betriebssystem eine lange Reihe nummerierter Blöcke, meist 4 KB groß. Ein Dateisystem macht daraus Dateien und Ordner: Es merkt sich, welche Blöcke zu welcher Datei gehören, welche frei sind, wie die Dateien heißen, wem sie gehören und wann sie geändert wurden.
Die schwierigste Aufgabe ist Persistenz: Schon eine kleine Änderung braucht mehrere Schreibvorgänge an verschiedenen Stellen. Fällt mittendrin der Strom aus, darf das Dateisystem nicht kaputt sein. Moderne Dateisysteme lösen das mit einem Journal oder mit Copy-on-Write. Und für Programme gilt: Wirklich auf der Platte ist erst, was mit fsync erzwungen wurde.
Von Blöcken zu Dateien
Ein Dateisystem verwaltet im Kern drei Dinge. Am Beispiel der Unix-Familie, also Linux und macOS – Windows mit NTFS funktioniert im Prinzip ähnlich:
Inode
Der Steckbrief einer Datei: Größe, Besitzer, Rechte, Zeitstempel und die Liste ihrer Blöcke. Nicht enthalten: ihr Name.
Verzeichnis
Eine besondere Datei, die nur eine Tabelle enthält: Name → Inode-Nummer. Ein Ordner „enthält“ also keine Dateien, sondern Namen.
Freiliste
Eine Übersicht, welche Blöcke frei sind – oft eine Bitmap mit einem Bit pro Block.
Dass Name und Datei getrennt sind, hat überraschende Folgen. Eine Datei kann mehrere Namen haben – das sind Hardlinks. Und Löschen entfernt zunächst nur einen Namen. Probier es aus:
Zwei Dinge aus dem Experiment sind in der Praxis wichtig:
- Gelöscht heißt nicht weg. Die Blöcke gelten als frei, ihr Inhalt bleibt, bis sie neu beschrieben werden. Darauf beruhen Wiederherstellungsprogramme – und deshalb muss man Datenträger vor dem Verkauf sicher löschen. Bei SSDs sorgt der TRIM-Befehl allerdings dafür, dass freigegebene Blöcke bald tatsächlich gelöscht werden.
- Fragmentierung. Wächst eine Datei und dahinter ist kein Platz, landen ihre Blöcke verstreut. Auf Festplatten mit mechanischem Lesekopf war das teuer, daher das Defragmentieren. Auf SSDs spielt es kaum eine Rolle.
lsof | grep deleted zeigt solche Fälle.Das eigentliche Problem: der Stromausfall
Hängt man an eine Datei einen Block an, müssen drei Dinge auf die Platte: der Datenblock selbst, der aktualisierte Inode (neue Größe, neuer Blockverweis) und die Bitmap (Block jetzt belegt). Das sind drei Schreibvorgänge an drei Stellen – und die Platte kann immer nur einen nach dem anderen garantieren. Was passiert, wenn dazwischen der Strom ausfällt?
Früher half nach einem Absturz nur eines: fsck oder chkdsk prüfte beim Neustart das ganze Dateisystem auf Widersprüche. Bei großen Platten dauerte das Stunden. Die beiden modernen Verfahren machen das überflüssig:
Journaling
Erst die geplanten Änderungen in ein Protokoll schreiben, mit einem Abschluss-Eintrag versiegeln, dann ausführen. Nach einem Absturz wird nur das Journal gelesen: Versiegelte Einträge werden wiederholt, unversiegelte verworfen. Beispiele: ext4, NTFS, XFS.
Copy-on-Write
Nie etwas überschreiben. Neue Versionen werden an freie Stellen geschrieben, und am Ende wird mit einem einzigen Schreibvorgang der oberste Zeiger umgehängt. Beispiele: Btrfs, ZFS, Apples APFS.
Bonus: Snapshots
Wer nie überschreibt, kann alte Versionen fast kostenlos aufheben. Copy-on-Write-Systeme können deshalb in Sekunden Schnappschüsse des ganzen Dateisystems anlegen – Grundlage etwa für Time Machine.
Wer genau hinschaut, erkennt das Muster: Ein Journal ist dasselbe wie das Write-Ahead-Log einer Datenbank, und das Umhängen eines Zeigers ist eine atomare Operation. Dateisysteme und Datenbanken lösen hier dasselbe Problem.
„Gespeichert“ ist nicht gespeichert: fsync
Wie im Eintrag Betriebssystem beschrieben, kehrt ein Schreibaufruf zurück, sobald der Kernel die Daten in seinem Zwischenspeicher im RAM hat. Auf die Platte schreibt er sie später, gebündelt und in günstiger Reihenfolge. Das ist schnell – aber bei einem Stromausfall sind die letzten Sekunden weg. Und selbst die SSD hat noch einen eigenen Zwischenspeicher.
Wer eine Garantie braucht, ruft fsync auf: Der Aufruf kehrt erst zurück, wenn die Daten dauerhaft gespeichert sind. Datenbanken tun das bei jedem Commit – das ist einer der Gründe, warum ein Commit messbar Zeit kostet.
Eine Datei sicher ersetzen
Eine Konfigurationsdatei direkt zu überschreiben, ist gefährlich: Stürzt das Programm mittendrin ab, ist sie halb alt, halb neu. Das bewährte Muster: in eine temporäre Datei schreiben, erzwingen, dann umbenennen. Ein Umbenennen innerhalb desselben Dateisystems ist atomar – danach sieht jeder entweder die alte oder die neue Datei, nie eine halbe.
Path ziel = Path.of("config.json");
Path tmp = ziel.resolveSibling("config.json.tmp");
try (FileChannel ch = FileChannel.open(tmp, CREATE, WRITE, TRUNCATE_EXISTING)) {
ch.write(ByteBuffer.wrap(daten));
ch.force(true); // fsync: Daten und Metadaten auf die Platte
}
Files.move(tmp, ziel, StandardCopyOption.ATOMIC_MOVE); // alt oder neu – nie halbGanz gründlich wäre es, danach auch das Verzeichnis zu synchronisieren, damit der neue Name selbst einen Absturz übersteht. Das geht in Java nur plattformabhängig; für die meisten Anwendungen genügt das Muster oben.
Was SSDs anders machen
Eine SSD kann einen Block nicht einfach überschreiben. Flash-Speicher lässt sich nur in großen Einheiten löschen, und jede Zelle verträgt nur eine begrenzte Zahl von Löschvorgängen. Ein Controller in der SSD, die Flash Translation Layer, schreibt deshalb jede Änderung an eine neue Stelle, führt Buch, wo welcher Block gerade wirklich liegt, verteilt die Abnutzung gleichmäßig und räumt im Hintergrund auf. Die SSD betreibt also intern selbst so etwas wie Copy-on-Write – unter einem Dateisystem, das davon nichts weiß.
Die gängigen Dateisysteme
| Dateisystem | Wo | Besonderheit |
|---|---|---|
| FAT32 | alte USB-Sticks, Kameras | von allen lesbar, aber einzelne Dateien höchstens 4 GB, kein Journal |
| exFAT | USB-Sticks, SD-Karten | Nachfolger ohne 4-GB-Grenze, ebenfalls überall lesbar |
| NTFS | Windows | Journaling, Rechte, Verschlüsselung |
| ext4 | Linux, Standard vieler Distributionen | Journaling, bewährt und robust |
| XFS | Linux-Server | Journaling, stark bei großen Dateien |
| Btrfs | Linux | Copy-on-Write, Snapshots, Prüfsummen |
| ZFS | Server, Speichersysteme | Copy-on-Write, Prüfsummen für alle Daten, eigene Plattenverwaltung |
| APFS | macOS, iOS | Copy-on-Write, Snapshots, Klone von Dateien ohne Kopieren |
In der Cloud kommt eine weitere Art hinzu: Objektspeicher wie Amazon S3. Er sieht aus wie ein Dateisystem, ist aber keines – es gibt kein Umbenennen, kein Anhängen und keine echten Ordner, nur Schlüssel und unveränderliche Objekte. Wer dort Dateisystem-Gewohnheiten voraussetzt, erlebt Überraschungen.
Für Entwickler übersetzt
| Konzept | In Java |
|---|---|
| Inode-Nummer | Files.getAttribute(p, "unix:ino") – auf Linux und macOS |
| Hardlink, Symlink | Files.createLink(neu, bestehend), Files.createSymbolicLink(link, ziel) |
| Linkzähler, Rechte, Zeiten | Files.readAttributes(p, PosixFileAttributes.class), "unix:nlink" |
| fsync | FileChannel.force(true), oder Öffnen mit StandardOpenOption.SYNC bzw. DSYNC |
| atomares Umbenennen | Files.move(..., ATOMIC_MOVE) – wirft eine Exception, wenn das Dateisystem es nicht garantiert |
| Seitencache direkt nutzen | FileChannel.map(...) blendet eine Datei in den Speicher ein; Änderungen gehen über den Seitencache des Kernels |
| Änderungen beobachten | WatchService – nutzt die Benachrichtigungen des Betriebssystems |
| Offene Dateien | try-with-resources: Ein vergessener Stream hält Dateien offen – unter Windows lassen sie sich dann nicht löschen, unter Linux bleibt ihr Platz belegt |
Häufige Irrtümer
close() ist sie beim Kernel. Auf der Platte ist sie erst nach fsync oder irgendwann später.