Die Tiefen des Windows-Speichers (Teil 1) — Der Moment, in dem eine virtuelle Adresse zu physischem RAM wird: Ein Seitenfehler von Anfang bis Ende

· · Windows, Speicherverwaltung, VirtualAlloc, Seitenfehler, VAD, Leistungsüberwachung

MEM_COMMIT an VirtualAlloc zu übergeben erhöht in diesem Moment den Commit. Das Working Set steigt jedoch nicht unbedingt um denselben Betrag. Wo ist also der Speicher, den Sie vermeintlich zugewiesen haben?

Die Antwort lautet: die meisten Seiten haben noch kein zugehöriges physisches RAM. Windows verzögert die Zuweisung einer physischen Seite, bis die Anwendung die Seite tatsächlich berührt. Wenn der erste Zugriff die CPU einen Seitenfehler auslösen lässt, prüft der Speicher-Manager VAD, PTE, Schutzattribute und den Backing Store und bindet bei Bedarf RAM Seite für Seite.1

Dieser Artikel folgt dem Weg, den „das erste Byte, das Sie berühren“ nimmt, bis es physisches RAM erreicht. Wenn Sie zuerst die Bedeutung von Zahlen wie Working Set und Commit klären wollen, siehe den Einführungsartikel „Was bedeutet Windows’ „Speicherauslastung“ eigentlich? — Working Set, Private Bytes, Commit und die Auslagerungsdatei richtig lesen“. Diese Reihe definiert die dort verwendeten Begriffe nicht neu; sie gräbt von der Mechanikseite aus, „warum die Zahl so herauskommt“.

„Die Tiefen des Windows-Speichers“ — alle 3 Teile

  1. Teil 1 (dieser Artikel): Virtuelle Adressen und Seitenfehler
    Wir verfolgen, wann ein mit VirtualAlloc zugewiesener Bereich physisches RAM erhält.
  2. Teil 2: Das Leben einer physischen Seite
    Wir verfolgen, wie eine Seite, die das Working Set verlässt, durch Modified, Standby, Free und Zeroed wandert.
  3. Teil 3: Section-Objekte und Copy-on-Write
    Wir verfolgen, warum DLLs, Dateizuordnungen und gemeinsamer Speicher physische Seiten teilen können.

Die Frage, die Teil 1 beantwortet, ist nur eine.

In welchem Moment wird eine committete virtuelle Adresse zu physischem RAM?

Die Zielgruppe sind Entwickler und Betrieb, die Speicherverbrauch von Windows-Apps, Seitenfehler direkt nach dem Start, 0xC0000005 und die Zahlen in VMMap und PerfMon vom Mechanismus her verstehen wollen. Voraussetzungen sind Windows 10/11 oder aktueller Windows Server, und erforderliches Vorwissen sind Zeiger und die Grundlagen von VirtualAlloc; Erfahrung mit Bit-Layouts von Seitentabellen oder einem Kernel-Debugger brauchen Sie nicht. Der Schwierigkeitsgrad ist mittel. Wir verwenden die Namen interner Strukturen, setzen aber keine undokumentierten Layouts voraus, die von einem bestimmten Windows-Build abhängen.

1. Zuerst das Fazit

Der gewöhnliche Ablauf privaten Speichers ist in einer Zeile dieser.

Reserve legt einen virtuellen Adressbereich beiseite, Commit verrechnet eine Commit-Charge, um einen künftigen Ort zur Aufbewahrung des Inhalts zu garantieren, und der Seitenfehler beim ersten Zugriff weist die physische Seite zu.

Mit anderen Worten: MEM_COMMIT ist kein Befehl, der sagt „weise jetzt sofort RAM zu“. Auch Microsofts VirtualAlloc-Dokumentation garantiert, dass der Anfangsinhalt einer committeten Seite null ist, und erklärt zugleich, dass die tatsächliche physische Seite erst zugewiesen wird, wenn auf die virtuelle Adresse zugegriffen wird.1

Es ist allerdings auch ungenau zu behaupten, „Reserve/Commit schreiben nur in den VAD“. In der Praxis erzeugt Reserve vor allem einen VAD, der den virtuellen Adressbereich und seine Attribute darstellt, und Commit erhöht das Commit Total des Systems und zeichnet den committeten Zustand des Bereichs auf. Zwischenstufen der Seitentabelle und einzelne PTEs werden bei Bedarf verzögert gebaut, und die endgültige Bindung an physisches RAM geschieht normalerweise beim ersten Zugriff.

Commit ist kein leeres Versprechen; es ist ein systemweites Versprechen, dass der Inhalt künftig in RAM oder einem geeigneten Backing Store gehalten werden kann. Der Einstiegspunkt, der dieses Versprechen Seite für Seite materialisiert, ist der Seitenfehler.

