Die Tiefen des Windows-Speichers (Teil 3) — Abschnittsobjekte und Copy-on-Write: Was DLLs und Dateizuordnungen wirklich sind

· Aktualisiert am: · · Windows, Speicherverwaltung, Gemeinsamer Speicher, Dateizuordnung, Copy-on-Write, DLL, Cache Manager

Änderungsverlauf (Erstfassung, veröffentlicht am 20. Aug 2026)
Erstveröffentlichung
Diesen Artikel zitieren(DOI (registriertes Archiv): 10.5281/zenodo.22176171)

Die folgenden DOIs verweisen auf bereits archivierte Versionen, die vom aktuellen Text abweichen können. Verwenden Sie die URL dieser Seite, um auf den aktuellen Text zu verweisen.

Go Komura (2026). Die Tiefen des Windows-Speichers (Teil 3) — Abschnittsobjekte und Copy-on-Write: Was DLLs und Dateizuordnungen wirklich sind. KomuraSoft LLC. https://comcomponent.com/de/blog/windows-memory-internals-section-copy-on-write/

DOI (registriertes Archiv)
10.5281/zenodo.22176171
DOI (zuletzt registrierte Version)
10.5281/zenodo.22176172

„Wenn 100 Prozesse dieselbe kernel32.dll nutzen, braucht Windows dann 100 Sätze Codeseiten?“ Der Ausgangspunkt von Teil 3 ist die Frage, wenn mehrere Prozesse denselben Inhalt nutzen. Wenn zwei Prozesse dieselbe Datei im Speicher zuordnen, kann der andere eine Seite nutzen, die einer von ihnen gelesen hat?

Die Antwort liegt im Mechanismus, von unterschiedlichen virtuellen Adressen auf dieselbe physische Seite abzubilden. Das Zentrum der Teilungseinheit ist das Abschnittsobjekt. Und der Mechanismus, der nur die beim Schreiben nötigen Seiten privatisiert, ist Copy-on-Write (CoW).

Im vorherigen Artikel „Die Tiefen des Windows-Speichers (Teil 2) — Das Leben einer physischen Seite“ haben wir verfolgt, wie eine physische Seite, die das Working Set verlässt, durch Modified, Standby, Free und Zeroed wandert. Auf Standby bleiben auch Seiten von DLLs, EXEs, zugeordneten Dateien und dem Dateicache. Hier kommt die Sicht des Teilens durch mehrere Prozesse hinzu.

Wir betrachten der Reihe nach EXE/DLL, Datendateien, auslagerungsdateigestützten gemeinsamen Speicher und den Dateicache und unterscheiden dann die Pfade von Cache, Daten und Image auf demselben Dateistrom. Das Lesen der Zahlen setzt den Einführungsartikel „Was bedeutet Windows’ „Speicherauslastung“ eigentlich?“ voraus.

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

Diese Reihe geht in der Reihenfolge eine physische Seite erhalten → dem Fluss von Residenthalten und Rückgewinnung folgen → Teilen und Privatisieren verstehen vor.

Teil Thema Was dieser Teil verfolgt
Teil 1 Virtuelle Adressen und Seitenfehler Wann ein mit VirtualAlloc angelegter Bereich physischen RAM erhält
Teil 2 Das Leben einer physischen Seite Die Zustandsübergänge einer Seite, die das Working Set verlässt, und die Rolle der Auslagerungsdatei
Teil 3 (dieser Artikel) Abschnittsobjekte und Copy-on-Write Wie DLLs, Dateizuordnungen und gemeinsamer Speicher physische Seiten teilen

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

Warum können mehrere Prozesse dieselbe DLL oder Datei als einen Satz physischer Seiten nutzen?

Bevor Sie lesen Inhalt
Zielgruppe Entwickler und Betrieb, die das Teilen von DLLs, CreateFileMapping, MapViewOfFile, gemeinsamen Speicher, CoW und die Beziehung zum Dateicache aus der inneren Struktur verstehen wollen
Voraussetzungen Windows 10/11 oder aktueller Windows Server
Erforderlicher Hintergrund Grundlagen virtueller Adressen, Seitenfehler und des Working Sets
Schwierigkeit Mittelstufe. Wir verwenden auch interne Begriffe wie Control Area und Prototype PTE

Zur Beobachtung nutzen wir VMMap, Process Explorer und QueryWorkingSetEx.

In der Abbildung kennzeichnet eine durchgezogene Linie eine stets geltende Beziehung und eine gestrichelte Linie eine bedingte (die Bedingungen stehen bei jeder Beziehung auf der Detailseite). Die vollständige Liste der Beziehungen (19 insgesamt, mit Beleg und Sicherheitsgrad) und die Definitionen der wichtigsten Konzepte sind auf der Detailseite der Wissenskarte (auf Japanisch) zusammengestellt. Daten: JSON-LD / Turtle

1. Zuerst das Fazit

