Les profondeurs de la mémoire Windows (partie 3) — Objets section et copie sur écriture : ce que sont vraiment les DLL et le mappage de fichiers

· · Windows, Gestion de la mémoire, Mémoire partagée, Mappage de fichiers, Copie sur écriture, DLL, Cache Manager

Dans l’article précédent, « Les profondeurs de la mémoire Windows (partie 2) — La vie d’une page physique », nous avons suivi comment une page physique qui quitte le Working Set circule entre Modified, Standby, Free et Zeroed. Cette liste Standby contient aussi des pages de DLL, d’EXE, de fichiers mappés et du cache de fichiers.

Une question se pose alors. Quand 100 processus utilisent le même kernel32.dll, Windows place-t-il 100 jeux de pages de code en RAM ? Quand deux processus mappent le même fichier en mémoire, l’autre peut-il utiliser une page que l’un d’eux a lue ?

La réponse est mapper plusieurs adresses virtuelles sur la même page physique. Le centre qui représente cette unité de partage est l’objet section, et le mécanisme qui ne sépare le partage qu’au moment de l’écriture est la copie sur écriture (Copy-on-Write, CoW).

Cet article suit comment les EXE et DLL, les fichiers de données, la mémoire partagée adossée au fichier d’échange et le cache de fichiers s’accrochent au même flux de fichier, et où les chemins de traitement divergent. La lecture des chiffres eux-mêmes prend pour acquis l’article d’introduction « Que représente réellement la « mémoire utilisée » sous Windows ».

« Les profondeurs de la mémoire Windows » — les 3 parties

  1. Partie 1 : Adresses virtuelles et défauts de page
    Nous suivons l’instant où une page virtuelle commise obtient de la RAM physique.
  2. Partie 2 : La vie d’une page physique
    Nous suivons les transitions d’état d’une page qui quitte le Working Set.
  3. Partie 3 (cet article) : Objets section et copie sur écriture
    Nous suivons le mécanisme par lequel DLL, mappages de fichiers et mémoire partagée partagent des pages physiques.

La question à laquelle répond la partie 3 n’est qu’une.

Pourquoi plusieurs processus peuvent-ils utiliser la même DLL ou le même fichier comme un seul jeu de pages physiques ?

Le public visé est constitué des développeurs et des opérateurs qui veulent comprendre le partage de DLL, CreateFileMapping, MapViewOfFile, la mémoire partagée, le CoW et le lien avec le cache de fichiers, non seulement comme usage d’API mais depuis la structure interne. Les prérequis sont Windows 10/11 ou Windows Server actuel, et le bagage requis est les bases des adresses virtuelles, des défauts de page et du Working Set. La difficulté est intermédiaire ; nous utilisons aussi des termes internes comme Control Area et Prototype PTE, mais les observations se reproduisent avec VMMap, Process Explorer et QueryWorkingSetEx.

1. Le fond d’abord

Pour commencer, voici le tableau d’ensemble.

  • Un objet section représente une plage de mémoire partageable.
    Chaque processus mappe une partie de cette section dans son propre espace virtuel comme une « vue ».1
  • Les vues de la même section n’ont pas besoin d’être à la même adresse virtuelle.
    Le 0x000001... du processus A et le 0x000002... du processus B peuvent pointer le même décalage de section et la même page physique.2
  • Il existe des sections adossées à un fichier et adossées au fichier d’échange.
    Les premières utilisent un vrai fichier ; les secondes servent notamment à la mémoire partagée sans fichier explicite.2
  • Charger un EXE/DLL est une section image ; un mappage de fichier ordinaire est une section de données.
    Avec SEC_IMAGE, les attributs de section à l’intérieur du PE déterminent la protection de page.3
  • Pendant les lectures, la même page physique peut être partagée.
    Quand un côté écrit sur une page CoW, seule cette page est copiée et le PTE du processus qui écrit est échangé.4
  • Même sur le même flux de fichier, les chemins cache, données et image divergent.
    Les E/S en cache du Cache Manager utilisent SharedCacheMap, le mappage de données utilise DataSectionObject, et les EXE/DLL utilisent ImageSectionObject. Les trois s’accrochent au même flux de fichier via SECTION_OBJECT_POINTERS, mais un défaut d’image ne passe pas par le Cache Manager.5
  • Les Private Bytes seuls ne permettent pas de confirmer qu’un CoW a eu lieu.
    FILE_MAP_COPY impute le Commit de toute la vue dès le mappage, au cas où chaque page serait plus tard privatisée. Pour une vérification par page, utilisez le bit Shared de QueryWorkingSetEx.67