Was bei Reserve, Commit und erstem Zugriff geschiehtMEM_RESERVE trägt Bereich und Attribute in den VAD ein, MEM_COMMIT verbraucht Commit Total um Speicher zu versprechen, und der Seitenfehler beim ersten Zugriff weist eine physische Seite zu und nimmt sie ins Working Set auf1. MEM_RESERVE2. MEM_COMMIT3. Erster Zugriff(Touch)Bereich und Attribute im VAD eintragenCommit Total verbrauchen(noch keine physische Seite)SeitenfehlerEine genullte physische Seite an die PTE bindenIns Working Set aufnehmen und die Anweisung erneut ausführen

Abbildung 1: Reserve, Commit und Touch sind getrennte Ereignisse. Physisches RAM wird nur im letzten Schritt, dem ersten Zugriff, gebunden.

2. Drei Register, die eine virtuelle Seite verfolgen

Um den Weg von einer virtuellen Adresse zu physischem RAM zu verstehen, müssen Sie die drei Arten von Registern unterscheiden, die Windows führt.

Register Einheit Rolle
VAD Virtueller Adressbereich Verwaltet, was die Region ist, Reserve/Commit, Schutz und Section-Zuordnung
Seitentabelle / PTE Virtuelle Seite Stellt die aktuelle Übersetzung zu einer physischen Seite dar oder einen noch nicht realisierten Zustand
PFN-Datenbank Physische Seite Verfolgt Besitz, Referenzen und Zustand jeder RAM-Seite

Der VAD hält Informationen über einen Bereich, die PTE über eine virtuelle Seite, und die PFN-Datenbank über eine physische Seite. Der Seitenfehler-Handler gleicht diese ab, um zu entscheiden, ob der Zugriff fortgesetzt werden kann.

Drei Register von der virtuellen Adresse zum physischen RAMEine virtuelle Adresse wird vom VAD in Bereichsgranularität und von der PTE in Granularität der virtuellen Seite verwaltet, und die PFN-Datenbank verfolgt die physische Seite als Übersetzungsziel der PTE in Granularität der physischen SeiteReserve/Commit und Schutz beurteilenGültige ÜbersetzungVirtuelle AdresseVAD(Bereichsregister)PTE(Register der virtuellen Seite)PFN-Datenbank(Register der physischen Seite)Physische RAM-Seite

Abbildung 2: Drei Register unterschiedlicher Granularität. Die Fehlerbehandlung gleicht VAD und PTE ab und spiegelt das Ergebnis auf der PFN-Seite wider.

Die Hauptfiguren dieses Artikels sind VAD und PTE. Die PFN-Datenbank betrachten wir in Teil 2 von der Seite der physischen Seite.

3. Reserve, Commit und Touch sind getrennte Ereignisse

3.1. Reserve — eine Adresse beanspruchen

Reservieren Sie zuerst einen zusammenhängenden virtuellen Adressbereich von 256 MiB.

void* base = VirtualAlloc(
    nullptr,
    256ull * 1024 * 1024,
    MEM_RESERVE,
    PAGE_NOACCESS);

Was an diesem Punkt geschehen ist, ist nur, dass eine Adresse im virtuellen Raum des Prozesses beiseitegelegt wurde, damit andere Zuweisungen diesen Bereich nicht nutzen können. MEM_RESERVE weist keinen physischen Speicher in RAM oder in der Auslagerungsdatei zu.1

Weil ein 64-Bit-Prozess einen riesigen virtuellen Raum hat, wird es praktisch, zuerst einen großen Bereich zu Reserve und später nur die benötigten Teile zu Commit.

3.2. Commit — versprechen, dass es gehalten werden kann

Als Nächstes Committen Sie den reservierten Bereich.

void* committed = VirtualAlloc(
    base,
    256ull * 1024 * 1024,
    MEM_COMMIT,
    PAGE_READWRITE);

Bei Erfolg steigt der versprochene Betrag, der sich im Commit Total des Systems — und gewöhnlich in den Private Bytes des Prozesses — widerspiegelt. Trotzdem stellen sich nicht auf einmal 256 MiB physische Seiten auf. Gewöhnliche Seiten bleiben bis zum ersten Zugriff physisch unzugewiesen.12

Wozu also Commit? Darin, dass das System, wenn es das Versprechen nicht übernehmen kann, bereits zum Commit-Zeitpunkt fehlschlagen kann, statt mitten in der Nutzung des Speichers.

3.3. Touch — wann eine physische Seite nötig wird

Schließlich schreibt die folgende Zuweisung zum ersten Mal in die erste Seite.

static_cast<unsigned char*>(base)[0] = 1;

Die CPU versucht, die virtuelle Adresse in eine physische Adresse zu übersetzen, aber die PTE hat noch keine gültige Übersetzung zu einer physischen Seite. Hier tritt ein Seitenfehler auf.

