Grundlagen

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.

Lesestand

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:

i

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:

Ein Dateisystem zum Anfassen

Verzeichnis /home/sven

NameInode

Inode-Tabelle

Nr.LinksBlöcke

Die SSD: 48 Blöcke

Verwaltung (Superblock, Bitmap, Inodes)freifrei – alte Daten noch lesbar

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.
Ein Klassiker im Betrieb: Eine riesige Logdatei wird gelöscht, aber die Platte bleibt voll. Der Grund: Der Prozess, der hineinschreibt, hat sie noch geöffnet. Auch ein offener Dateizeiger zählt als Verweis – erst wenn der Prozess die Datei schließt oder neu startet, gibt das Dateisystem die Blöcke frei. 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?

Stromausfall beim Anhängen eines Blocks
Strom fällt aus nach

    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:

    J

    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.

    C

    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 halb

    Ganz 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

    DateisystemWoBesonderheit
    FAT32alte USB-Sticks, Kamerasvon allen lesbar, aber einzelne Dateien höchstens 4 GB, kein Journal
    exFATUSB-Sticks, SD-KartenNachfolger ohne 4-GB-Grenze, ebenfalls überall lesbar
    NTFSWindowsJournaling, Rechte, Verschlüsselung
    ext4Linux, Standard vieler DistributionenJournaling, bewährt und robust
    XFSLinux-ServerJournaling, stark bei großen Dateien
    BtrfsLinuxCopy-on-Write, Snapshots, Prüfsummen
    ZFSServer, SpeichersystemeCopy-on-Write, Prüfsummen für alle Daten, eigene Plattenverwaltung
    APFSmacOS, iOSCopy-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

    KonzeptIn Java
    Inode-NummerFiles.getAttribute(p, "unix:ino") – auf Linux und macOS
    Hardlink, SymlinkFiles.createLink(neu, bestehend), Files.createSymbolicLink(link, ziel)
    Linkzähler, Rechte, ZeitenFiles.readAttributes(p, PosixFileAttributes.class), "unix:nlink"
    fsyncFileChannel.force(true), oder Öffnen mit StandardOpenOption.SYNC bzw. DSYNC
    atomares UmbenennenFiles.move(..., ATOMIC_MOVE) – wirft eine Exception, wenn das Dateisystem es nicht garantiert
    Seitencache direkt nutzenFileChannel.map(...) blendet eine Datei in den Speicher ein; Änderungen gehen über den Seitencache des Kernels
    Änderungen beobachtenWatchService – nutzt die Benachrichtigungen des Betriebssystems
    Offene Dateientry-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

    „Gelöscht ist gelöscht.“ – Gelöscht ist zunächst nur der Name. Die Daten bleiben, bis die Blöcke neu beschrieben oder per TRIM geleert werden.
    „Ein Ordner enthält Dateien.“ – Er enthält Namen, die auf Inodes verweisen. Deshalb ist Verschieben innerhalb eines Dateisystems sofort fertig, egal wie groß die Datei ist – es wird nur ein Eintrag umgehängt.
    „Nach close() ist die Datei auf der Platte.“ – Nach close() ist sie beim Kernel. Auf der Platte ist sie erst nach fsync oder irgendwann später.
    „Ein Journal schützt meine Daten.“ – Meist schützt es nur die Struktur des Dateisystems. Viele Systeme protokollieren standardmäßig nur Metadaten; der Inhalt einer Datei kann nach einem Absturz trotzdem alt oder unvollständig sein.
    „Defragmentieren macht den Rechner schneller.“ – Bei Festplatten mit Lesekopf ja. Bei SSDs bringt es nichts und verschleißt sie unnötig.

    Verwandte Begriffe