3 Punkte von GN⁺ 2025-11-05 | 1 Kommentare | Auf WhatsApp teilen
  • Erklärt die Struktur des Prozessspeichers unter Linux auf Ebene des tatsächlichen Verhaltens und erläutert Schritt für Schritt die Beziehung zwischen virtuellem Adressraum und physischem Speicher
  • Beschreibt anhand zentraler Mechanismen wie Seitentabellen, VMA, mmap, Page Fault, CoW, wie Prozesse Speicher konkret besitzen und darauf zugreifen
  • Zeigt, wie sich über das /proc-Dateisystem der Speicherzustand pro Prozess beobachten lässt, und stellt die Rolle fortgeschrittener Diagnosewerkzeuge wie pagemap und kpageflags vor
  • Behandelt Performance-Optimierung und Dirty-Tracking im User Space mit aktuellen Kernel-Funktionen wie Transparent Huge Pages (THP), userfaultfd und PAGEMAP_SCAN
  • Erklärt außerdem sicherheits- und performancebezogene Kernel-Designprinzipien wie PTI als Gegenmaßnahme gegen Meltdown, TLB-Flushes und die W^X-Richtlinie und vermittelt so ein Gesamtverständnis des Linux-Speichermanagements

Grundstruktur des Prozessspeichers

  • Wenn ein Programm ausgeführt wird, wirkt es so, als gäbe es einen riesigen zusammenhängenden Speicherbereich, tatsächlich wird dieser aber vom Linux-Kernel dynamisch in Seiteneinheiten aufgebaut
    • Die CPU schlägt in der Seitentabelle nach und wandelt virtuelle Adressen in physische Frames um
    • Gibt es keine Zuordnung, tritt ein Page Fault auf, und der Kernel weist eine neue Seite zu oder gibt einen Fehler zurück
  • Wenn der physische RAM knapp wird, verschiebt der Kernel ungenutzte Seiten auf den Datenträger oder entfernt Dateiseiten, um Platz zu schaffen
  • /proc ist ein virtuelles Dateisystem, das der Kernel im Speicher aufbaut und das Prozess- und Kernelzustände in Dateiform offenlegt

Adressraum und VMA

  • Jeder Prozess besitzt ein Adressraumobjekt, das intern aus mehreren VMA (Virtual Memory Areas) besteht
    • Eine VMA ist ein zusammenhängender Adressbereich mit denselben Rechten (R/W/X) und demselben Backend (anonymer Speicher oder Datei)
  • Seitentabellen sind die von der Hardware referenzierten Strukturen und speichern die Zuordnungsinformationen (PTE) zwischen virtuellen und physischen Seiten
  • Änderungen am Adressraum erfolgen über drei Systemaufrufe
    • mmap: neuen Bereich anlegen
    • mprotect: Rechte ändern
    • munmap: Mapping entfernen
  • Seiten haben 4 KiB als Basiseinheit, einige Systeme unterstützen auch große Seiten mit 2 MiB oder 1 GiB

Speicheraufbau mit /proc/self/maps ansehen

  • Mit dem Befehl cat /proc/self/maps lässt sich die Speicherbelegung eines Prozesses prüfen
    • Sichtbar sind Code, Daten und bss der ausführbaren Datei, Heap, anonyme Mappings, Shared Libraries, Stack usw.
  • Die Bereiche [vdso] und [vvar] sind vom Kernel gemappter Code und Daten für schnelle Systemaufrufe

Funktionsweise von mmap

  • mmap ist keine unmittelbare Speicherallokation, sondern eine vermerkte Zusage im Adressraum
    • Seiten werden erst beim ersten Zugriff zugewiesen
  • Bei Dateimappings muss offset seitenaligned sein, und ein Zugriff über das Dateiende hinaus löst SIGBUS aus
  • MAP_SHARED wird direkt in die Datei zurückgeschrieben, MAP_PRIVATE erzeugt über Copy-on-Write (CoW) unabhängige Seiten
  • MAP_FIXED_NOREPLACE sorgt für mehr Sicherheit, indem es fehlschlägt, wenn an der angegebenen Adresse bereits ein Mapping existiert

Erster Zugriff und Page Fault

  • Beim ersten Zugriff auf ein neues Mapping findet die CPU keinen Eintrag in der Seitentabelle, wodurch ein Page Fault entsteht
    • Der Kernel prüft die Gültigkeit der Adresse, Zugriffsrechte und das Vorhandensein des Bereichs
    • Bei anonymem Mapping wird eine neue, mit Nullen gefüllte Seite zugewiesen, bei Dateimapping wird aus dem Page Cache gelesen
  • Ein minor fault liegt vor, wenn die Daten bereits im RAM sind, ein major fault, wenn Platten-I/O nötig ist
  • Der Stack ist durch eine Guard Page geschützt; ein zu weit nach unten gehender Zugriff führt zu SIGSEGV