Der Speicher-Manager, der die Kontrolle erhält, beurteilt dies als „ersten Zugriff auf eine committete, beschreibbare private Seite“, holt eine genullte physische Seite, bindet sie an die PTE und nimmt sie ins Working Set auf. Dann führt er die fehlgeschlagene Schreibanweisung erneut aus.

Aus Sicht der App sieht es nach einer bloßen Zuweisung aus, intern aber tritt die Kontrolle mitten in der Zuweisung in den Kernel ein, eine physische Seite wird zugewiesen, und die Ausführung kehrt zu derselben Anweisung zurück.

4. Der VAD — das Bereichsregister des virtuellen Raums

VAD steht für Virtual Address Descriptor, und Windows verwaltet die genutzten Adressbereiche eines Prozesses als Baum von VADs. Mit dem WinDbg-Befehl !vad können Sie Start- und End-VPN, Commit, Schutzattribute, Private/Mapped, die Control Area und mehr prüfen.3

Typische Informationen, die ein VAD aufzeichnet, umfassen Folgendes.

  • Anfang und Ende des Adressbereichs
  • Art, etwa Private, Mapped oder Image
  • Reserve-/Commit-Zustand
  • Schutz wie Lesen, Schreiben, Ausführen und Copy-on-Write
  • Zuordnung zu einer Datei oder Section
  • Sonderattribute wie Guard-Seiten

Der Grund für die Verwaltung nach Bereich ist Effizienz. 256 MiB sind bei 4 KiB 65.536 Seiten. Statt für jede Seite von vornherein eine vollständige Verwaltungsstruktur zu bauen, ist es weniger verschwenderisch, „dieser zusammenhängende Bereich ist eine Reservierung“ in einem VAD zu halten und Seiten zu materialisieren, sobald sie nötig werden.

4.1. Einen VAD zu finden garantiert keine Wiederherstellung

„Wenn es im VAD liegt, wird der Fehler aufgelöst; wenn nicht, erhalten Sie eine Zugriffsverletzung“ ist eine bequeme Einstiegserklärung, vereinfacht aber zu stark. Auch wenn ein VAD gefunden wird, kann der gewöhnliche Zugriff in Fällen wie den folgenden nicht fortgesetzt werden.

  • Nur Reserve, und die Zielseite ist nicht committet
  • PAGE_NOACCESS
  • Ein Schreibzugriff auf eine schreibgeschützte Seite
  • Befehlsausführung von einer nicht ausführbaren Seite
  • Erste Berührung einer Guard-Seite
  • Eine Berührung außerhalb des gültigen Bereichs einer Section

Umgekehrt kann es, selbst wenn die PTE ungültig ist, als Demand-Zero, Transition-Wiederherstellung, Page-In oder CoW aufgelöst werden, wenn der Softwarezustand von VAD und PTE einen legitimen Zugriff zeigt. Genauer lautet die Antwort: VAD, PTE, Schutzattribute und Zugriffstyp gemeinsam beurteilen.

5. Seitentabellen und der TLB

Der Zeiger, den eine App hält, ist eine virtuelle Adresse. Damit die CPU auf RAM zugreifen kann, muss sie eine virtuelle Seitennummer in eine physische Seitennummer übersetzen. Diese hierarchische Übersetzungstabelle ist die Seitentabelle, und der Blatteintrag ist die PTE (Page Table Entry).

Eine gültige PTE hält begrifflich eine PFN, Lese-/Schreib-/Ausführungsschutz, Benutzerrechte, Accessed/Dirty und Ähnliches. Das tatsächliche Bit-Layout hängt von CPU und Windows-Version ab.

Die Seitentabelle jedes Mal abzulaufen wäre viel zu langsam, daher speichert die CPU jüngste Übersetzungen im TLB (Translation Lookaside Buffer). Die Adressübersetzung läuft in dieser Reihenfolge.

  1. Hat der TLB eine Übersetzung und passt der Zugriff zu diesem Schutz, wird dieses Ergebnis verwendet.
  2. Hat der TLB keine Übersetzung, läuft die CPU die Seitentabelle ab.
  3. Gibt es eine gültige PTE und passt der Schutz ebenfalls, wird sie im TLB eingetragen und die Ausführung fortgesetzt.
  4. Gibt es keine gültige Übersetzung oder liegt ein Schutzverstoß vor, geht die Kontrolle zum Einstiegspunkt des Seitenfehlers. Die Schutzprüfung erfolgt auch, wenn die Übersetzung aus dem TLB kam.