En une phrase : ce qui est partagé n’est pas l’adresse virtuelle, mais le contenu à l’intérieur de la section et la page physique qui lui correspond à cet instant.

2. Objets section et vues

Dans la définition de Microsoft, un objet section représente une région de mémoire partageable et constitue aussi le mécanisme qui mappe un fichier dans l’espace d’adressage d’un processus.1

L’astuce pour le comprendre est de séparer la section elle-même de la vue que chaque processus voit.

Concept Rôle
Objet section Représente le contenu à partager, la taille, le magasin de soutien et le plafond de protection
Vue Montre une partie de la section comme une plage d’adresses virtuelles dans un processus
PTE Lie chaque page virtuelle de la vue à la page physique actuelle ou à un état non réalisé
PFN Représente une page physique qui existe réellement en RAM

En Win32, CreateFileMapping renvoie un handle vers un objet de mappage de fichier, et MapViewOfFile crée une vue dans l’espace virtuel du processus.3

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

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

Appeler CreateFileMapping seul ne vous donne pas encore une adresse que le processus peut lire. De plus, créer une vue ne met pas immédiatement chaque page en RAM. À partir de la première page touchée, un défaut de page lit le contenu du fichier et lie une page physique au PTE.8 Autrement dit, la pagination à la demande vue dans la partie 1 s’applique aussi aux vues de section.

2.1. La même section, des adresses virtuelles différentes

Même lorsque le processus A et le processus B mappent le même décalage de la même section, l’adresse de début de la vue peut différer.

Mapper des adresses virtuelles différentes sur la même page physiqueLe processus A et le processus B ont chacun une vue à une adresse virtuelle différente, mais ils atteignent la même page physique PFN X via le même décalage de sectionProcessus A : 0x000001A00000 + 0x3000Décalage de section 0x3000Processus B : 0x000002700000 + 0x3000La même page physique PFN X

Figure 1 : Ce qui est partagé, ce sont le contenu à l’intérieur de la section et la page physique, pas l’adresse virtuelle.

C’est pourquoi il ne faut pas stocker un pointeur brut dans la mémoire partagée. La valeur de pointeur du processus A peut être une adresse sans rapport dans le processus B.

Dans une structure partagée, utilisez un décalage depuis le début de la vue, des entiers de largeur fixe, et une version et un alignement explicites. La documentation Microsoft de MapViewOfFileEx recommande aussi de stocker un décalage depuis la base plutôt qu’un pointeur, car rien ne garantit que la même adresse sera disponible plus tard.6

3. Adossé à un fichier et adossé au fichier d’échange

Les sections se scindent en deux grandes sortes selon l’endroit d’où le contenu peut être restauré.

Comment le soutien fichier et le soutien fichier d'échange divergentPasser un vrai fichier à CreateFileMapping produit une section adossée à un fichier, et une page propre peut être relue depuis le fichier d'origine. Passer INVALID_HANDLE_VALUE produit une section adossée au fichier d'échange ; le fichier d'échange soutient le contenu, qui disparaît à la destruction de l'objetPasser un handle de vrai fichierPasser INVALID_HANDLE_VALUECreateFileMappingSection adossée à un fichierSection adossée au fichier d'échangeUne page propre peut être relue depuis le fichier d'origineLe fichier d'échange soutient le contenu, qui disparaît à la destruction

Figure 2 : La différence de magasin de soutien décide d’où le contenu peut être restauré et combien de temps il vit.

3.1. Sections adossées à un fichier

Passer un vrai fichier à CreateFileMapping produit une section adossée à un fichier.

  • Une vue en lecture seule lit les pages nécessaires depuis le fichier.
  • Les modifications d’une vue lecture/écriture sont traitées comme des données de ce fichier.
  • Les modifications d’une vue CoW ne sont pas écrites dans le fichier d’origine ; elles deviennent des pages privées.