Das Gesamtbild fasst sich in drei Punkten zusammen.

  1. Trennen Sie den geteilten Inhalt und die Adresse, die jeder Prozess sieht. Ein Abschnittsobjekt steht für einen teilbaren Speicherbereich, und jeder Prozess ordnet einen Teil davon als Sicht ein. Auch bei demselben Abschnittsoffset dürfen die virtuellen Adressen der Sichten nicht übereinstimmen.12
  2. Trennen Sie, womit der Inhalt gestützt wird und über welchen Pfad er genutzt wird. Es gibt Abschnitte, die eine echte Datei stützt, und solche, die die Auslagerungsdatei stützt. EXE/DLL sind Image-Abschnitte, gewöhnliche Dateizuordnungen Datenabschnitte. Den Seitenschutz von SEC_IMAGE bestimmen die Attribute im PE. Auch die Pfade von Cache, Daten und Image dürfen Sie nicht zu einem zusammenfassen.234
  3. Die Privatisierung durch CoW bestätigen Sie am Seitenzustand des Teilens. Während des Lesens wird geteilt, nur die geschriebene Seite wird kopiert und die PTE dieses Prozesses ausgetauscht. FILE_MAP_COPY erhebt Commit vorab für die ganze Sicht, deshalb können Private Bytes allein das Eintreten nicht festmachen. Zur Bestätigung nutzen Sie das Shared-Bit von QueryWorkingSetEx.567

In einem Satz: Geteilt werden nicht virtuelle Adressen, sondern der Inhalt im Abschnitt und die physische Seite, die ihm in diesem Moment entspricht.

Was Sie wissen wollen Abschnitt
Beziehung von Abschnitt, Sicht und Backing Store Abschnitte 2 bis 3
Teilen von DLLs und CoW beim Schreiben Abschnitte 4 bis 6
Unterschied zum Cache Manager und warum etwas nach dem Schließen bleibt Abschnitte 7 bis 8
Teilen und Privatisieren in zwei Prozessen nachprüfen Abschnitte 9 bis 10

2. Abschnittsobjekt und Sicht

Nach Microsofts Definition steht ein Abschnittsobjekt für einen teilbaren Speicherbereich und ist zugleich der Mechanismus, eine Datei in den Adressraum eines Prozesses abzubilden.1

Der Kniff ist, den Abschnitt selbst und die Sicht, die jeder Prozess sieht, getrennt zu denken.

Begriff Rolle
Abschnittsobjekt Steht für geteilten Inhalt, Größe, Backing Store und obere Schutzgrenze
Sicht Zeigt einen Teil des Abschnitts in einen virtuellen Adressbereich eines Prozesses
PTE Bindet jede virtuelle Seite in der Sicht an die aktuelle physische Seite oder den noch nicht materialisierten Zustand
PFN Steht für eine physische Seite, die wirklich im RAM liegt

Auch die Win32-API lesen Sie entlang dieser Rollenunterschiede.3

Stufe Was Sie erhalten
CreateFileMapping Handle des Dateizuordnungsobjekts
MapViewOfFile Eine Sicht im virtuellen Raum des Prozesses
Erster Zugriff auf die Sicht Zuordnung der betroffenen Seite zum physischen Speicher über einen Seitenfehler

Zuerst ein Beispiel, eine schreibgeschützte Datei zuzuordnen.

HANDLE mapping = CreateFileMappingW(
    file,
    nullptr,
    PAGE_READONLY,
    0,
    0,
    nullptr);

void* view = MapViewOfFile(
    mapping,
    FILE_MAP_READ,
    0,
    0,
    0);

Allein der Aufruf von CreateFileMapping liefert noch keine Adresse, die der Prozess lesen kann. Auch das Anlegen einer Sicht bringt nicht alle Seiten sofort in den RAM. Ab der ersten berührten Seite liest ein Seitenfehler den Dateiinhalt und bindet eine physische Seite an die PTE.8 Das Demand-Paging aus Teil 1 gilt also unverändert auch für Abschnittsichten.

2.1. Derselbe Abschnitt, unterschiedliche virtuelle Adressen

Auch wenn Prozess A und B denselben Offset desselben Abschnitts zuordnen, kann die Startadresse der Sicht verschieden sein.

Abbildung unterschiedlicher virtueller Adressen auf dieselbe physische SeiteProzess A und Prozess B haben jeweils eine Sicht an einer anderen virtuellen Adresse, erreichen über denselben Abschnittsoffset aber dieselbe physische Seite PFN XProcess A: 0x000001A00000 + 0x3000Abschnittsoffset 0x3000Process B: 0x000002700000 + 0x3000dieselbe physische Seite PFN X

Abbildung 1: Geteilt werden Inhalt im Abschnitt und physische Seite, nicht die virtuelle Adresse.

Hier liegt der Grund, warum Sie im gemeinsamen Speicher keine Rohzeiger speichern dürfen. Der Zeigerwert von Prozess A kann in Prozess B eine irrelevante Adresse sein.