Wie dieser Ablauf zeigt, sind ein TLB-Miss und ein Seitenfehler verschiedene Dinge. Ist das einzige Problem, dass der TLB keine Übersetzung hat und die PTE gültig ist, geschieht nur ein Seitentabellenlauf. Umgekehrt geht selbst bei vorhandener TLB-Übersetzung ein Schutzverstoß wie ein Schreibzugriff auf eine schreibgeschützte Seite oder Befehlsausführung auf einer nicht ausführbaren Seite zum Einstiegspunkt des Seitenfehlers. Deshalb kann ein Schreibzugriff auf eine CoW-Seite fehlschlagen, selbst wenn die Übersetzung bereits zwischengespeichert ist.

Ablauf der Adressübersetzung und Einstiegspunkt des SeitenfehlersAuch wenn der TLB eine Übersetzung hat, führt ein Schutzmismatch zum Einstiegspunkt des Seitenfehlers. Hat der TLB keine Übersetzung, wird die Seitentabelle abgelaufen; eine gültige PTE die auch zum Schutz passt wird im TLB eingetragen und die Ausführung fortgesetzt, und eine ungültige Übersetzung oder ein Schutzverstoß führen zum Einstiegspunkt des SeitenfehlersJaPasstSchutzverstoßNeinJaUngültig oder SchutzverstoßSpeicherzugriffHat der TLB eine Übersetzung?Passt der Zugriff zum Schutz?Mit dieser Übersetzung fortfahrenZum Einstiegspunkt des SeitenfehlersSeitentabellenlaufGültige PTE und Schutz passt ebenfalls?Im TLB eintragen und fortfahren(kein Fehler)

Abbildung 3: Ein TLB-Miss kann durch einen Seitentabellenlauf aufgelöst werden. Die Kontrolle geht zum Seitenfehler, wenn die Übersetzung ungültig ist oder ein Schutzverstoß vorliegt; ein Schutzverstoß tritt auch bei einem TLB-Treffer auf.

5.1. Eine ungültige PTE ist nicht bloß ein leeres Feld

Auch eine ungültige PTE ist nicht leer. Aus dem Softwarezustand einer ungültigen PTE unterscheidet Windows Fälle wie die folgenden.

  • Eine Demand-Zero-Seite, die nie materialisiert wurde
  • Eine Transition-Seite, die im RAM bleibt
  • Eine gemeinsam genutzte Seite, die auf eine Prototype-PTE verweist
  • Eine private Seite, die in der Auslagerungsdatei gespeichert ist
  • Ein Schutzverstoß oder ein ungültiger Bereich

Die Aufgabe der CPU ist nur zu entscheiden „das ist keine gewöhnliche gültige Übersetzung“ und sie an den Kernel zu übergeben; der Speicher-Manager liefert von dort die Bedeutung.

6. Ein Seitenfehler von Anfang bis Ende

Verfolgen wir einen ersten Schreibzugriff auf eine committete private Seite in sechs Stufen.

  1. Die CPU versucht zu schreiben.
    Sie prüft TLB und Seitentabelle, aber die Ziel-PTE hat keine gültige PFN.
  2. Die CPU löst einen Seitenfehler aus.
    Sie übergibt die fehlerhafte virtuelle Adresse, den Lese-/Schreib-/Ausführungstyp, Benutzer/Kernel und ob das Problem eine fehlende Übersetzung oder ein Schutzverstoß ist an den Kernel.
  3. Der Speicher-Manager prüft VAD und PTE.
    Er entscheidet, ob die Seite committet ist, ob der Schutz passt und welches von Demand-Zero, Transition, gemeinsam genutzt, Page-In, CoW oder einer Ausnahme zutrifft.
  4. Bei Demand-Zero wird eine genullte physische Seite geholt.
    Eine neu ausgegebene Seite muss null sein, damit keine Daten eines anderen Prozesses durchsickern.
  5. Die Verwaltungsinformationen von PTE und PFN werden aktualisiert.
    PFN und Schutz werden in der PTE gesetzt, die physische Seite wird Active gemacht und ins Working Set des Prozesses aufgenommen.
  6. Die fehlgeschlagene Anweisung wird erneut ausgeführt.
    Weil der Fehler normal aufgelöst wurde, wird keine Benutzermodus-Ausnahme zugestellt, und die App setzt die Zuweisung wie gewohnt fort.

ETW-Seitenfehlerereignisse zeichnen Transition, Demand Zero, Copy-on-Write, Guard Page, Hard Page Fault und Access Violation ebenfalls als eigene Arten auf.4

Ein Seitenfehler ist also von Anfang an kein Wort, das „anormal“ bedeutet. Er ist der gemeinsame Einstiegspunkt, um das Betriebssystem entscheiden zu lassen, wenn die CPU auf dem gewöhnlichen Weg nicht übersetzen konnte.

Verzweigung der SeitenfehlerauflösungenDer Speicher-Manager beurteilt VAD, PTE, Schutzattribute und Zugriffstyp und verteilt auf Demand-Zero, Wiederanbinden einer noch im RAM liegenden Seite, einen harten Fehler aus einem Backing Store, Copy-on-Write, eine Guard-Seiten-Benachrichtigung oder eine AusnahmeErster ZugriffNoch im RAMDatenträgerlesen nötigCoW-SchreibzugriffGuard-SeiteNicht auflösbarSeitenfehler tritt aufVAD, PTE, Schutz, Typ beurteilenDemand-Zero(weich)Aus Standby wiederanbinden(weich)Harter Fehler(Datenträger-E/A)Kopieren und die PTE tauschenGuard löschen und benachrichtigenAusnahme(0xC0000005 usw.)

Abbildung 4: Fehler, die durch denselben Einstiegspunkt kommen, teilen sich je nach Beurteilung in sechs Arten von Ausgang. Einzelheiten zu Guard-Seiten stehen in Abschnitt 9.

7. Demand-Zero — ein weicher Fehler, der nicht vom Datenträger liest

Demand-Zero ist der repräsentative weiche Fehler, der auftritt, wenn eine committete private Seite erstmals berührt wird. Microsofts Working-Set-Dokumentation listet auch „der Prozess bezieht sich erstmals auf eine zugewiesene virtuelle Seite“ als Beispiel eines weichen Fehlers.5

Demand-Zero hat die folgenden Merkmale.

  • Es besteht keine Notwendigkeit, Originaldaten vom Datenträger zu lesen
  • Der Anfangsinhalt ist null
  • Eine verfügbare physische Seite wird gebunden
  • Working Set und die kumulierte Page Fault Count steigen
  • Diese Behandlung allein erhöht Memory\\Pages Input/sec nicht

Deshalb bedeutet ein Ausschlag von Page Faults/sec direkt nach dem Start für sich genommen nicht, dass der Speicher der Engpass ist.

Der Kompromiss der verzögerten Zuweisung lohnt sich ebenfalls zu klären. Wenn Sie 256 MiB committen und tatsächlich nur 8 MiB nutzen, ist es vernünftig, die restlichen 248 MiB aus dem RAM zu lassen. Im Gegenzug trägt der erste Zugriff die Kosten der Fehlerbehandlung. Für latenzempfindliche Arbeit gibt es ein Design, das jede Seite vor dem Start berührt, um vorab zu fehlen (prefault), aber das ist ein Kompromiss, der die RAM-Residenz vorab erhöht.

8. Weiche Fehler und harte Fehler

8.1. Weiche Fehler

Ein weicher Fehler ist ein Fehler, der ohne Lese-E/A zu einem Backing Store aufgelöst werden kann. Typische Beispiele umfassen Folgendes.

  • Demand-Zero
  • Wiederanbinden einer Seite, die auf Standby/Transition bleibt
  • Anbinden einer gemeinsam genutzten Seite, die im Working Set eines anderen Prozesses liegt
  • Anbinden einer vorab geladenen Seite
  • Copy-on-Write, dessen Originalseite resident ist

Es bleibt CPU-Kosten für den Kernelübergang, Sperren, PTE-/PFN-Aktualisierungen, TLB-Kohärenz und dergleichen, aber es gibt keine Speicherwartezeit.5

8.2. Harte Fehler

Wenn die benötigte Seite dagegen nirgendwo im RAM liegt und aus einem Backing Store gelesen werden muss, ist das ein harter Fehler. Die Lesequelle ist nicht nur die Auslagerungsdatei.

  • Eine private Seite, die in die Auslagerungsdatei geschrieben wurde
  • Eine speicherabgebildete Datei
  • Ein EXE- oder DLL-Image
  • Eine Datendatei, auf die der Dateicache verweist

ETW-HardFault-Ereignisse enthalten FileObject, ReadOffset und ByteCount, sodass Sie die tatsächliche Lesequelle verfolgen können.6

Daher ist Hard Fault = ein Lesen von pagefile.sys nicht wahr.

Wenn ein Lesen aus dem Backing Store nötig ist, geht die Anforderung in den Windows-E/A-Stapel. Der Fluss von IRPs und Ausgabe/Abschluss wird in „Die Tiefen von Windows I/O (Teil 1)“ behandelt, und die Verbindung zum Dateicache in „Die Tiefen von Windows-I/O (Teil 4)“. Liegt die Seite im RAM, kann der Speicher-Manager von selbst zurückkehren; sonst gibt er E/A aus und wartet mit dem fehlerhaften Thread bis zum Abschluss.

9. Ein nicht auflösbarer Fehler wird zur Ausnahme

Ein Fehler, der nach Prüfung von VAD und PTE nicht als legitime Zuweisung, Page-In oder CoW aufgelöst werden kann, wird dem Benutzermodus als Ausnahme zugestellt.

Der repräsentative Fall ist STATUS_ACCESS_VIOLATION, Ausnahmecode 0xC0000005. Er tritt bei Lesen, Schreiben oder Ausführen einer ungültigen Adresse auf; der erste Ausnahmeparameter zeigt den Zugriffstyp und der zweite die verletzende Adresse.7

Typische Muster umfassen Folgendes.

  • NULL, eine freigegebene Adresse oder eine Adresse außerhalb eines Arrays lesen
  • Auf eine schreibgeschützte Seite schreiben
  • Einen Befehl von einer Seite ausführen, die DEP/NX nicht ausführbar gemacht hat
  • Einen reservierten, nicht committeten Bereich berühren

PAGE_GUARD hat eine etwas andere Bedeutung. Es ist eine einmalige Benachrichtigung über Zugriff: Es löst STATUS_GUARD_PAGE_VIOLATION aus und wird etwa für das Stack-Wachstum verwendet.8

Normale verzögerte Zuweisung, Page-In, CoW, Guard-Benachrichtigung und eine abschließende Zugriffsverletzung versammeln sich aus Sicht der CPU am selben Einstiegspunkt des Seitenfehlers. Was den Ausgang entscheidet, ist die Kombination aus VAD, PTE, Schutzattributen und Zugriffstyp.

10. Selbst nachsehen

Sie können den bisherigen Ablauf auf Ihrer eigenen Maschine beobachten. Das folgende C++-Programm Reserve 256 MiB, Committet sie, schreibt ein Byte auf jede Seite und Release zuletzt. Es wartet an jeder Stufe auf Enter, damit Sie die Änderungen in VMMap und PerfMon beobachten können.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>

#include <cstdio>
#include <cstdlib>

#pragma comment(lib, "Psapi.lib")

constexpr SIZE_T kSize = 256ull * 1024 * 1024;

void PrintMemory(const char* stage)
{
    PROCESS_MEMORY_COUNTERS_EX c{};
    c.cb = sizeof(c);
    if (!GetProcessMemoryInfo(
            GetCurrentProcess(),
            reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&c),
            sizeof(c))) {
        std::printf("GetProcessMemoryInfo failed: %lu\n", GetLastError());
        return;
    }

    std::printf(
        "%-10s WS=%zu MiB  Private=%zu MiB  Faults=%lu\n",
        stage,
        c.WorkingSetSize / 1024 / 1024,
        c.PrivateUsage / 1024 / 1024,
        c.PageFaultCount);
}

void Pause(const char* message)
{
    PrintMemory(message);
    std::puts("Press Enter...");
    (void)std::getchar();
}

int main()
{
    SYSTEM_INFO si{};
    GetSystemInfo(&si);
    std::printf("PID=%lu, page=%lu bytes\n",
                GetCurrentProcessId(), si.dwPageSize);

    void* base = VirtualAlloc(nullptr, kSize, MEM_RESERVE, PAGE_NOACCESS);
    if (!base) {
        std::fprintf(stderr, "Reserve failed: %lu\n", GetLastError());
        return EXIT_FAILURE;
    }
    Pause("reserved");

    if (!VirtualAlloc(base, kSize, MEM_COMMIT, PAGE_READWRITE)) {
        std::fprintf(stderr, "Commit failed: %lu\n", GetLastError());
        VirtualFree(base, 0, MEM_RELEASE);
        return EXIT_FAILURE;
    }
    Pause("committed");

    auto* bytes = static_cast<volatile unsigned char*>(base);
    for (SIZE_T offset = 0; offset < kSize; offset += si.dwPageSize) {
        bytes[offset] = 1;
    }
    Pause("touched");

    if (!VirtualFree(base, 0, MEM_RELEASE)) {
        std::fprintf(stderr, "Release failed: %lu\n", GetLastError());
        return EXIT_FAILURE;
    }
    Pause("released");
}

Aus der x64 Native Tools-Eingabeaufforderung von Visual Studio können Sie mit folgendem Befehl bauen.

cl /std:c++20 /EHsc /W4 memory_fault_demo.cpp

10.1. Worauf Sie in VMMap achten

VMMap ist ein Werkzeug, das reservierten virtuellen Speicher, Commit, Working Set, Private und Shareable nach Typ anzeigt.9 Die zu erwartenden Änderungen in jeder Stufe sind die folgenden.

Stufe Erwartete Änderung
Reserve Address Space Size steigt, aber Commit/WS steigen nicht um denselben Betrag
Commit Private Commit steigt um etwa 256 MiB
Touch Working Set und Private WS steigen deutlich, und die Fault Count steigt ebenfalls
Release Der Zielbereich verschwindet, und Commit und WS fallen

Tatsächliche Zahlen variieren mit Laufzeit, Sicherheitsprodukten, Speicherdruck und dem Beobachtungszeitpunkt. Achten Sie darauf, in welche Richtung sich die Zahlen zwischen den Stufen bewegt haben, nicht darauf, ob sie genau 256 MiB ergeben.

10.2. Weich und hart in PerfMon trennen

Legen Sie in PerfMon die folgenden Zähler auf dieselbe Zeitachse.

  • Process(<target>)\\Page Faults/sec
  • Memory\\Pages Input/sec
  • Memory\\Page Reads/sec
  • Memory\\Available MBytes
  • Process(<target>)\\Working Set - Private
  • Process(<target>)\\Private Bytes

Process\\Page Faults/sec enthält sowohl weiche als auch harte Fehler. Memory\\Pages Input/sec dagegen ist die Zahl der Seiten, die vom Datenträger gelesen wurden, um harte Fehler aufzulösen.10

In der Touch-Stufe dieses Programms sollte Page Faults/sec springen, während Pages Input/sec nicht stark steigen sollte. Neu committete Seiten werden durch Demand-Zero materialisiert, daher müssen keine Originaldaten vom Datenträger gelesen werden.

Wenn mehrere Prozesse denselben Namen teilen, können PerfMon-Nummern wie process#1 über Neustarts hinweg wechseln. Gegenprüfen Sie mit einem Zähler, der die PID anzeigt, oder identifizieren Sie per PID mit Process V2 oder ETW/WPA.

11. Drei Fehldeutungen, die Sie in der Praxis vermeiden sollten

11.1. „Commit ist gestiegen, also ist es ein RAM-Leck“

Commit ist der versprochene Betrag an Inhalt, der gehalten werden soll; unberührte Seiten müssen nicht im RAM resident sein. Um ein Leck zu beurteilen, sehen Sie die Zeitreihe der Private Bytes, die Aufschlüsselung der Zuweisungen und ob die Zahl nach dem Ende der Verarbeitung auf eine Basislinie zurückkehrt.

11.2. „Page Faults/sec ist hoch, also ist die Festplatte langsam“

Weiche Fehler beinhalten keine Datenträger-E/A. Trennen Sie Page Faults/sec, Pages Input/sec und die Speicherwartezeit, und verfolgen Sie bei Bedarf Quelldatei und Stapel mit ETW-HardFault-Ereignissen.

11.3. „Das Working Set zu leeren behebt das Leck“

Eine Seite aus dem Working Set zu entfernen gibt weder Commit noch Besitz frei. Die Seite wandert nach Standby oder Modified und fehlt später wieder herein. Ein Leck zu beheben erfordert, dass der Allokator VirtualFree, eine Heap-Freigabe, Objektzerstörung und dergleichen ausführt.

Wohin diese entfernte physische Seite geht, verfolgen wir in Teil 2.

12. Zusammenfassung

  • MEM_RESERVE legt einen virtuellen Adressbereich beiseite, weist aber keinen physischen Bereich in RAM oder Auslagerungsdatei zu.1
  • MEM_COMMIT verbraucht Commit und garantiert, dass der Inhalt künftig gehalten werden kann, aber eine gewöhnliche physische Seite wird erst beim ersten Zugriff zugewiesen.12
  • Der VAD ist das Bereichsregister, die PTE das Register der virtuellen Seite, die PFN-Datenbank das Register der physischen Seite.
  • Ein TLB-Miss ist kein Seitenfehler. Ist die PTE gültig, löst ihn allein ein Seitentabellenlauf.
  • Demand-Zero, Transition-Wiederherstellung und das Anbinden einer gemeinsam genutzten Seite sind weiche Fehler, die ohne Datenträger-E/A auflösbar sind.5
  • Ist ein Lesen aus der Auslagerungsdatei, einer DLL, einer EXE oder einer zugeordneten Datei nötig, ist es ein harter Fehler.6
  • Kann die Prüfung von VAD, PTE und Schutzattributen den Fehler nicht auflösen, erhalten Sie eine Ausnahme wie 0xC0000005.7
  • Für eine Leistungsbeurteilung sehen Sie nicht Page Faults/sec allein; sehen Sie Pages Input/sec, Available, Working Set, Private Bytes und die Speicherwartezeit auf derselben Zeitachse.

Weiter in Teil 2, „Das Leben einer physischen Seite: Fünf Listen und die Wahrheit über die Auslagerungsdatei“.

Nachdem das Commit-Versprechen zu einer physischen Seite geworden ist, verfolgen wir anhand der PFN-Datenbank und der Seitenlisten, wohin diese Seite geht, wenn sie das Working Set verlässt.

Weiterführende Artikel

Zugehörige Beratungsfelder

KomuraSoft LLC untersucht Speicherverbrauch von Windows-Anwendungen, Zugriffsverletzungen, Startverzögerungen, Paging und Fehler in nativem Code.