Si une page adossée à un fichier est propre, la page physique peut être abandonnée et relue depuis le fichier d’origine. Cette propriété soutient l’efficacité de Standby et du cache de fichiers vue dans la partie 2.

3.2. Sections adossées au fichier d’échange

Passer INVALID_HANDLE_VALUE comme hFile de CreateFileMapping et spécifier une taille produit une section adossée au fichier d’échange.

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

C’est une section sans fichier de données explicite, adossée au fichier d’échange. Le contenu initial est zéro, et plusieurs processus peuvent ouvrir le même objet via un nom, l’héritage de handle, DuplicateHandle, etc.93 Les modifications sont visibles des processus qui mappent la même page partagée. En revanche, quand l’objet section est détruit le contenu ne reste pas, donc ce n’est pas adapté pour laisser un fichier persistant.2

Un point de vigilance : la mémoire partagée n’apporte pas automatiquement l’exclusion mutuelle. Vous concevez à part un mutex, un sémaphore, un événement, un protocole sans verrou, ou l’équivalent.9

4. Mappage d’image et mappage de données

Quand on dit « un EXE ou une DLL est aussi un mappage de fichier », les différences avec un fichier de données ordinaire restent à garder.

Élément Mappage d’image Mappage de données
Usage principal Charger un EXE ou une DLL Fichiers ordinaires, données partagées
Attribut de création SEC_IMAGE PAGE_READONLY, PAGE_READWRITE, etc.
Protection de page Les attributs à l’intérieur de l’image PE décident Le mappage et la spécification de vue décident
Écritures Peuvent être privatisées via une section inscriptible ou le CoW Écriture partagée ou CoW au choix
Type VirtualQuery MEM_IMAGE MEM_MAPPED

Avec SEC_IMAGE, les attributs de section de l’image exécutable elle-même décident de la protection de page de la vue, davantage que la valeur de protection ordinaire passée à CreateFileMapping.3

Par ce mécanisme, les pages non modifiées comme le code peuvent partager la même page physique entre de nombreux processus, et seules les pages qui ont besoin d’un changement privé au processus se séparent via le CoW. Cela dit, toutes les pages d’une DLL ne sont pas nécessairement partagées — à cause de la relocation ASLR, des correctifs du chargeur, du hotpatching, des attributs réels des sections PE, etc.

La conception importante est partager d’abord les pages partageables, et ne copier tardivement que les pages qui ont besoin d’un changement.

5. La copie sur écriture de bout en bout

Suivons depuis l’état où deux processus lisent la même page CoW jusqu’à ce que le processus A écrive un octet.

5.1. Avant l’écriture

Avant qu’une écriture n’ait lieu, les PTE des deux processus atteignent conceptuellement la même page partagée, et les lectures réussissent telles quelles.

État partagé avant la copie sur écritureAvant qu'une écriture n'ait lieu, le PTE du processus A et le PTE du processus B atteignent tous deux la même page partagée PFN X, et les lectures réussissent telles quellesPTE du processus APFN X partagé(lecture / copie sur écriture)PTE du processus B

Figure 3 : Avant l’écriture, les PTE des deux processus pointent vers la même page physique.

5.2. Un défaut de protection à l’écriture

Une page CoW n’est pas dès le départ une page partagée ordinaire en écriture. Quand le processus A tente d’écrire, le CPU lève un défaut de protection. Le gestionnaire de mémoire qui reçoit le contrôle juge que ce n’est pas une écriture illégale mais une écriture sur un attribut CoW.

5.3. Créer une nouvelle page physique

Sur ce jugement, Windows fait ce qui suit.

  1. Obtenir une page physique pour le processus A.
  2. Copier le contenu de PFN X vers la nouvelle page PFN Y.
  3. Échanger le PTE du processus A vers PFN Y.
  4. Changer la protection du processus A en lecture/écriture ordinaire.
  5. Réexécuter l’instruction d’écriture qui a échoué.
État scindé après la copie sur écritureAprès l'écriture du processus A, seul le PTE du processus A est échangé vers la page privée PFN Y qui a reçu une copie du contenu, tandis que le PTE du processus B continue de pointer la page partagée d'origine PFN XCopié à l'écriturePTE du processus APFN Y privé(R/W, après écriture)PTE du processus BPFN X partagé(origine)