In einer gemeinsamen Struktur nutzen Sie Offsets vom Anfang der Sicht, festbreitige Ganzzahlen sowie eine explizite Version und Ausrichtung. Auch Microsofts Dokumentation zu MapViewOfFileEx rät, Offsets von der Basis statt Zeiger zu speichern, weil dieselbe Adresse künftig nicht garantiert verfügbar ist.6

3. Dateigestützt und auslagerungsdateigestützt

Abschnitte teilen sich grob in zwei Arten danach, wo der Inhalt wiederhergestellt wird.

Die Verzweigung von dateigestützt und auslagerungsdateigestütztÜbergeben Sie CreateFileMapping eine echte Datei, entsteht ein dateigestützter Abschnitt, dessen saubere Seiten aus der Originaldatei neu gelesen werden können. INVALID_HANDLE_VALUE ergibt einen auslagerungsdateigestützten Abschnitt, dessen Inhalt die Auslagerungsdatei stützt und der mit der Objektzerstörung verschwindetHandle einer echten Datei übergebenINVALID_HANDLE_VALUE übergebenCreateFileMappingdateigestützter Abschnittauslagerungsdateigestützter Abschnittsaubere Seiten sind aus der Originaldatei neu einlesbarInhalt stützt die Auslagerungsdatei und verschwindet bei Zerstörung

Abbildung 2: Der Unterschied des Backing Store bestimmt, wo der Inhalt wiederhergestellt werden kann, und die Lebensdauer.

3.1. Dateigestützter Abschnitt

Übergeben Sie CreateFileMapping eine echte Datei, entsteht ein dateigestützter Abschnitt.

  • Eine schreibgeschützte Sicht liest benötigte Seiten aus der Datei.
  • Änderungen einer Lese-/Schreibsicht gelten als Daten dieser Datei.
  • Änderungen einer CoW-Sicht werden nicht in die Originaldatei geschrieben und werden private Seiten.

Ist eine dateigestützte Seite sauber, kann die physische Seite verworfen und aus der Originaldatei neu gelesen werden. Diese Eigenschaft stützt die Effizienz von Standby und Dateicache aus Teil 2.

3.2. Auslagerungsdateigestützter Abschnitt

Übergeben Sie CreateFileMapping in hFile INVALID_HANDLE_VALUE und geben Sie eine Größe an, entsteht ein auslagerungsdateigestützter Abschnitt.

HANDLE mapping = CreateFileMappingW(
    INVALID_HANDLE_VALUE,
    nullptr,
    PAGE_READWRITE,
    0,
    64 * 1024,
    L"Local\\KomuraMemoryDemo");

Dieser Abschnitt hat keine explizite Datendatei; den Inhalt stützt die Auslagerungsdatei. Der Anfangsinhalt ist null. Damit mehrere Prozesse dasselbe Objekt nutzen, verwenden Sie Namen, Handle-Vererbung, DuplicateHandle und Vergleichbares.93

Änderungen an einer gemeinsamen Seite sind sichtbar, aber keine dauerhafte Speicherung. Prozesse, die dieselbe gemeinsame Seite zuordnen, sehen die Änderungen. Wird das Abschnittsobjekt zerstört, bleibt der Inhalt nicht; als Ersatz für eine dauerhafte Datei eignet er sich nicht.2

Beachten Sie, dass gemeinsamer Speicher nicht automatisch Ausschluss mitbringt. Mutex, Semaphore, Event, lock-free-Protokoll und Vergleichbares entwerfen Sie gesondert.9

4. Image-Zuordnung und Datenzuordnung

Wenn man sagt „auch EXE und DLL sind Dateizuordnung“, muss der Unterschied zu einer gewöhnlichen Datendatei bleiben.

Punkt Image-Zuordnung Datenzuordnung
Hauptverwendung Laden von EXE, DLL Gewöhnliche Datei, gemeinsame Daten
Erzeugungsattribut SEC_IMAGE PAGE_READONLY, PAGE_READWRITE und Vergleichbares
Seitenschutz Bestimmen die Attribute im PE-Image Bestimmen Angabe von Zuordnung und Sicht
Schreiben Kann über writable section oder CoW privatisiert werden Shared write oder CoW wählbar
Type von VirtualQuery MEM_IMAGE MEM_MAPPED

Bei SEC_IMAGE bestimmen die Abschnittsattribute des ausführbaren Images selbst den Seitenschutz der Sicht stärker als der gewöhnliche Schutzwert, den Sie CreateFileMapping übergeben.3

4.1. Nicht die ganze DLL wird unbedingt geteilt

Unveränderte Seiten wie Code können viele Prozesse auf derselben physischen Seite teilen. Seiten, die prozesseigene Änderung brauchen, zweigen per CoW ab.

Es gibt jedoch Einflüsse von ASLR-Relokation, Loader-Korrektur, Hotpatch und den tatsächlichen PE-Abschnittsattributen. Auch wenn dieselbe DLL geladen ist, werden nicht alle Seiten unbedingt geteilt.

Wichtig ist der Entwurf, teilbare Seiten zuerst zu teilen und nur Seiten, die Änderung brauchen, verzögert zu kopieren.

5. Copy-on-Write von Anfang bis Ende

Wir verfolgen den Zustand, in dem zwei Prozesse dieselbe CoW-Seite lesen, bis Prozess A ein Byte schreibt.

5.1. Vor dem Schreiben

Bevor geschrieben wird, erreichen die PTE beider Prozesse begrifflich dieselbe gemeinsame Seite, und das Lesen gelingt unverändert.

Gemeinsamer Zustand vor Copy-on-WriteBevor geschrieben wird, erreichen die PTE von Prozess A und Prozess B dieselbe gemeinsame Seite PFN X, und das Lesen gelingt unverändertProcess A PTEgemeinsam PFN X(read / copy-on-write)Process B PTE

Abbildung 3: Vor dem Schreiben zeigen die PTE beider Prozesse auf dieselbe physische Seite.

5.2. Schreiben löst einen Schutzfehler aus

Eine CoW-Seite ist von Anfang an keine gewöhnliche gemeinsam schreibbare Seite. Versucht Prozess A zu schreiben, erzeugt die CPU einen Schutzfehler. Der Speicher-Manager, der die Kontrolle erhält, beurteilt das nicht als unzulässiges Schreiben, sondern als Schreiben auf ein CoW-Attribut.

5.3. Eine neue physische Seite anlegen

Nach dieser Beurteilung tut Windows Folgendes.

  1. Eine physische Seite für Prozess A holen.
  2. Den Inhalt von PFN X auf die neue Seite PFN Y kopieren.
  3. Die PTE von Prozess A auf PFN Y umhängen.
  4. Den Schutz auf der Seite von Prozess A auf gewöhnliches Lesen/Schreiben ändern.
  5. Die gescheiterte Schreibanweisung erneut ausführen.
Verzweigter Zustand nach Copy-on-WriteNach dem Schreiben von Prozess A wird nur dessen PTE auf die kopierte private Seite PFN Y umgehängt, die PTE von Prozess B zeigt weiter auf die ursprüngliche gemeinsame Seite PFN XInhalt beim Schreiben kopierenProcess A PTEprivat PFN Y(read/write, nach Änderung)Process B PTEgemeinsam PFN X(ursprünglicher Inhalt)

Abbildung 4: Nur die PTE des schreibenden Prozesses wird auf die neue private Seite umgehängt, der andere liest weiter den ursprünglichen Inhalt.

Prozess B liest weiter den ursprünglichen Inhalt und sieht die Änderung von Prozess A nicht. Das ist Copy-on-Write. Sowohl das Teilen von DLLs als auch FILE_MAP_COPY nutzen dasselbe Prinzip, nicht zu kopieren, bis geschrieben wird.56

5.4. Der Unterschied zu FILE_MAP_WRITE

Das Auswahlkriterium ist, ob Sie das Schreiben auch der Gegenseite zeigen oder nur die eigene Änderung wollen.6

Sicht Was nach dem Schreiben geschieht
FILE_MAP_WRITE Als gemeinsames Schreiben wird die Änderung auch in anderen Sichten derselben Dateizuordnung sichtbar
FILE_MAP_COPY Nur die geschriebene Seite wird prozesseigen. Die Änderung wird nicht in die Originaldatei zurückgeschrieben und geht verloren, wenn die Sicht aufgehoben wird

„Aktualisierung über gemeinsamen Speicher mitteilen“ oder „von gemeinsamen Anfangsdaten aus jeder Prozess privat ändern“. Je nach Ziel ist die Wahl entgegengesetzt.

6. Nach CoW bleibt MEM_MAPPED / MEM_IMAGE

Hier trennen Sie die Herkunft des Bereichs und den aktuellen Teilungszustand der physischen Seite.

Eine Seite nach CoW ist physisch Private, aber der Type von VirtualQuery wechselt nicht zu MEM_PRIVATE. Eine Datensicht bleibt MEM_MAPPED, ein ausführbares Image MEM_IMAGE. Type zeigt, aus welcher Anfangszuweisung der Bereich stammt.7

Ob eine Seite je Seite CoW-fertig ist, prüfen Sie so.

  1. Die Zielseite anfassen und resident machen.
  2. Mit QueryWorkingSetEx die Working-Set-Informationen der Seite holen.
  3. Das Bit Shared ansehen.
  4. Ist Shared == 0, ist diese residente Seite Private.

Auch in VMMap sehen Sie nicht nur den Type des Bereichs, sondern die Aufteilung Private/Shareable des Working Set.

6.1. Private Bytes müssen nicht steigen

FILE_MAP_COPY privatisiert nur geschriebene Seiten. Der Zeitpunkt, zu dem Commit erhoben wird, ist jedoch ein anderer.

Für die Möglichkeit, später in alle Seiten der Sicht zu schreiben, erhebt Windows beim Zuordnen die Commit charge für die ganze Sicht.6 Deshalb steigen Private Bytes nicht unbedingt um 4 KiB in dem Moment, in dem die erste Seite geschrieben wird.

Die bevorzugten Kennzahlen zur Beobachtung von CoW sind:

  • das Shared-Bit von QueryWorkingSetEx
  • Private WS / Shareable WS in VMMap
  • physische Seiteninformationen in RAMMap
  • Private Bytes als Hilfsinformation

Machen Sie „stiegen Private Bytes im Moment des Schreibens?“ allein zum Bestehen/Nichtbestehen, übersehen Sie korrekt laufendes CoW.

7. Berührungspunkt mit dem Cache Manager — drei Pfade trennen

„Laden von EXE/DLL, Dateicache und gemeinsamer Speicher sind alles Abschnitte“ gibt das Gesamtbild, aber Sie dürfen die Implementierung nicht auf ein Objekt zusammendrücken.

Ein Dateistrom hat SECTION_OBJECT_POINTERS, die Speicher-Manager und Cache Manager nutzen.

typedef struct _SECTION_OBJECT_POINTERS {
    PVOID DataSectionObject;
    PVOID SharedCacheMap;
    PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;

Diese Struktur bindet das Dateiobjekt an die Abschnitte des Dateistroms und verfolgt Inhalt und Cache-Informationen im Speicher.4 Zustand und Verarbeitungspfad, auf die jedes Feld zeigt, sind jedoch getrennt.

Pfad Zugehöriges Feld Rolle der Verarbeitung
cached ReadFile / WriteFile SharedCacheMap Der Cache Manager nutzt eine Cache-Sicht
Zuordnungsfehler einer Datendatei DataSectionObject Der Speicher-Manager verarbeitet auf der Datenseite und koordiniert, damit der Inhalt mit cached I/O auf demselben Dateistrom stimmig bleibt
Image-Fehler von EXE/DLL ImageSectionObject Der Speicher-Manager verarbeitet mit Image-Abschnitt und Paging-E/A. Das ist kein Pfad über SharedCacheMap

Der Berührungspunkt mit der I/O-Reihe ist, dass diese drei auf demselben Dateistrom verbunden sind. Lesen Sie das nicht als „jeder Pfad geht durch den Cache Manager“.

Drei Pfade, die an denselben Dateistrom gebunden sindcached ReadFile/WriteFile nutzt SharedCacheMap, ein Fehler der Datenzuordnung DataSectionObject, ein Image-Fehler von EXE/DLL ImageSectionObject; die drei binden sich über SECTION_OBJECT_POINTERS an denselben Dateistromcached ReadFile / WriteFileSharedCacheMapFehler der DatenzuordnungDataSectionObjectImage-Fehler von EXE/DLLImageSectionObjectderselbe Dateistrom (SECTION_OBJECT_POINTERS)

Abbildung 5: Die drei Pfade werden getrennt verarbeitet, sind aber auf demselben Dateistrom verbunden.

Die Gemeinsamkeit der drei ist nicht „alle gehen in den Cache Manager“, sondern dass derselbe Dateistrom über SECTION_OBJECT_POINTERS getrennte Zustände von Cache, Datenabschnitt und Image-Abschnitt verbindet.4 Cache-Lesen/Schreiben, Lazy Writer und die Beziehung Cc/Mm behandelt „Die Tiefen von Windows-I/O (Teil 4) — Cache-Manager“.

Mischen Sie speicherabgebildete Sichten und ReadFile/WriteFile, ist nicht garantiert, dass stets derselbe Moment des Inhalts sichtbar ist. Entwurf mit Synchronisation, Flush und Dateifreigabemodus ist nötig.36

8. Lebensdauer von Objekt und Sicht

Ein Handle zu schließen und eine Sicht aufzuheben, sind verschieden. Allein das Schließen des Handles von CreateFileMapping lässt bestehende Sichten nicht verschwinden.

Eine Sicht hat eine interne Referenz auf den Abschnitt. Erst wenn alle Sichten mit UnmapViewOfFile und alle Handles mit CloseHandle geschlossen sind, kann das Objekt zerstört werden.3

UnmapViewOfFile(view);
CloseHandle(mapping);
CloseHandle(file);

Diese Trennung der Lebensdauer wird zur Ursache von „die Datei ist geschlossen, wird aber noch verwendet“. Auch nach dem Schließen des Dateihandles, wenn Image-Abschnitt oder Datensicht den Dateistrom referenzieren, kommt der endgültige close der Datei später.

Die Beziehung zu cleanup/close auf der I/O-Seite behandelt „Die Tiefen von Windows I/O (Teil 1)“, Fallstricke der Implementierung „Fallstricke bei Shared Memory und Best Practices für die Praxis“.

9. Mit eigenen Augen nachprüfen

9.1. Dieselbe DLL in zwei Prozessen ansehen

Zuerst prüfen Sie das Teilen einer DLL an bestehenden Prozessen.

  1. Starten Sie Process Explorer als Administrator.
  2. Starten Sie zwei cmd.exe.
  3. Wählen Sie View > Lower Pane View > DLLs.
  4. Bestätigen Sie in beiden Prozessen Pfad und Zuordnung derselben DLL.
  5. Öffnen Sie in VMMap jedes cmd.exe und vergleichen Sie Working Set, Private und Shareable von Images.

Allein die Anzeige derselben DLL beweist nicht die Übereinstimmung der PFN

Dass Process Explorer dieselbe DLL zeigt, beweist, dass beide dasselbe Image zuordnen. Allein das beweist jedoch nicht die PFN-Übereinstimmung jeder Seite. Kombinieren Sie die Shareable-Aufteilung von VMMap, RAMMap und QueryWorkingSetEx, um das Teilen je Seite zu bestätigen. Process Explorer und VMMap stellt Sysinternals bereit.1011

9.2. FILE_MAP_COPY in zwei Prozessen beobachten

Das folgende Programm ordnet dieselbe Datei als CoW-Sicht zu und zeigt das Shared-Bit von QueryWorkingSetEx. Das Dateizuordnungsobjekt wird mit PAGE_READONLY erzeugt; dieser Schutz ist mit einer Sicht von FILE_MAP_COPY kompatibel, und das erste Schreiben auf der Sichtseite löst CoW aus.3

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

#include <cstdio>
#include <cwchar>

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

void PrintPage(const char* stage, void* address)
{
    MEMORY_BASIC_INFORMATION mbi{};
    if (!VirtualQuery(address, &mbi, sizeof(mbi))) {
        std::printf("VirtualQuery failed: %lu\n", GetLastError());
        return;
    }

    for (int attempt = 0; attempt < 3; ++attempt) {
        // The page may have been trimmed while the user was waiting.
        // Touch it immediately before querying the working-set attributes.
        volatile unsigned char resident =
            *static_cast<volatile unsigned char*>(address);
        (void)resident;

        PSAPI_WORKING_SET_EX_INFORMATION ws{};
        ws.VirtualAddress = address;
        if (!QueryWorkingSetEx(GetCurrentProcess(), &ws, sizeof(ws))) {
            std::printf("QueryWorkingSetEx failed: %lu\n", GetLastError());
            return;
        }

        if (!ws.VirtualAttributes.Valid) {
            Sleep(0);
            continue;
        }

        std::printf(
            "%s: Type=0x%lx Valid=1 Shared=%llu ShareCount=%llu\n",
            stage,
            static_cast<unsigned long>(mbi.Type),
            static_cast<unsigned long long>(ws.VirtualAttributes.Shared),
            static_cast<unsigned long long>(ws.VirtualAttributes.ShareCount));
        return;
    }

    std::printf(
        "%s: page is not resident; Shared/ShareCount were not interpreted\n",
        stage);
}

int wmain(int argc, wchar_t** argv)
{
    if (argc != 3) {
        std::fwprintf(stderr, L"usage: cow_demo <file> <read|write>\n");
        return 2;
    }

    HANDLE file = CreateFileW(
        argv[1], GENERIC_READ, FILE_SHARE_READ,
        nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (file == INVALID_HANDLE_VALUE) return 3;

    HANDLE mapping = CreateFileMappingW(
        file, nullptr, PAGE_READONLY, 0, 0, nullptr);
    if (!mapping) {
        CloseHandle(file);
        return 4;
    }

    auto* view = static_cast<unsigned char*>(
        MapViewOfFile(mapping, FILE_MAP_COPY, 0, 0, 0));
    if (!view) {
        CloseHandle(mapping);
        CloseHandle(file);
        return 5;
    }

    volatile unsigned char value = view[0];
    (void)value;

    std::puts("Start the other process. When both are waiting, press Enter...");
    (void)std::getchar();
    PrintPage("before", view);

    if (std::wcscmp(argv[2], L"write") == 0) {
        std::puts("Press Enter to trigger copy-on-write...");
        (void)std::getchar();
        view[0] ^= 0x5a;
        PrintPage("after write", view);
    } else {
        std::puts("After the writer changes its page, press Enter...");
        (void)std::getchar();
        PrintPage("reader after peer write", view);
    }

    std::puts("Press Enter to exit...");
    (void)std::getchar();

    UnmapViewOfFile(view);
    CloseHandle(mapping);
    CloseHandle(file);
}

Bauen und dieselbe Datei in zwei Prozessen öffnen

Bauen und vorbereiten.

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

$path = "$env:TEMP\\cow-demo.bin"
[IO.File]::WriteAllBytes($path, [byte[]]::new(65536))

Anschließend öffnen Sie in zwei Konsolen dieselbe Datei.

.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" read
.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" write

Die Reihenfolge der Enter-Tasten und die betrachteten Werte trennen

Reihenfolge Vorgang Was Sie bestätigen
1 Beide starten, zuerst auf der read-Seite, dann auf der write-Seite Enter Bei beiden before ist Shared gesetzt
2 Auf der write-Seite erneut Enter Die Seite des schreibenden Prozesses hat Shared 0
3 Danach auf der read-Seite Enter Die reader-Seite liest weiter die Originalseite

Der Type von VirtualQuery bleibt nach dem Schreiben MEM_MAPPED. Bestätigen Sie die Änderung des Teilungszustands und den Type-Wert getrennt.

Nur Ergebnisse interpretieren, bei denen Valid gesetzt ist

PrintPage fasst die Zielseite unmittelbar vor der Abfrage erneut an und interpretiert Shared und ShareCount nicht, wenn Valid == 0, mit bis zu drei Versuchen. Wird sie dann immer noch nicht resident, gibt es kein Ergebnis, und das wird angezeigt. Timing und Speicherdruck können ShareCount ändern, deshalb betrachten Sie nicht einen festen Wert, sondern die Änderung des Shared-Bits, die bei Valid == 1 bestätigt wurde.

10. Fünf Fehllektüren, die Sie in der Praxis vermeiden sollten

10.1. „Gemeinsamer Speicher liegt an derselben virtuellen Adresse“

Geteilt werden Abschnitt und physische Seite. Die virtuelle Adresse der Sicht kann je Prozess verschieden sein, deshalb speichern Sie Offsets, keine Rohzeiger.

10.2. „Dieselbe DLL, also werden alle Seiten unbedingt geteilt“

Saubere Codeseiten teilen sich leicht, Relokation, writable section, CoW und der residente Zustand zum Messzeitpunkt erzeugen jedoch auch Private-Seiten.

10.3. „Nach CoW wird es MEM_PRIVATE“

Der Type von VirtualQuery bleibt MEM_MAPPED oder MEM_IMAGE. Den tatsächlichen Teilungszustand bestätigen Sie mit QueryWorkingSetEx.7

10.4. „Steigen Private Bytes nicht, findet kein CoW statt“

FILE_MAP_COPY erhebt Commit vorab für die ganze Sicht. Priorisieren Sie Private WS und das Shared-Bit.6

10.5. „Gemeinsame Seite, also keine Synchronisation nötig“

Dass dieselbe physische Seite sichtbar ist, und dass mehrere CPU-Kerne sicher aktualisieren können, sind verschiedene Probleme. Entwerfen Sie Atomizität, Speicherordnung, Ausschluss, Zwischenzustand bei Absturz und Versionskompatibilität.

Die Idee, Referenz und Lebensdauer getrennt zu verfolgen, gilt auch für Prozesse, die bei Excel-COM-Interop bleiben. Siehe „Warum EXCEL.EXE-Prozesse nach C#-Excel-COM-Automatisierung bestehen bleiben“.

11. Zusammenfassung

  • Ein Abschnittsobjekt steht für einen teilbaren Speicherbereich, und jeder Prozess ordnet ihn als Sicht in den eigenen virtuellen Raum ein.1
  • Derselbe Offset desselben Abschnitts wird von unterschiedlichen virtuellen Adressen auf dieselbe physische Seite abgebildet.2
  • Ein dateigestützter Abschnitt stützt eine echte Datei, ein auslagerungsdateigestützter Abschnitt unter anderem benannten gemeinsamen Speicher.9
  • EXE/DLL werden als Image-Abschnitt, gewöhnliche Dateien als Datenabschnitt behandelt; Schutz und Ziel des Zurückschreibens unterscheiden sich.3
  • CoW teilt die physische Seite während des Lesens und kopiert beim ersten Schreiben nur diese Seite und tauscht die PTE.5
  • Nach CoW liefert VirtualQuery weiter MEM_MAPPED/MEM_IMAGE, deshalb bestätigen Sie mit dem Shared-Bit von QueryWorkingSetEx.7
  • Bei FILE_MAP_COPY wird Commit für die ganze Sicht vorab erhoben, deshalb können Private Bytes allein CoW nicht festmachen.6
  • Cached I/O des Cache Managers, Datenzuordnung und Image-Zuordnung sind getrennte Pfade über SharedCacheMap, DataSectionObject und ImageSectionObject, verbunden auf demselben Dateistrom.4
  • Erst mit Lebensdauer von Sicht und Handle, Synchronisation, ACL und Offset-Entwurf wird gemeinsamer Speicher sicher.

Damit ist „Die Tiefen des Windows-Speichers“ in drei Teilen abgeschlossen. Virtuelle Adressen reservieren/commiten, per Seitenfehler eine physische Seite erhalten, vom Working Set auf die Seitenlisten wandern, über Abschnitte teilen und nur geschriebene Seiten per CoW verzweigen — die Speicherverwaltung von Windows hängt als dieser eine Fluss zusammen.

Verwandte Artikel

Verwandte Beratungsbereiche

Die KomuraSoft LLC übernimmt Fehleruntersuchungen zu gemeinsamem Speicher, Dateizuordnung, DLL-Laden, Dateisperren, Interprozesskommunikation und Speicherverbrauch in Windows-Anwendungen.

  1. Microsoft Learn, Section Objects and Views. Dazu, dass ein Abschnittsobjekt für einen teilbaren Speicherbereich steht und jeder Prozess einen Teil des Abschnitts als Sicht zuordnet. ↩ ↩2 ↩3

  2. Microsoft Learn, File-Backed and Page-File-Backed Sections. Zu dateigestützten und auslagerungsdateigestützten Abschnitten, CoW und dazu, dass unterschiedliche virtuelle Adressen verschiedener Prozesse denselben physischen Speicher teilen können. ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, CreateFileMappingW function. Zu Dateizuordnungsobjekt, auslagerungsdateigestützt, SEC_IMAGE, Lebensdauer von Sicht und Handle und zur Stimmigkeit von Sichten, die dieselbe Datei stützen. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  4. Microsoft Learn, SECTION_OBJECT_POINTERS structure. Dazu, dass DataSectionObject, SharedCacheMap und ImageSectionObject Zuordnung und Cache-Informationen des Dateistroms an Speicher-Manager und Cache Manager binden. ↩ ↩2 ↩3 ↩4

  5. Microsoft Learn, Memory Protection. Dazu, dass mehrere Prozesse dieselbe physische DLL-Seite teilen und beim Schreiben eines von ihnen auf eine neue physische Seite kopiert und die PTE aktualisiert wird (CoW). ↩ ↩2 ↩3

  6. Microsoft Learn, MapViewOfFileEx function. Zum CoW von FILE_MAP_COPY, dazu, dass private Seiten von der Auslagerungsdatei gestützt werden, Commit charge für die ganze Sicht erhoben wird und Offsets statt virtueller Adressen gespeichert werden sollten. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  7. Microsoft Learn, VirtualQuery function. Dazu, dass Type nach CoW MEM_MAPPED/MEM_IMAGE bleibt und das Shared-Bit von QueryWorkingSetEx die Privatisierung bestätigt. ↩ ↩2 ↩3 ↩4

  8. Microsoft Learn, Managing Memory Sections. Dazu, dass physischer Speicher erst zugewiesen wird, wenn auf die Sicht zugegriffen wird, und der erste Seitenfehler den Dateiinhalt liest. ↩

  9. Microsoft Learn, Sharing Files and Memory. Dazu, dass dasselbe Dateizuordnungsobjekt über Namen und Handle geteilt wird, mit INVALID_HANDLE_VALUE auslagerungsdateigestützter gemeinsamer Speicher entsteht und Synchronisation gesondert nötig ist. ↩ ↩2 ↩3

  10. Microsoft Learn, Process Explorer - Sysinternals. Dazu, dass Process Explorer Handles eines Prozesses sowie geladene DLLs und speicherabgebildete Dateien anzeigen kann. ↩

  11. Microsoft Learn, VMMap - Sysinternals. Dazu, dass VMMap den virtuellen Speicher eines Prozesses in Image, Mapped File, Private und mehr zerlegt und die Aufteilung Private/Shareable des Working Set anzeigt. ↩

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.

Bekommt jeder Prozess, der dieselbe DLL nutzt, eine vollständige Kopie dieser DLL im RAM?
In der Regel nicht. Unveränderte Seiten desselben Images werden von den unterschiedlichen virtuellen Adressen jedes Prozesses auf dieselbe physische Seite abgebildet. Nur Seiten, die geschrieben werden müssen, werden über Copy-on-Write und Ähnliches zu prozesseigenen physischen Seiten.
Weist CreateFileMapping dem Prozess in diesem Moment Speicher zu?
CreateFileMapping erzeugt ein Dateizuordnungsobjekt, aber erst MapViewOfFile macht es im virtuellen Raum des Prozesses sichtbar. Die physischen Seiten der Sicht entstehen normalerweise erst durch einen Seitenfehler ab der ersten angesprochenen Seite.
Worin unterscheiden sich FILE_MAP_WRITE und FILE_MAP_COPY?
Eine Änderung über FILE_MAP_WRITE ist ein Schreiben, das auf der gemeinsamen Dateidatenseite sichtbar wird. FILE_MAP_COPY teilt die Anfangsseiten, aber nur geschriebene Seiten werden zu prozessprivaten Kopien; die Änderungen werden nicht in die Originaldatei zurückgeschrieben und gehen verloren, wenn die Sicht aufgehoben wird.
Liefert VirtualQuery nach Copy-on-Write MEM_PRIVATE?
Nein. Eine Datensicht bleibt MEM_MAPPED und eine Imagesicht bleibt MEM_IMAGE. Ob eine Seite tatsächlich privatisiert wurde, sehen Sie, indem Sie die Seite resident machen und das Shared-Bit von QueryWorkingSetEx prüfen.
Darf man einen Rohzeiger im gemeinsamen Speicher ablegen?
In der Regel nicht. Auch bei demselben Abschnitt ist nicht garantiert, dass die Sicht jedes Prozesses an derselben virtuellen Adresse liegt. In einer gemeinsamen Struktur verwenden Sie Offsets von der Basis, festbreitige Ganzzahlen sowie ein explizites Layout- und Synchronisationsschema.

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