Quellen

  1. Microsoft Learn, VirtualAlloc function. Dazu, dass MEM_RESERVE einen virtuellen Adressbereich reserviert, ohne physischen Speicher zuzuweisen; dass MEM_COMMIT eine Commit-Charge gegen den Gesamtspeicher und die Auslagerungsdatei des Systems verrechnet; dass der Anfangsinhalt einer committeten Seite null ist; und dass die tatsächliche physische Seite erst zugewiesen wird, wenn darauf zugegriffen wird.  2 3 4 5 6

  2. Microsoft Learn, PERFORMANCE_INFORMATION structure. Dazu, dass CommitTotal die aktuelle Zahl der System-Commit-Seiten ist und CommitLimit die Obergrenze, die ohne Erweiterung der Auslagerungsdatei committet werden kann.  2

  3. Microsoft Learn, !vad (WinDbg). Dazu, dass !vad den VAD-Baum anzeigt und Start- und End-VPN, Commit, Mapped/Private, Schutzattribute, die Control Area und mehr prüfen lässt. 

  4. Microsoft Learn, PageFault_TypeGroup1 class. Dazu, dass ETW Transition Fault, Demand Zero Fault, Copy-on-Write, Guard Page Fault, Hard Page Fault und Access Violation unterscheidet und aufzeichnet. 

  5. Microsoft Learn, Working Set. Dazu, dass ein weicher Fehler ohne Zugriff auf einen Backing Store auflösbar ist und aus dem Working Set eines anderen Prozesses, Transition, Demand-Zero bei erster Referenz und dergleichen entsteht.  2 3

  6. Microsoft Learn, PageFault_HardFault class. Dazu, dass ein HardFault-Ereignis FileObject, ReadOffset, ByteCount, VirtualAddress und eine Thread-ID enthält, sodass die Lesequelle verfolgt werden kann.  2

  7. Microsoft Learn, Access Violation C0000005. Dazu, dass 0xC0000005 bei Lesen, Schreiben oder Ausführen einer ungültigen Speicheradresse auftritt und die Ausnahmeparameter Zugriffstyp und verletzende Adresse anzeigen.  2

  8. Microsoft Learn, Creating Guard Pages. Dazu, dass PAGE_GUARD eine einmalige Benachrichtigung über Seitenzugriff liefert und STATUS_GUARD_PAGE_VIOLATION auslöst. 

  9. Microsoft Learn, VMMap - Sysinternals. Dazu, dass VMMap committeten virtuellen Speicher nach Typ aufschlüsselt und Working Set sowie eine detaillierte Adresskarte jedes Typs anzeigt. 

  10. Microsoft Learn, Performance Analysis of Logs (PAL) Tool. Dazu, dass Memory\\Pages Input/sec die Zahl der Seiten ist, die vom Datenträger gelesen wurden, um harte Seitenfehler aufzulösen. 

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Weist ein MEM_COMMIT an VirtualAlloc in diesem Moment RAM zu?
Im gewöhnlichen privaten Speicher verbraucht Commit den Commit-Spielraum des Systems, die zugehörige physische Seite wird aber erst beim ersten Zugriff zugewiesen. Eine Seite, die zuerst durch einen Schreibzugriff berührt wird, erhält ihre physische Seite während der Behandlung eines Demand-Zero-Fehlers.
Bedeutet ein Seitenfehler, dass etwas falsch ist oder ein Leistungsproblem vorliegt?
Nein. Weiche Fehler ohne Datenträger-E/A — etwa Demand-Zero oder die Rückkehr einer Seite aus Standby — sind Normalbetrieb. Für eine Leistungsbeurteilung sehen Sie nicht nur Page Faults/sec, sondern auch Pages Input/sec, die Speicherwartezeit und Available MBytes.
Sind ein TLB-Miss und ein Seitenfehler dasselbe?
Sie sind verschieden. Auch wenn der TLB keine Übersetzung hat, läuft die CPU bei gültiger PTE der Seitentabelle lediglich die Tabelle ab und trägt die Übersetzung neu ein. Zum Einstiegspunkt des Seitenfehlers geht es, wenn die PTE ungültig ist oder ein Schutzverstoß vorliegt.
Kann man keine Zugriffsverletzung bekommen, wenn der Adressbereich in einem VAD liegt?
Nicht unbedingt. Neben der Existenz eines VAD bewertet der Speicher-Manager Reserve gegenüber Commit, Lese-/Schreib-/Ausführungsschutz, Guard-Seiten, den PTE-Zustand und mehr. Lässt sich der Fehler nicht auflösen, erhalten Sie eine Ausnahme wie 0xC0000005.
Bedeutet ein hohes Page Faults/sec, dass dem System RAM fehlt?
Das allein sagt das nicht. Page Faults/sec enthält auch eine große Zahl weicher Fehler. Sie müssen es auf derselben Zeitachse mit Memory\Pages Input/sec, Memory\Page Reads/sec, Available MBytes und der Datenträgerwartezeit korrelieren.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog