Prozessspeicher unter Linux verständlich erkunden
(0xkato.xyz)- 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 wiepagemapundkpageflagsvor - 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
/procist 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 anlegenmprotect: Rechte ändernmunmap: 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/mapslä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
mmapist keine unmittelbare Speicherallokation, sondern eine vermerkte Zusage im Adressraum- Seiten werden erst beim ersten Zugriff zugewiesen
- Bei Dateimappings muss
offsetseitenaligned sein, und ein Zugriff über das Dateiende hinaus löstSIGBUSaus MAP_SHAREDwird direkt in die Datei zurückgeschrieben,MAP_PRIVATEerzeugt über Copy-on-Write (CoW) unabhängige SeitenMAP_FIXED_NOREPLACEsorgt 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
forkteilen 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_PRIVATEarbeiten nach demselben Prinzip - Relevante Optionen
vfork: gemeinsamer Adressraum mit dem Elternprozessclone(CLONE_VM): erzeugt ThreadsMADV_DONTFORK,MADV_WIPEONFORK: Mapping im Kindprozess ausschließen oder mit Nullen initialisieren
Rechte ändern und TLB-Invalidierung
- Wenn
mprotectSeitenrechte ä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,smapsundsmaps_rolluplassen sich Rechte, RSS und HugePage-Nutzung pro Bereich prüfen /proc/<pid>/pagemapliefert Zustände auf Seitenebene (vorhanden, geswappt, PFN usw.), PFNs sind für normale Benutzer jedoch nicht sichtbar/proc/kpagecountund/proc/kpageflagszeigen pro PFN die Anzahl der Mappings und Seiteneigenschaften wie anonym, Datei, dirty usw.- Mit
mincoreundSEEK_DATA/SEEK_HOLElassen sich Daten- und Lochbereiche in Sparse Files identifizieren - Durch die Kombination von
PAGEMAP_SCANunduserfaultfdlä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
khugepagedfasst benachbarte Seiten zusammen
- Der Thread
- 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überAnonHugePagesundFilePmdMappedprüfen - Systemweite Einstellungen werden unter
/sys/kernel/mm/transparent_hugepage/verwaltet - Mit
MADV_HUGEPAGEundMADV_NOHUGEPAGEist eine Steuerung pro Bereich möglich
Dirty-Tracking im User Space
- Mit
userfaultfdundPAGEMAP_SCANlassen 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
PCIDundINVPCIDermöglichen die Verwaltung prozessbezogener TLB-Tags und reduzieren unnötige Flushestlb_single_page_flush_ceilingist 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 PCIDhilft dabei, TLB-Flushes zu minimieren
- Beim Eintritt wird über einen
- PTI ist standardmäßig aktiviert und kann mit
noptideaktiviert werden
Sicheres Vorgehen des Kernels bei Mapping-Änderungen
- Bei Änderungen an Mappings ist die Reihenfolge
- Cache-Regeln behandeln
- Seitentabellen ändern
- 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
mmap→EINVAL: Fehler bei der Ausrichtung des Datei-Offsetsmmap→ENOMEM: 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_NOREPLACEempfohlen
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:
offsetseitenaligned, kein Zugriff hinter EOF - Bei vielen Page Faults:
MADV_WILLNEEDoder vorab zugreifen - Speicheranalyse:
/proc/<pid>/smaps_rollup→/proc/<pid>/maps forkgroßer Prozesse: CoW berücksichtigen, im Kindexecverwenden- In latenzkritischen Umgebungen: THP/mTHP,
mlockund TLB-Verhalten beobachten
1 Kommentare
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
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
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
Ich denke nicht, dass Einfachheit automatisch besser ist
Es erscheint die Meldung, dass die Website als gefährliche oder unsichere Domain blockiert wurde
Das VirusTotal-Prüfergebnis sieht unproblematisch aus
Ich frage mich, was damit gemeint ist, dass der Fehlerbericht nur „Rauschen (noise)“ sei