Kapitelübersicht
  1. 1 Einführung
  2. 2 Architektur-Überblick
  3. 3 Der Bootvorgang
  4. 4 Systemaufrufe
  5. 5 Prozesse & Threads
  6. 6 Der Scheduler
  7. 7 Speicherverwaltung
  8. 8 Interrupts & verzögerte Arbeit
  9. 9 Synchronisation
  10. 10 VFS & Dateisysteme
  11. 11 Block-I/O
  12. 12 Gerätetreiber
  13. 13 Netzwerk-Stack
  14. 14 Interprozesskommunikation
  15. 15 Sicherheit & Isolation
  16. 16 eBPF
  17. 17 Kernel-Entwicklung

Kapitel 7

Speicherverwaltung

Wie der Kernel physischen Arbeitsspeicher verwaltet und jedem Prozess einen eigenen virtuellen Adressraum bietet – Seitentabellen und TLB, Seitenfehler, Buddy- und Slab-Allocator, Page Cache, Swap und der OOM-Killer.

Kurz gesagt

Jedes Programm sieht seinen eigenen, scheinbar riesigen Speicher. Die Hardware übersetzt diese virtuellen Adressen über Seitentabellen in echte Adressen im Arbeitsspeicher. Der Kernel richtet diese Tabellen ein und stellt Speicher erst dann tatsächlich bereit, wenn er benutzt wird. Freier Arbeitsspeicher bleibt dabei nicht ungenutzt, sondern dient als Zwischenspeicher für Dateien. Wird der Speicher knapp, lagert der Kernel Daten aus. Im Notfall beendet er ein Programm.

Nach diesem Kapitel …

  • erklären, wie die MMU eine virtuelle Adresse über mehrstufige Seitentabellen übersetzt und welche Rolle der TLB spielt
  • den Aufbau des Adressraums eines Prozesses aus VMAs beschreiben
  • die verschiedenen Arten von Seitenfehlern unterscheiden und ihren Ablauf nachvollziehen
  • Buddy-Allocator, SLUB und vmalloc einordnen und ihre Einsatzzwecke nennen
  • Page Cache, Speicherrückgewinnung, Swap und Overcommit erklären
  • die Ausgabe von free und /proc/meminfo richtig interpretieren

Zwei Sichten auf den Speicher

Die Speicherverwaltung arbeitet mit zwei Arten von Adressen:

Die Übersetzung zwischen beiden erledigt die MMUMMU (Memory Management Unit)Hardware-Baustein der CPU, der virtuelle in physische Adressen übersetzt und dabei Zugriffsrechte prüft. in Hardware, bei jedem einzelnen Speicherzugriff. Der Kernel gibt nur die Regeln vor, indem er Seitentabellen anlegt. Diese Trennung bringt drei große Vorteile:

  1. Isolation: Ein Prozess kann den Speicher eines anderen gar nicht adressieren.
  2. Flexibilität: Zusammenhängende virtuelle Bereiche dürfen auf beliebig verstreute physische Seiten abgebildet werden.
  3. Bedarfsorientierung: Seiten müssen erst existieren, wenn sie benutzt werden, und können vorübergehend ausgelagert sein.

Seitentabellen und Adressübersetzung

Eine einfache Tabelle „virtuelle Seite → physischer Rahmen“ für 128 TiB Adressraum wäre gigantisch: 2³⁵ Einträge à 8 Byte, also 256 GiB pro Prozess. Die Lösung sind mehrstufige Seitentabellen. Sie bilden einen Baum, von dem nur die tatsächlich benutzten Zweige existieren müssen.

Adressübersetzung über vier Ebenen (x86-64) Virtuelle Adresse: 0x00007f3a1c2b5e48 PGD 254 011111110 PUD 232 011101000 PMD 225 011100001 PTE 181 010110101 Offset 0xe48 111001001000 CR3 PGD Ebene 4 [254] PUD Ebene 3 [232] PMD Ebene 2 [225] PTE Ebene 1 [181] Seitenrahmen 4 KiB im RAM + 0xe48 → gesuchtes Byte TLB 0x7f3a1c2b5 → Rahmen · virtuelle Seite → Rahmen gemerkt
  1. Die Adresse wird zerlegt

    Von den 64 Bit der Adresse 0x00007f3a1c2b5e48 sind 48 relevant. Die unteren 12 Bit sind der Offset innerhalb einer 4-KiB-Seite. Die übrigen 36 Bit bilden vier Indizes à 9 Bit – je einer pro Ebene der Seitentabellen. 9 Bit reichen für 512 Einträge, und 512 Einträge à 8 Byte füllen genau eine 4-KiB-Seite.

  2. Ebene 4: CR3 zeigt auf die oberste Tabelle

    Das CPU-Register CR3 enthält die physische Adresse der obersten Tabelle (PGD) des laufenden Prozesses – beim Kontextwechsel wird genau dieses Register umgeschaltet. Bits 47–39 der Adresse (hier 254) wählen den Eintrag, der auf die nächste Tabelle zeigt.

  3. Ebene 3: Page Upper Directory

    Bits 38–30 (hier 232) wählen in dieser Tabelle den Eintrag für die nächste Ebene. Ein Eintrag hier könnte alternativ direkt auf eine 1-GiB-Seite zeigen (Huge Page).

  4. Ebene 2: Page Middle Directory

    Bits 29–21 (hier 225) wählen den nächsten Eintrag. Auf dieser Ebene sind 2-MiB-Seiten möglich – Transparent Huge Pages nutzen das, um Speicher mit einem einzigen Eintrag abzudecken.

  5. Ebene 1: der Seitentabelleneintrag

    Bits 20–12 (hier 181) wählen den eigentlichen Seitentabelleneintrag (PTE). Er enthält die Nummer des physischen Seitenrahmens und Flags: vorhanden (Present), beschreibbar, für den Benutzermodus zugänglich, nicht ausführbar (NX), zuletzt benutzt (Accessed), verändert (Dirty). Fehlt das Present-Bit oder passen die Rechte nicht, löst die MMU einen Seitenfehler aus.

  6. Rahmen plus Offset ergibt die physische Adresse

    Physische Rahmenadresse aus dem PTE plus Offset 0xe48 aus den unteren 12 Bit ergibt die physische Adresse des gesuchten Bytes. Erst jetzt kann der eigentliche Speicherzugriff stattfinden.

  7. Der TLB merkt sich das Ergebnis

    Vier zusätzliche Speicherzugriffe pro Zugriff wären viel zu langsam. Der Translation Lookaside Buffer (TLB) speichert deshalb kürzlich benutzte Übersetzungen. Trifft der nächste Zugriff dieselbe Seite, entfällt die gesamte Tabellenwanderung. Ändert der Kernel Seitentabellen, muss er die betroffenen TLB-Einträge ungültig machen – auf allen CPUs, die sie zwischengespeichert haben könnten.

Schritt für Schritt: Wie die MMU eine virtuelle Adresse über vier Ebenen von Seitentabellen in eine physische Adresse übersetzt.

Linux beschreibt die Ebenen architekturunabhängig als PGD → P4D → PUD → PMD → PTE. Bei 4-stufigen Tabellen wird die P4D-Ebene einfach „durchgereicht“. Mit 5-stufigen Tabellen (57-Bit-Adressen) ist sie echt vorhanden. Auf Architekturen mit weniger Ebenen fallen entsprechend Ebenen weg. Der übrige Kernel-Code muss davon nichts wissen.

Tiefer eintauchenHuge Pages: weniger Ebenen, weniger TLB-Einträge

Ein Eintrag auf PMD-Ebene kann statt auf eine weitere Tabelle direkt auf eine 2-MiB-Seite zeigen, ein PUD-Eintrag sogar auf eine 1-GiB-Seite. Solche Huge PagesHuge PageSpeicherseite, die größer als die Standardgröße ist, auf x86-64 2 MiB oder 1 GiB. Spart Einträge in Seitentabellen und im TLB. sparen eine oder zwei Ebenen der Tabellenwanderung und vor allem TLB-Einträge: Ein einziger Eintrag deckt 512-mal so viel Speicher ab. Das bringt Datenbanken, virtuellen Maschinen und wissenschaftlichen Anwendungen mit großen Datenmengen messbar mehr Leistung.

Linux bietet zwei Wege:

  • hugetlbfs: Huge Pages werden beim Start reserviert und von Anwendungen ausdrücklich angefordert.
  • Transparent Huge Pages (THP): Der Kernel verwendet automatisch 2-MiB-Seiten, wenn ein passender, ausgerichteter Bereich vorhanden ist, und fasst kleine Seiten im Hintergrund zusammen (khugepaged). Die Einstellung steht in /sys/kernel/mm/transparent_hugepage/enabled.

Seit einigen Versionen gibt es zusätzlich Folios: Der Kernel verwaltet zusammenhängende Gruppen von Seiten als eine Einheit (struct folio), etwa 16 oder 64 KiB im Page Cache. Das verringert den Verwaltungsaufwand, auch ohne echte Huge Pages.

Der TLB und seine Kosten

Weil jeder Zugriff ohne Cache bis zu vier zusätzliche Speicherzugriffe erfordern würde, ist der TLBTLB (Translation Lookaside Buffer)Kleiner, sehr schneller Cache in der CPU, der kürzlich benutzte Übersetzungen von virtuellen in physische Seitenadressen speichert. entscheidend für die Leistung. Er hat nur einige hundert bis wenige tausend Einträge, deckt mit 4-KiB-Seiten also nur wenige Megabyte direkt ab. Programme, die zufällig auf große Datenmengen zugreifen, leiden deshalb unter TLB-Misses.

Ändert der Kernel eine Seitentabelle, muss er zwischengespeicherte Übersetzungen ungültig machen. Auf Mehrkernsystemen bedeutet das oft einen TLB-Shootdown: Die CPU schickt allen anderen CPUs, die den Adressraum gerade benutzen, einen Interrupt, damit auch sie ihre Einträge verwerfen. Das ist einer der Gründe, warum Operationen wie munmap() auf großen Systemen teuer werden können.

Der Adressraum eines Prozesses

Der virtuelle Adressraum eines Prozesses wird durch struct mm_struct beschrieben. Er besteht aus einer Reihe von VMAsVMA (Virtual Memory Area)Zusammenhängender Bereich im Adressraum eines Prozesses mit einheitlichen Rechten und gleicher Herkunft, etwa der Code eines Programms, der Heap oder eine eingeblendete Datei (struct vm_area_struct). (virtual memory areas, struct vm_area_struct). Jede VMA ist ein zusammenhängender Bereich mit einheitlichen Eigenschaften:

EigenschaftBeispiele
LageStart- und Endadresse, seitenweise ausgerichtet
Rechtelesen, schreiben, ausführen (r, w, x)
Herkunftdateibasiert (Programmcode, Bibliotheken, mmap einer Datei) oder anonym (Heap, Stack, malloc)
Teilungprivat (Änderungen per Copy-on-Write) oder gemeinsam (MAP_SHARED)

Genau diese Liste zeigt /proc/<pid>/maps. Seit Linux 6.1 verwaltet der Kernel die VMAs in einem Maple Tree, einem für diesen Zweck entwickelten B-Baum, der schnelles Suchen und Iterieren erlaubt und sich gut für nebenläufige Zugriffe eignet.

Wichtig: Eine VMA ist nur eine Zusage. mmap() oder malloc() legen zunächst nur eine VMA an. Physischer Speicher wird dabei nicht belegt. Den bekommt der Prozess erst beim ersten Zugriff, über einen Seitenfehler.

Seitenfehler

Ein SeitenfehlerSeitenfehler (Page Fault)Ausnahme, die die MMU auslöst, wenn für eine Adresse kein gültiger Seitentabelleneintrag existiert oder die Rechte nicht passen. Meist kein Fehler, sondern Anlass für den Kernel, Speicher bereitzustellen. tritt auf, wenn die MMU für eine Adresse keinen gültigen Eintrag findet oder die Rechte nicht ausreichen. Die CPU unterbricht den Befehl, legt die betroffene Adresse in das Register CR2 und springt in den Kernel:

Was bei einem Seitenfehler passiert Zugriff auf eine virtuelle Adresse MMU: kein gültiger Eintrag oder Rechte fehlen exc_page_fault() → do_user_addr_fault() Adresse steht im Register CR2 Passende VMA mit passenden Rechten? nein SIGSEGV Segmentation fault ja handle_mm_fault() Seitentabellen durchlaufen, Ursache bestimmen do_anonymous_page() erster Zugriff auf anonymen Speicher minor do_fault() Dateiinhalt aus dem Page Cache / Datenträger minor / major do_swap_page() Seite aus dem Swap zurückholen major do_wp_page() Schreiben auf eine Copy-on-Write-Seite minor Seitentabelle aktualisieren Befehl wird erneut ausgeführt – diesmal erfolgreich
Vereinfachter Ablauf. Ob ein Seitenfehler ein Fehler ist, entscheidet sich erst im Kernel: Meist ist er der normale Weg, Speicher erst bei Bedarf bereitzustellen.

Man unterscheidet zwei Arten:

  • Minor Faults lassen sich ohne Zugriff auf einen Datenträger auflösen. Beispiele sind eine neue, mit Nullen gefüllte Seite, eine Copy-on-Write-Kopie oder eine Datei-Seite, die schon im Page Cache liegt. Sie kosten Mikrosekunden.
  • Major Faults müssen auf einen Datenträger warten, weil die Seite erst aus einer Datei oder dem Swap gelesen werden muss. Sie kosten je nach Datenträger Mikrosekunden (NVMe) bis Millisekunden (Festplatte), und der Task schläft so lange.

Die zentrale Funktion ist handle_mm_fault in mm/memory.c. Sie wird nicht nur von der CPU-Ausnahme aufgerufen, sondern auch, wenn der Kernel selbst Seiten eines Prozesses braucht, etwa bei copy_from_user() (Kapitel 4).

Tiefer eintauchenWie der Kernel die VMA sperrt

Bei der Behandlung eines Seitenfehlers muss die VMA stabil bleiben: Ein anderer Thread desselben Prozesses könnte sie gleichzeitig mit munmap() entfernen. Früher schützte eine einzige Sperre pro Adressraum (mmap_lock) alle VMAs. Bei Programmen mit vielen Threads war das ein Engpass.

Seit Linux 6.4 versucht der Kernel zuerst, nur die betroffene VMA zu sperren (lock_vma_under_rcu()). Die Suche geschieht dabei RCU-geschützt im Maple Tree (Kapitel 9). Nur wenn das nicht klappt, fällt er auf die globale Sperre zurück. Seitenfehler in verschiedenen Bereichen desselben Prozesses können so parallel bearbeitet werden.

Physischen Speicher verteilen

Der Kernel hat viele „Kunden“ für Speicher: Prozesse, den Dateicache, Netzwerkpuffer, Treiber und seine eigenen Datenstrukturen. Dafür gibt es mehrere Schichten von Allokatoren:

Wer verteilt den Speicher? Die Allokatoren im Überblick User Space Kernel Kernel-Code → brk() · mmap() malloc() / free() C-Bibliothek verwaltet den Heap Programme, Bibliotheken, Dateien werden eingeblendet Adressräume der Prozesse VMAs, Seitentabellen, Seitenfehler kmalloc() SLUB: kleine Objekte vmalloc() virtuell zusammenhängend Page Cache Dateiinhalte im RAM Buddy-Allocator: alloc_pages() Blöcke aus 2ⁿ zusammenhängenden Seiten Zonen je NUMA-Knoten DMA · DMA32 · Normal · Movable
Alle Wege führen zum Buddy-Allocator, der physische Seiten verwaltet. Darüber liegen spezialisierte Schichten für kleine Kernel-Objekte (SLUB), virtuell zusammenhängende Bereiche (vmalloc), den Dateicache und den Speicher von Prozessen.

Knoten und Zonen

Physischer Speicher ist zunächst nach NUMA-Knoten gegliedert. Auf Systemen mit mehreren Prozessorsockeln ist jedem Sockel „sein“ Speicher zugeordnet, auf den er schneller zugreift als auf den der anderen. Der Kernel versucht, Speicher möglichst auf dem Knoten der anfragenden CPU zu vergeben.

Jeder Knoten ist weiter in Zonen unterteilt, weil nicht jeder Speicher für jeden Zweck taugt:

ZoneZweck
ZONE_DMAdie ersten 16 MiB, für sehr alte Geräte mit eingeschränkter Adressierung
ZONE_DMA32die ersten 4 GiB, für Geräte, die nur 32-Bit-Adressen beherrschen
ZONE_NORMALder gesamte übrige Speicher
ZONE_MOVABLEnur verschiebbare Seiten, wichtig für das Entfernen von Speicher im Betrieb und für Huge Pages

Der Buddy-Allocator

Die unterste Schicht verwaltet physische Seiten in Blöcken von 2ⁿ zusammenhängenden Seiten. Wie sie das schnell und ohne starke Zersplitterung schafft, zeigt der folgende Ablauf:

Der Buddy-Allocator: Teilen und Verschmelzen Seiten 0 – 15 belegt 0123456789101112131415 16 Ordnung 4 Freilisten Ordnung 0: – Ordnung 1: – Ordnung 2: – Ordnung 3: – Ordnung 4: [0] 8 Ordnung 3 8 Ordnung 3 Freilisten Ordnung 0: – Ordnung 1: – Ordnung 2: – Ordnung 3: [0] [8] Ordnung 4: – 4 Ordnung 2 4 Ordnung 2 8 Ordnung 3 Freilisten Ordnung 0: – Ordnung 1: – Ordnung 2: [0] [4] Ordnung 3: [8] Ordnung 4: – 2 Ordnung 1 2 Ordnung 1 4 Ordnung 2 8 Ordnung 3 Freilisten Ordnung 0: – Ordnung 1: [0] [2] Ordnung 2: [4] Ordnung 3: [8] Ordnung 4: – 1 1 2 Ordnung 1 4 Ordnung 2 8 Ordnung 3 Freilisten Ordnung 0: [1] Ordnung 1: [2] Ordnung 2: [4] Ordnung 3: [8] Ordnung 4: – 16 Ordnung 4 Freilisten Ordnung 0: – Ordnung 1: – Ordnung 2: – Ordnung 3: – Ordnung 4: [0]
  1. Ausgangslage

    Der Allocator verwaltet freien Speicher in Blöcken von 2ⁿ Seiten – n heißt Ordnung und reicht von 0 (eine Seite, 4 KiB) bis 10 (1024 Seiten, 4 MiB). Für jede Ordnung gibt es eine Freiliste. Hier ist ein einziger freier Block der Ordnung 4 (16 Seiten) vorhanden.

  2. Anforderung: eine Seite – Block halbieren

    Jemand fordert eine einzelne Seite an (Ordnung 0). In den Freilisten der Ordnungen 0 bis 3 ist nichts, also wird der Block der Ordnung 4 halbiert. Die beiden Hälften heißen Buddies; die rechte kommt in die Freiliste der Ordnung 3.

  3. Weiter halbieren

    Die linke Hälfte (8 Seiten) ist immer noch zu groß und wird erneut geteilt. Ein Block der Ordnung 2 landet in der Freiliste.

  4. Und noch einmal

    Aus 4 Seiten werden zwei Blöcke à 2 Seiten. Einer davon wandert in die Freiliste der Ordnung 1.

  5. Zuteilung

    Der letzte Block wird in zwei Einzelseiten geteilt: Eine wird vergeben, ihr Buddy kommt in die Freiliste der Ordnung 0. Die Zuteilung hat nur wenige Schritte gekostet – höchstens so viele, wie es Ordnungen gibt.

  6. Freigabe: Buddies verschmelzen

    Wird die Seite freigegeben, prüft der Allocator, ob ihr Buddy ebenfalls frei ist. Wenn ja, verschmelzen beide zu einem Block der nächsthöheren Ordnung – und das wiederholt sich, solange der jeweilige Buddy frei ist. Hier entsteht wieder der ursprüngliche Block mit 16 Seiten. Die Adresse eines Buddies lässt sich übrigens mit einem einzigen XOR berechnen.

Ein Ausschnitt aus 16 Seiten. Freie Blöcke werden so lange halbiert, bis ein Block der gewünschten Größe entsteht; beim Freigeben verschmelzen freie Buddies wieder zu größeren Blöcken.

Im Kernel-Code fordert man Seiten mit alloc_pages(gfp, order) an. Der erste Parameter, die GFP-Flags (get free pages), beschreibt, unter welchen Bedingungen die Anforderung erfüllt werden darf:

FlagBedeutung
GFP_KERNELNormalfall im Prozesskontext. Darf schlafen, Speicher zurückgewinnen und I/O auslösen.
GFP_ATOMICim Interruptkontext oder unter einem Spinlock. Darf nicht schlafen, greift notfalls auf Reserven zu.
GFP_USER / GFP_HIGHUSERSeiten für den User Space
GFP_NOIO, GFP_NOFSdarf keine I/O bzw. keine Dateisystem-Operation auslösen (verhindert Verklemmungen)

SLUB: kleine Objekte

Die meisten Datenstrukturen des Kernels sind viel kleiner als eine Seite: eine task_struct, ein Inode, ein Netzwerkpuffer-Kopf. Für sie gibt es den Slab-Allocator, seit Linux 6.8 ausschließlich in der Variante SLUB:

  • Für jeden häufig benutzten Objekttyp gibt es einen Cache (kmem_cache_create()), der Seiten vom Buddy-Allocator holt und in gleich große Objekte aufteilt.
  • Freigegebene Objekte kommen in den Cache zurück und stehen sofort wieder zur Verfügung, ohne dass Seiten zurückgegeben werden müssen.
  • Jede CPU hat eigene Listen, sodass die meisten Anforderungen ganz ohne Sperren auskommen.

Für Objekte ohne eigenen Cache gibt es kmalloc(size, gfp), das auf Caches für feste Größenklassen (8, 16, 32, … Byte) zurückgreift. kmalloc() liefert physisch zusammenhängenden Speicher.

vmalloc()

Braucht der Kernel einen großen Puffer, der nur virtuell zusammenhängend sein muss, nimmt er vmalloc(). Es holt einzelne Seiten vom Buddy-Allocator, die physisch verstreut sein dürfen, und blendet sie in einen zusammenhängenden Bereich des Kernel-Adressraums ein. Das ist langsamer als kmalloc(), scheitert aber nicht an Zersplitterung. Kernel-Module werden zum Beispiel in einen solchen Bereich geladen. kvmalloc() versucht zuerst kmalloc() und weicht bei Bedarf auf vmalloc() aus.

Der Page Cache

Linux verwendet freien Arbeitsspeicher, statt ihn ungenutzt zu lassen: als Page CachePage CacheTeil des Arbeitsspeichers, in dem der Kernel Dateiinhalte zwischenspeichert, um Zugriffe auf Datenträger zu vermeiden. für Dateiinhalte. Jedes read() und jeder Seitenfehler auf eine eingeblendete Datei geht zuerst dorthin:

  • Lesen: Liegt die Seite schon im Cache, sind keine Datenträgerzugriffe nötig. Liest ein Programm eine Datei der Reihe nach, lädt der Kernel vorausschauend weitere Seiten (Readahead).
  • Schreiben: write() ändert zunächst nur die Seite im Cache und markiert sie als dirty. Kernel-Threads schreiben solche Seiten später gesammelt zurück (Writeback), spätestens nach etwa 30 Sekunden oder wenn zu viele Seiten dirty sind. Wer sicher sein will, dass Daten auf dem Datenträger angekommen sind, ruft fsync() auf.

Weil Programme und Dateien über denselben Cache laufen, teilen sich auch mehrere Prozesse, die dieselbe Bibliothek benutzen, deren Seiten im RAM. Die Bibliothek liegt nur einmal im Speicher, egal wie viele Programme sie einblenden.

Wenn der Speicher knapp wird

Der Kernel versucht, immer einen gewissen Vorrat an freien Seiten zu halten. Wird er knapp, beginnt die Speicherrückgewinnung (reclaim):

  1. Saubere Datei-Seiten aus dem Page Cache werden einfach verworfen, denn sie lassen sich jederzeit neu von der Datei lesen.
  2. Dirty Datei-Seiten werden erst zurückgeschrieben, dann verworfen.
  3. Anonyme Seiten (Heap, Stack) haben keine Datei hinter sich. Sie können nur in den SwapSwapBereich auf einem Datenträger (oder komprimiert im RAM), in den der Kernel selten benutzte anonyme Speicherseiten auslagert, um Arbeitsspeicher freizumachen. ausgelagert werden, also auf eine Swap-Partition, in eine Swap-Datei oder komprimiert in den RAM (zram, zswap).

Welche Seiten gehen, entscheidet der Kernel anhand von LRU-Listen (least recently used): Seiten, auf die lange nicht zugegriffen wurde, sind zuerst dran. Seit Linux 6.1 gibt es dafür zusätzlich den genaueren Multi-Gen LRU, der Seiten nach mehreren „Generationen“ ihres letzten Zugriffs einteilt. Die Arbeit erledigt im Hintergrund der Kernel-Thread kswapd. Reicht das nicht, muss der anfordernde Task selbst Speicher zurückgewinnen (direct reclaim) und wird dabei spürbar langsamer.

Overcommit und der OOM-Killer

Linux sagt standardmäßig mehr Speicher zu, als physisch vorhanden ist (Overcommit), weil viele Programme reservierten Speicher nie vollständig nutzen. Das Verhalten steuert /proc/sys/vm/overcommit_memory:

WertVerhalten
0Heuristik (Standard): offensichtlich unerfüllbare Anforderungen werden abgelehnt
1immer zusagen
2strikt: nur so viel zusagen, wie Swap plus ein einstellbarer Anteil des RAM hergeben

Geht die Rechnung nicht auf und lässt sich auch durch Rückgewinnung nichts mehr freimachen, greift der OOM-KillerOOM-KillerNotmechanismus des Kernels: Lässt sich trotz aller Maßnahmen kein Speicher mehr freimachen, beendet er gezielt einen Prozess, meist den mit dem größten Speicherverbrauch. (out of memory) ein. Er bewertet alle Prozesse, vor allem nach ihrem Speicherverbrauch (oom_badness), und beendet den mit der höchsten Punktzahl mit SIGKILL. Über /proc/<pid>/oom_score_adj (−1000 bis 1000) lässt sich das beeinflussen. −1000 schützt einen Prozess vollständig.

In Systemen mit cgroups gibt es den OOM-Killer auch pro Gruppe: Überschreitet ein Container sein Speicherlimit, wird ein Prozess innerhalb dieses Containers beendet, nicht irgendwo im System. Viele Distributionen setzen außerdem systemd-oomd ein, das schon früher eingreift, wenn das System unter anhaltendem Speicherdruck steht.

free richtig lesen

Ein häufiges Missverständnis: „Linux frisst meinen Speicher, free zeigt kaum etwas frei!“ Ein Blick auf die Spalten klärt das:

               total        used        free      shared  buff/cache   available
Mem:            31Gi       9,2Gi       1,1Gi       1,0Gi        21Gi        21Gi
Swap:          8,0Gi       512Mi       7,5Gi
  • free: wirklich ungenutzter Speicher. Er ist auf einem gesunden System klein, weil ungenutzter Speicher verschwendeter Speicher ist.
  • buff/cache: Page Cache und Kernel-Caches. Der größte Teil davon lässt sich bei Bedarf sofort freigeben.
  • available: die Schätzung des Kernels, wie viel Speicher für neue Programme verfügbar ist, ohne auszulagern. Das ist die aussagekräftige Zahl.

Selbst ausprobieren

Selbst ausprobieren: Speicher untersuchen

# Überblick und Details
free -h
head -n 25 /proc/meminfo

# Seitengröße
getconf PAGESIZE

# Adressraum der Shell: VMAs mit Rechten und Herkunft
cat /proc/$$/maps

# Wie viel davon liegt tatsächlich im RAM? (RSS je VMA, kompakt)
pmap -x $$ | tail -n 5

# Freie Blöcke je Ordnung und Zone (Spalten: Ordnung 0 bis 10)
cat /proc/buddyinfo

# Die größten Slab-Caches des Kernels
sudo slabtop -o | head -n 15

# Seitenfehler eines Programms zählen (minor und major; benötigt das Paket „time“)
/usr/bin/time -v ls -R /usr/share > /dev/null 2> /tmp/zeit.txt; grep -i "page faults" /tmp/zeit.txt

# Speicherdruck und Auslagerung live (Spalten si/so = Swap rein/raus)
vmstat 1 5

# OOM-Bewertung der eigenen Shell
cat /proc/$$/oom_score /proc/$$/oom_score_adj

Selbst ausprobieren: Den Page Cache spüren

# Eine 1-GiB-Testdatei anlegen
dd if=/dev/urandom of=/tmp/testdatei bs=1M count=1024

# Page Cache leeren (nur zum Testen – verlangsamt das System kurzzeitig)
sync; echo 3 | sudo tee /proc/sys/vm/drop_caches

# Erster Lesevorgang: vom Datenträger
time cat /tmp/testdatei > /dev/null

# Zweiter Lesevorgang: aus dem Page Cache – deutlich schneller
time cat /tmp/testdatei > /dev/null

rm /tmp/testdatei

Zwischen den beiden Läufen kannst du mit free -h beobachten, wie buff/cache um etwa 1 GiB wächst.