fork() und Copy-on-Write bei MAP_PRIVATE

  • Beim fork teilen sich Eltern- und Kindprozess dieselben physischen Seiten, die beide als read-only markiert werden
    • Erst beim Schreiben wird eine neue Seite kopiert, damit beide unabhängig bleiben
  • Dateimappings mit MAP_PRIVATE arbeiten nach demselben Prinzip
  • Relevante Optionen
    • vfork: gemeinsamer Adressraum mit dem Elternprozess
    • clone(CLONE_VM): erzeugt Threads
    • MADV_DONTFORK, MADV_WIPEONFORK: Mapping im Kindprozess ausschließen oder mit Nullen initialisieren

Rechte ändern und TLB-Invalidierung

  • Wenn mprotect Seitenrechte ändert, führt der Kernel eine Aufteilung der VMA und Änderungen an den Seitentabellen durch und invalidiert anschließend den TLB
  • Gemäß der W^X-Richtlinie darf eine Seite nicht gleichzeitig beschreibbar und ausführbar sein
  • Der TLB (Translation Lookaside Buffer) ist ein Cache für aktuelle Adressübersetzungen; seine Invalidierung verursacht kurzzeitig Verzögerungen

Detaillierte Beobachtung über /proc

  • Mit /proc/<pid>/maps, smaps und smaps_rollup lassen sich Rechte, RSS und HugePage-Nutzung pro Bereich prüfen
  • /proc/<pid>/pagemap liefert Zustände auf Seitenebene (vorhanden, geswappt, PFN usw.), PFNs sind für normale Benutzer jedoch nicht sichtbar
  • /proc/kpagecount und /proc/kpageflags zeigen pro PFN die Anzahl der Mappings und Seiteneigenschaften wie anonym, Datei, dirty usw.
  • Mit mincore und SEEK_DATA/SEEK_HOLE lassen sich Daten- und Lochbereiche in Sparse Files identifizieren
  • Durch die Kombination von PAGEMAP_SCAN und userfaultfd lässt sich Dirty-Tracking im User Space implementieren

Transparent Huge Pages (THP) und mTHP

  • THP bündelt häufig genutzten Speicher automatisch zu großen Seiten (z. B. 2 MiB), um die TLB-Effizienz zu verbessern
    • Der Thread khugepaged fasst benachbarte Seiten zusammen
  • mTHP unterstützt variable große Seiten (Folio-Größen) wie 16 KiB oder 64 KiB
  • Ob dies genutzt wird, lässt sich in /proc/self/smaps über AnonHugePages und FilePmdMapped prüfen
  • Systemweite Einstellungen werden unter /sys/kernel/mm/transparent_hugepage/ verwaltet
  • Mit MADV_HUGEPAGE und MADV_NOHUGEPAGE ist eine Steuerung pro Bereich möglich

Dirty-Tracking im User Space

  • Mit userfaultfd und PAGEMAP_SCAN lassen sich nur geänderte Seiten kopieren
    • Der Kernel führt Scannen und Schreibschutz in einer einzigen atomaren Operation aus
    • Das ist effizient für Snapshots, Live-Migration und ähnliche Szenarien

Mechanismus von TLB-Flushes

  • Auf x86 erfolgt die TLB-Invalidierung auf zwei Arten
    • INVLPG: invalidiert eine einzelne Seite
    • vollständiger Flush durch erneutes Laden der Wurzel der Seitentabelle
  • PCID und INVPCID ermöglichen die Verwaltung prozessbezogener TLB-Tags und reduzieren unnötige Flushes
  • tlb_single_page_flush_ceiling ist der Schwellenwert, anhand dessen der Kernel zwischen seitenweisem und vollständigem Flush wählt

Gegenmaßnahme gegen Meltdown: Page Table Isolation (PTI)

  • Meltdown ist eine Schwachstelle, bei der Kernel-Daten während spekulativer Ausführung über den Cache offengelegt werden können
  • Linux trennt mit PTI (Page Table Isolation) den Benutzer- und Kernel-Adressraum
    • Beim Eintritt wird über einen CR3-Wechsel eine kernel-exklusive Seitentabelle verwendet
    • PCID hilft dabei, TLB-Flushes zu minimieren
  • PTI ist standardmäßig aktiviert und kann mit nopti deaktiviert werden

Sicheres Vorgehen des Kernels bei Mapping-Änderungen

  • Bei Änderungen an Mappings ist die Reihenfolge
    1. Cache-Regeln behandeln
    2. Seitentabellen ändern
    3. TLB invalidieren
  • Auch bei kernelinternen Mappings (vmap, vmalloc) werden vor und nach I/O Cache und TLB synchronisiert
  • Auf einigen Architekturen ist nach dem Kopieren von Code ein Instruction-Cache-Flush nötig

Stack- und Aufrufstruktur auf x86

  • Im 64-Bit-Modus werden die Register RIP, RSP und RBP verwendet, der Stack wächst nach unten
  • Nach der System V AMD64 ABI werden Argumente über RDI, RSI, RDX, RCX, R8, R9 übergeben, Rückgabewerte über RAX
  • Der User Mode läuft in Ring 3, der Kernel in Ring 0; Systemaufrufe und Interrupts wechseln über Gates dazwischen

Fehlersituationen und Diagnose

  • mmapEINVAL: Fehler bei der Ausrichtung des Datei-Offsets
  • mmapENOMEM: zu wenig virtueller Raum oder Overcommit-Beschränkung
  • Zugriff auf Dateimapping → SIGBUS: Zugriff hinter EOF
  • mprotect(PROT_EXEC)EACCES: noexec-Mount oder W^X-Richtlinie
  • RSS-Anstieg nach fork(): Seitenkopien durch CoW
  • Vorhandenes Mapping mit MAP_FIXED überschrieben → MAP_FIXED_NOREPLACE empfohlen

Checkliste für die Praxis

  • Speicher sofort reservieren: mmap + PROT_READ|PROT_WRITE + MAP_PRIVATE|MAP_ANONYMOUS
  • Bei Codegenerierung: W^X einhalten, mprotect(PROT_READ|PROT_EXEC)
  • Bei Dateimappings: offset seitenaligned, kein Zugriff hinter EOF
  • Bei vielen Page Faults: MADV_WILLNEED oder vorab zugreifen
  • Speicheranalyse: /proc/<pid>/smaps_rollup/proc/<pid>/maps
  • fork großer Prozesse: CoW berücksichtigen, im Kind exec verwenden
  • In latenzkritischen Umgebungen: THP/mTHP, mlock und TLB-Verhalten beobachten

1 Kommentare

 
GN⁺ 2025-11-05
Hacker-News-Kommentare
  • Ich mag solche kurzen Erklärtexte wirklich sehr
    Selbst wenn ich den Inhalt schon kenne, hilft es, ihn beim Lesen noch einmal zu überprüfen

  • Wenn ich Formulierungen wie „mmap, without the fog“ sehe, wirkt das auf mich, als wäre es ein mit LLM mitverfasster Text, und das macht mich unnötig nervös und genervt

    • Der Ton des Textes fühlt sich so an, als hätte man Gemini um eine einfache Erklärung gebeten
      Dazu kommt noch ein seltsamer Ausdruck wie „without the fog“, was den Eindruck verstärkt, dass ChatGPT mitgeschrieben hat
  • Wenn ich von Instruction Pipelining lese, bekomme ich Lust, in die Zeit einfacher Architekturen wie dem 6502 zurückzukehren
    Damals funktionierte alles „so wie es ist“, ohne kompliziertes Mapping oder Proxys
    Mit schnellen Interconnects könnte man vielleicht wieder von solcher Einfachheit träumen

    • Natürlich erkenne ich an, dass solche „Tricksereien (cheats)“ zur Leistungssteigerung beigetragen haben
      Aber wenn man sich Probleme wie Meltdown und Spectre ansieht, sieht man auch klar den Preis der gestiegenen Komplexität
      Gerade jetzt, wo das Mooresche Gesetz an seine Grenzen stößt, frage ich mich, ob dieser Komplexitäts-Trade-off wirklich optimal ist
    • Eigentlich erklärt der Artikel das Konzept des virtuellen Speichers (virtual memory), und das ist eine Technologie, die dem 6502 um etwa zehn Jahre vorausgeht
    • Es stimmt zwar, dass die Komplexität zugenommen hat, aber wir haben auch viel dadurch gewonnen
      Ich denke nicht, dass Einfachheit automatisch besser ist
    • Ich frage mich allerdings, warum man sich nach solcher Einfachheit sehnt
  • Es erscheint die Meldung, dass die Website als gefährliche oder unsichere Domain blockiert wurde

    • Verwendest du vielleicht einen Firmenlaptop? Die Sicherheitsabteilung des Unternehmens vertraut .xyz-Domains womöglich nicht
    • Vermutlich hat die Sicherheitssoftware einfach falsch angeschlagen
      Das VirusTotal-Prüfergebnis sieht unproblematisch aus
    • Sieht nach einem einfachen Fehlalarm (false alarm) aus
    • Ich frage mich, welcher Browser das blockiert hat
    • Lustig, lol
  • Ich frage mich, was damit gemeint ist, dass der Fehlerbericht nur „Rauschen (noise)“ sei