Figure 4 : Seul le PTE du processus qui écrit est échangé vers une nouvelle page privée ; l’autre côté continue de lire le contenu d’origine.

Le processus B continue de lire le contenu d’origine et ne voit pas la modification du processus A. C’est la copie sur écriture. Le partage de DLL et FILE_MAP_COPY utilisent le même principe : ne pas copier tant que vous n’écrivez pas.46

5.4. La différence avec FILE_MAP_WRITE

Une page écrite via une écriture partagée avec FILE_MAP_WRITE est conçue pour qu’une modification d’un côté soit aussi visible depuis une autre vue qui utilise le même mappage de fichier. Avec FILE_MAP_COPY, au contraire, seules les pages écrites deviennent privées au processus ; les modifications ne sont pas réécrites dans le fichier d’origine et sont perdues quand la vue est démapée.6

Voulez-vous « communiquer une mise à jour via la mémoire partagée », ou « que chaque processus fasse des modifications privées à partir de données initiales communes » ? Le bon choix est l’opposé selon le but.

6. Après le CoW, cela reste MEM_MAPPED / MEM_IMAGE

Une page après CoW est devenue physiquement Private. On s’attendrait alors à ce que le Type de VirtualQuery passe aussi à MEM_PRIVATE, mais en réalité une vue de données reste MEM_MAPPED et une image exécutable reste MEM_IMAGE. VirtualQuery indique de quelle allocation initiale la région vient.7

Pour voir par page si le CoW a déjà eu lieu, utilisez la procédure suivante.

  1. Accéder à la page cible et la rendre résidente.
  2. Obtenir les informations Working Set de la page avec QueryWorkingSetEx.
  3. Examiner le bit Shared.
  4. Si Shared == 0, cette page résidente est Private.

En confirmant aussi dans VMMap, regardez non seulement le Type de la région mais la ventilation Private/Shareable du Working Set.

6.1. Les Private Bytes peuvent ne pas augmenter

Avec FILE_MAP_COPY, le processus pourra plus tard écrire sur chaque page de la vue. Windows prend donc une charge de Commit équivalente à toute la vue au moment du mappage.6 En conséquence, écrire la première page n’augmente pas nécessairement les Private Bytes de 4 KiB à cet instant.

Les indicateurs à privilégier pour observer le CoW sont les suivants.

  • Le bit Shared de QueryWorkingSetEx
  • Private WS / Shareable WS de VMMap
  • Les informations de page physique de RAMMap
  • Les Private Bytes comme information complémentaire

Si vous traitez « les Private Bytes ont-ils augmenté au moment de l’écriture » comme le test de réussite, vous manquerez un CoW qui fonctionne correctement.

7. Le point de contact avec le Cache Manager — séparer trois chemins

Dire « le chargement EXE/DLL, le cache de fichiers et la mémoire partagée sont tous des sections » donne le tableau d’ensemble, mais il ne faut pas écraser l’implémentation en un seul objet.

Un flux de fichier a des SECTION_OBJECT_POINTERS, utilisés par le gestionnaire de mémoire et le Cache Manager.

typedef struct _SECTION_OBJECT_POINTERS {
    PVOID DataSectionObject;
    PVOID SharedCacheMap;
    PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;
  • DataSectionObject : état de section pour un fichier de données
  • SharedCacheMap : la vue de cache que le Cache Manager suit
  • ImageSectionObject : état de section pour une image exécutable

La documentation Microsoft explique que cette structure lie un objet fichier aux sections du flux de fichier et suit le contenu en mémoire et les informations de cache.5

Ici la série I/O et la série mémoire se rejoignent. Gardez toutefois les trois chemins séparés pour les comprendre.

  • ReadFile / WriteFile en cache utilise le SharedCacheMap et la vue de cache du Cache Manager.
  • Un défaut de mappage sur un fichier de données est traité par le gestionnaire de mémoire du côté DataSectionObject. Il coopère avec les E/S en cache sur le même flux de fichier pour garder le contenu cohérent.
  • Un défaut d’image sur un EXE/DLL est traité par le gestionnaire de mémoire avec ImageSectionObject et les E/S de pagination. Ce n’est pas un chemin qui passe par le SharedCacheMap du Cache Manager.
Trois chemins qui s'accrochent au même flux de fichierReadFile/WriteFile en cache utilise SharedCacheMap, un défaut de mappage de données utilise DataSectionObject, et un défaut d'image EXE/DLL utilise ImageSectionObject ; les trois s'accrochent au même flux de fichier via SECTION_OBJECT_POINTERSReadFile / WriteFile en cacheSharedCacheMapDéfaut de mappage de donnéesDataSectionObjectDéfaut d'image EXE/DLLImageSectionObjectLe même flux de fichier(SECTION_OBJECT_POINTERS)

Figure 5 : Les trois chemins sont traités séparément, mais ils s’accrochent sur le même flux de fichier.

Ce que les trois ont en commun n’est pas que « tout entre dans le Cache Manager », mais que le même flux de fichier lie des états séparés — cache, section de données et section d’image — via SECTION_OBJECT_POINTERS.5 Les lectures et écritures en cache, le Lazy Writer et la relation entre Cc et Mm sont traités dans « Les profondeurs de l’E/S Windows (épisode 4) — Le gestionnaire de cache : quand votre WriteFile atteint-il vraiment le disque ? ».

Notez que lorsque vous mélangez une vue mappée en mémoire avec ReadFile/WriteFile, rien ne garantit que vous voyiez toujours le contenu du même instant. La conception doit inclure la synchronisation, le flush et le mode de partage de fichier.36

8. Durée de vie de l’objet et de la vue

Fermer le handle CreateFileMapping seul ne détruit pas une vue existante. Une vue tient une référence interne à la section, et ce n’est qu’après que chaque vue a été UnmapViewOfFile et chaque handle CloseHandle que l’objet devient destructible.3

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

Cette séparation des durées de vie est une cause du phénomène « j’ai fermé le fichier, mais il est encore en cours d’utilisation ». Même après avoir fermé le handle de fichier, si une section image ou une vue de données se réfère encore au flux de fichier, la fermeture finale du fichier vient plus tard.

La relation avec le nettoyage/fermeture côté I/O est traitée dans « Les profondeurs de l’I/O Windows (partie 1) », et les pièges d’implémentation dans « Pièges de la mémoire partagée et bonnes pratiques concrètes ».

9. Voyez-le vous-même

9.1. Regarder la même DLL depuis deux processus

D’abord, confirmez le partage de DLL avec des processus existants.

  1. Démarrez Process Explorer en tant qu’administrateur.
  2. Démarrez deux processus cmd.exe.
  3. Choisissez View > Lower Pane View > DLLs.
  4. Confirmez le chemin et le mappage de la même DLL dans les deux processus.
  5. Ouvrez chaque cmd.exe dans VMMap et comparez Images Working Set, Private et Shareable.

Voir la même DLL dans Process Explorer est la preuve que les deux ont mappé la même image. Cela seul, cependant, ne prouve pas que le PFN de chaque page correspond. Combinez la ventilation Shareable de VMMap, RAMMap et QueryWorkingSetEx pour confirmer le partage par page. Process Explorer et VMMap sont fournis par Sysinternals.1011

9.2. Observer FILE_MAP_COPY depuis deux processus

Le programme suivant mappe le même fichier comme une vue CoW et affiche le bit Shared de QueryWorkingSetEx. L’objet de mappage de fichier est créé avec PAGE_READONLY, mais cette protection est compatible avec une vue FILE_MAP_COPY, et la première écriture côté vue provoque le CoW.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);
}

Compilez et préparez.

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

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

Puis ouvrez le même fichier depuis deux consoles.

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

Après avoir démarré les deux, appuyez sur Entrée d’abord du côté lecture puis du côté écriture, et confirmez que Shared est défini dans before des deux côtés. Appuyez encore une fois sur Entrée du côté écriture et la page de ce processus devient Shared 0. Puis appuyez sur Entrée du côté lecture et vous confirmez que le lecteur continue de lire la page d’origine. Le Type de VirtualQuery reste MEM_MAPPED après l’écriture aussi.

Notez que PrintPage retouche la page cible juste avant l’interrogation, et si Valid == 0 il n’interprète pas Shared et ShareCount et réessaie jusqu’à trois fois. Si la page n’est toujours pas résidente, il ne produit pas de résultat et le signale. ShareCount peut changer avec le timing et la pression mémoire, donc regardez le changement du bit Shared confirmé à Valid == 1, pas une valeur fixe.

10. Cinq lectures erronées à éviter en pratique

10.1. « La mémoire partagée aboutit à la même adresse virtuelle »

Ce qui est partagé, c’est la section et la page physique. L’adresse virtuelle de la vue peut différer par processus, donc stockez un décalage plutôt qu’un pointeur brut.

10.2. « Si c’est la même DLL, chaque page est nécessairement partagée »

Les pages de code propres sont faciles à partager, tandis que la relocation, une section inscriptible, le CoW et la résidence au moment de la mesure produisent aussi des pages Private.

10.3. « Après le CoW, cela devient MEM_PRIVATE »

Le Type de VirtualQuery reste MEM_MAPPED ou MEM_IMAGE. Confirmez l’état de partage réel avec QueryWorkingSetEx.7

10.4. « Si les Private Bytes n’ont pas augmenté, le CoW n’a pas eu lieu »

FILE_MAP_COPY impute le Commit de toute la vue d’avance. Privilégiez Private WS et le bit Shared.6

10.5. « Si la page est partagée, la synchronisation est inutile »

Voir la même page physique et pouvoir la mettre à jour en toute sécurité depuis plusieurs cœurs CPU sont des problèmes différents. Concevez l’atomicité, l’ordre mémoire, l’exclusion mutuelle, l’état intermédiaire en cas de plantage et la compatibilité de version.

L’idée de suivre séparément les références et la durée de vie s’applique aussi au problème d’un processus qui reste après l’interop COM Excel. Voir aussi « Le problème d’EXCEL.EXE qui reste actif lors de la manipulation d’Excel en C# — schémas de libération des références COM et décision de remplacement ».

11. Résumé

  • Un objet section représente une plage de mémoire partageable, et chaque processus la mappe dans son propre espace virtuel comme une vue.1
  • Le même décalage de la même section est mappé depuis des adresses virtuelles différentes sur la même page physique.2
  • Une section adossée à un fichier soutient un vrai fichier ; une section adossée au fichier d’échange soutient notamment la mémoire partagée nommée.9
  • Un EXE/DLL est traité comme une section image et un fichier ordinaire comme une section de données ; la protection et la destination de réécriture diffèrent.3
  • Le CoW partage la page physique pendant les lectures et, à la première écriture, copie seulement cette page et échange le PTE.4
  • Après le CoW, VirtualQuery renvoie encore MEM_MAPPED/MEM_IMAGE, donc vous confirmez avec le bit Shared de QueryWorkingSetEx.7
  • Avec FILE_MAP_COPY, le Commit de toute la vue est facturé d’abord, donc vous ne pouvez pas juger le CoW d’après les Private Bytes seuls.6
  • Les E/S en cache du Cache Manager, le mappage de données et le mappage d’image s’accrochent sur le même flux de fichier comme des chemins séparés qui utilisent respectivement SharedCacheMap, DataSectionObject et ImageSectionObject.5
  • La mémoire partagée ne devient sûre que lorsque vous incluez la durée de vie des vues et des handles, la synchronisation, les ACL et la conception des décalages.

Cela termine les trois parties de « Les profondeurs de la mémoire Windows ». Vous réservez/commitez une adresse virtuelle, obtenez une page physique par un défaut de page, déplacez la page du Working Set vers une liste de pages, la partagez via une section, et ne scindez via le CoW que les pages que vous avez écrites — la gestion de la mémoire Windows est reliée comme ce flux unique.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge les investigations de défauts portant sur la mémoire partagée, le mappage de fichiers, le chargement de DLL, le verrouillage de fichiers, la communication inter-processus et l’usage mémoire des applications Windows.

Références

  1. Microsoft Learn, Section Objects and Views. Sur un objet section qui représente une plage de mémoire partageable, et chaque processus qui mappe une partie de la section comme une vue.  2 3

  2. Microsoft Learn, File-Backed and Page-File-Backed Sections. Sur les sections adossées à un fichier et au fichier d’échange, le CoW, et la possibilité de partager la même mémoire physique depuis les adresses virtuelles de processus différents.  2 3 4

  3. Microsoft Learn, CreateFileMappingW function. Sur l’objet de mappage de fichier, les sections adossées au fichier d’échange, SEC_IMAGE, la durée de vie des vues et des handles, et la cohérence entre vues qui adossent le même fichier.  2 3 4 5 6 7 8

  4. Microsoft Learn, Memory Protection. Sur plusieurs processus qui partagent les pages physiques de la même DLL, et le CoW qui copie vers une nouvelle page physique et met à jour le PTE quand un côté écrit.  2 3

  5. Microsoft Learn, SECTION_OBJECT_POINTERS structure. Sur DataSectionObject, SharedCacheMap et ImageSectionObject qui lient le mappage d’un flux de fichier et les informations de cache au gestionnaire de mémoire / Cache Manager.  2 3 4

  6. Microsoft Learn, MapViewOfFileEx function. Sur le CoW avec FILE_MAP_COPY ; les pages privées adossées au fichier d’échange ; la facturation du Commit pour toute la vue ; et le stockage d’un décalage plutôt que d’une adresse virtuelle.  2 3 4 5 6 7 8

  7. Microsoft Learn, VirtualQuery function. Sur le Type qui reste MEM_MAPPED/MEM_IMAGE après le CoW, et la possibilité de confirmer la privatisation avec le bit Shared de QueryWorkingSetEx 2 3 4

  8. Microsoft Learn, Managing Memory Sections. Sur le fait que la mémoire physique n’est pas assignée tant que la vue n’est pas accédée, et que le défaut de page du premier accès lit le contenu du fichier. 

  9. Microsoft Learn, Sharing Files and Memory. Sur le partage du même objet de mappage de fichier par nom ou handle ; la création de mémoire partagée adossée au fichier d’échange avec INVALID_HANDLE_VALUE ; et la nécessité d’une synchronisation à part.  2 3

  10. Microsoft Learn, Process Explorer - Sysinternals. Sur Process Explorer qui peut afficher les handles d’un processus et les DLL / fichiers mappés en mémoire chargés. 

  11. Microsoft Learn, VMMap - Sysinternals. Sur VMMap qui décompose la mémoire virtuelle d’un processus en Image, Mapped File, Private, etc., et affiche la ventilation Private/Shareable du Working Set. 

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Chaque processus qui utilise la même DLL obtient-il une copie complète de cette DLL en RAM ?
En général, non. Les pages non modifiées de la même image sont mappées, depuis les adresses virtuelles différentes de chaque processus, sur la même page physique. Seules les pages qui doivent être écrites deviennent des pages physiques privées au processus, via la copie sur écriture notamment.
CreateFileMapping alloue-t-il de la mémoire au processus à cet instant ?
CreateFileMapping crée un objet de mappage de fichier, mais c'est MapViewOfFile qui le rend visible dans l'espace virtuel du processus. De plus, les pages physiques de la vue sont normalement matérialisées par un défaut de page à partir de la première page accédée.
Quelle est la différence entre FILE_MAP_WRITE et FILE_MAP_COPY ?
Une modification via FILE_MAP_WRITE est une écriture répercutée du côté des données de fichier partagées. FILE_MAP_COPY partage les pages initiales, mais seules les pages écrites deviennent des copies privées au processus ; les modifications ne sont pas réécrites dans le fichier d'origine et sont perdues quand la vue est démapée.
Après une copie sur écriture, VirtualQuery renvoie-t-il MEM_PRIVATE ?
Non. Une vue de données reste MEM_MAPPED et une vue d'image reste MEM_IMAGE. Pour voir si une page a réellement été privatisée, rendez la page résidente et examinez le bit Shared de QueryWorkingSetEx.
Peut-on stocker un pointeur brut dans la mémoire partagée ?
En général, non. Même avec la même section, rien ne garantit que la vue de chaque processus soit placée à la même adresse virtuelle. Dans une structure partagée, utilisez des décalages depuis la base, des entiers de largeur fixe, et un schéma explicite de disposition et de synchronisation.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog