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

· Mis à jour le: · · Windows, Gestion de la mémoire, Mémoire partagée, Mappage de fichiers, Copie à l'écriture, DLL, Cache Manager

Historique des révisions (première version, publiée le 20 Aug 2026)
Première publication
Citer cet article(DOI (archive enregistrée): 10.5281/zenodo.22176177)

Les DOI ci-dessous renvoient à des versions déjà archivées et peuvent différer du texte actuel. Pour citer le texte actuel, utilisez l’URL de cette page.

Go Komura (2026). Les profondeurs de la mémoire Windows (partie 3) — Objets section et copie à l'écriture : ce que sont vraiment les DLL et le mappage de fichiers. KomuraSoft LLC. https://comcomponent.com/fr/blog/windows-memory-internals-section-copy-on-write/

DOI (archive enregistrée)
10.5281/zenodo.22176177
DOI (dernière version enregistrée)
10.5281/zenodo.22176178

« Si 100 processus utilisent le même kernel32.dll, faut-il 100 jeux de pages de code ? » La partie 3 part de la question qui se pose lorsque plusieurs processus utilisent les mêmes contenus. Lorsque deux processus mappent le même fichier en mémoire, une page lue par l’un peut-elle être utilisée par l’autre ?

La réponse tient à un mécanisme qui mappe des adresses virtuelles différentes sur la même page physique. Au centre de l’unité de partage se trouve l’objet section. Et le mécanisme qui ne privatise, au moment de l’écriture, que les pages qui en ont besoin, c’est la copie à l’écriture (Copy-on-Write, CoW).

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 qui quitte le Working Set circule entre Modified, Standby, Free et Zeroed. Standby conserve aussi des pages de DLL, d’EXE, de fichiers mappés et du cache de fichiers. Cette fois, nous ajoutons le point de vue du partage entre plusieurs processus.

Nous regardons tour à tour 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, puis nous distinguons les chemins cache, données et image sur le même flux de fichier. Pour la lecture des chiffres, l’article d’introduction « Que représente réellement la « mémoire utilisée » sous Windows » est présupposé.

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

Cette série avance dans l’ordre obtenir une page physique → suivre la résidence et la récupération → comprendre le partage et la privatisation.

Partie Thème Ce que cette partie suit
Partie 1 Adresses virtuelles et défauts de page Quand une région allouée avec VirtualAlloc obtient de la RAM physique
Partie 2 La vie d’une page physique Les transitions d’état d’une page qui quitte le Working Set, et le rôle du fichier d’échange
Partie 3 (cet article) Objets section et copie à l’écriture Comment les DLL, les mappages de fichiers et la mémoire partagée partagent des pages physiques

La partie 3 ne répond qu’à une question.

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

Avant de commencer Détails
Public visé Développeurs et opérateurs qui veulent comprendre, depuis les structures internes, comment le partage de DLL, CreateFileMapping, MapViewOfFile, la mémoire partagée, CoW et le cache de fichiers se relient
Environnement Windows 10/11 ou un Windows Server actuel
Prérequis Bases des adresses virtuelles, des défauts de page et du Working Set
Difficulté Intermédiaire. Des termes internes comme Control Area et Prototype PTE apparaissent aussi

Pour l’observation, nous utilisons VMMap, Process Explorer et QueryWorkingSetEx.

Dans le diagramme, un trait continu marque une relation qui vaut toujours et un trait pointillé une relation conditionnelle (les conditions figurent dans l’explication de chaque relation sur la page de détail). La liste complète des relations (19 au total, avec preuve et niveau de certitude) et les définitions des concepts principaux sont rassemblées sur la page de détail de la carte des connaissances (en japonais). Données : JSON-LD / Turtle

1. D’abord la conclusion

Le tableau d’ensemble se résume aux trois points suivants.

  1. Séparez les contenus partagés de l’adresse que chaque processus voit. Un objet section représente une plage de mémoire partageable, et chaque processus en mappe une partie comme une vue. Même lorsqu’ils utilisent le même décalage de section, les adresses virtuelles des vues n’ont pas à coïncider.12
  2. Séparez ce qui soutient les contenus du chemin par lequel on les utilise. Certaines sections sont adossées à un vrai fichier, d’autres au fichier d’échange. Un EXE/DLL est une section image ; un mappage de fichier ordinaire est une section de données. Avec SEC_IMAGE, la protection de page est décidée par les attributs à l’intérieur du PE. Les chemins cache, données et image ne doivent pas non plus être amalgamés.234
  3. Confirmez la privatisation CoW par l’état de partage par page. Les pages sont partagées pendant la lecture ; seule une page écrite est copiée, et le PTE de ce processus est remplacé. Parce que FILE_MAP_COPY facture le Commit pour la vue entière d’avance, Private Bytes à lui seul ne peut pas établir que CoW a eu lieu. Utilisez le bit Shared de QueryWorkingSetEx pour le confirmer.567

En une phrase : ce qui est partagé, ce n’est pas l’adresse virtuelle, mais les contenus à l’intérieur de la section et les pages physiques qui leur correspondent à cet instant.

Ce que vous voulez savoir Sections à lire
Comment sections, vues et magasins de soutien se relient Sections 2 à 3
Le partage de DLL et CoW à l’écriture Sections 4 à 6
La différence avec le Cache Manager, et pourquoi les choses restent utilisées après fermeture Sections 7 à 8
Confirmer le partage et la privatisation avec deux processus Sections 9 à 10

2. Objets section et vues

Dans la définition de Microsoft, un objet section représente une région de mémoire qui peut être partagée, et c’est aussi le mécanisme qui mappe un fichier dans l’espace d’adressage d’un processus.1

La clé pour le comprendre est de penser l’objet section lui-même et la vue que chaque processus voit comme des choses distinctes.

Concept Rôle
Objet section Représente les contenus partagés, la taille, le magasin de soutien et la borne supérieure de protection
Vue Expose 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 pas encore matérialisé
PFN Représente une page physique qui existe réellement en RAM

L’API Win32 se lit selon la même répartition des rôles.3

Étape Ce que vous obtenez
CreateFileMapping Un handle vers un objet de mappage de fichier
MapViewOfFile Une vue placée dans l’espace d’adressage virtuel du processus
Premier accès à la vue La page accédée liée à la mémoire physique par un défaut de page

Commençons par un exemple qui mappe un fichier en lecture seule.

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

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

Appeler CreateFileMapping seul ne donne pas encore au processus une adresse qu’il peut lire. Et créer une vue n’amène pas non plus immédiatement toutes les pages en RAM. À partir de la première page touchée, les défauts de page lisent le contenu du fichier et lient des pages physiques aux PTE.8 Autrement dit, la pagination à la demande vue dans la partie 1 s’applique aux vues de section telle quelle.

2.1. Même section, 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, les adresses de début de leurs vues peuvent 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 tous deux 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 + 0x3000Même page physique PFN X

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

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

Dans une structure partagée, utilisez des décalages depuis le début de la vue, des entiers de largeur fixe, une version explicite et un alignement explicite. La documentation Microsoft de MapViewOfFileEx recommande aussi de stocker des décalages depuis la base plutôt que des pointeurs, parce que rien ne garantit que la même adresse sera disponible à l’avenir.6

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

Les sections se divisent en deux grandes sortes selon l’endroit d’où leurs contenus peuvent être restaurés.

Comment les sections adossées à un fichier et au fichier d'échange divergentPasser un vrai fichier à CreateFileMapping produit une section adossée à un fichier dont les pages propres peuvent être relues depuis le fichier d'origine. Passer INVALID_HANDLE_VALUE produit une section adossée au fichier d'échange dont les contenus sont soutenus par le fichier d'échange et disparaissent quand l'objet est détruitPasser un handle vers un vrai fichierPasser INVALID_HANDLE_VALUECreateFileMappingSection adossée à un fichierSection adossée au fichier d'échangeLes pages propres peuvent être relues depuis le fichier d'origineLes contenus sont soutenus par le fichier d'échange et disparaissent à la destruction

Figure 2 : La différence de magasin de soutien décide d’où les contenus peuvent être restaurés et combien de temps ils vivent.

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 dont elle a besoin depuis le fichier.
  • Les modifications faites via une vue en lecture/écriture sont traitées comme des données de ce fichier.
  • Les modifications faites via 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 jetée et relue plus tard depuis le fichier d’origine. Cette propriété est ce qui soutient l’efficacité de Standby et du cache de fichiers vus 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");

Cette section n’a pas de fichier de données explicite ; ses contenus sont soutenus par le fichier d’échange. Le contenu initial est zéro. Pour utiliser le même objet depuis plusieurs processus, utilisez un nom, l’héritage de handle, DuplicateHandle, ou équivalent.93

Les modifications d’une page partagée sont visibles, mais ce n’est pas un stockage persistant. Les processus qui mappent la même page partagée peuvent voir les modifications. En revanche, une fois l’objet section détruit, les contenus ne survivent pas, donc ce n’est pas adapté à se substituer à un fichier persistant.2

Une prudence : la mémoire partagée n’apporte pas d’exclusion mutuelle intégrée. Un mutex, un sémaphore, un événement, un protocole lock-free ou équivalent doit être conçu à part.9

4. Mappage image et mappage de données

Lorsque l’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 en vue.

Élément Mappage image Mappage de données
Usage principal Chargement des EXE et DLL Fichiers ordinaires, données partagées
Attribut de création SEC_IMAGE PAGE_READONLY, PAGE_READWRITE, etc.
Protection de page Décidée par les attributs à l’intérieur de l’image PE Décidée par ce que le mappage et la vue spécifient
Écritures Peuvent être privatisées via une section inscriptible ou CoW On peut choisir l’écriture partagée ou CoW
Type VirtualQuery MEM_IMAGE MEM_MAPPED

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

4.1. Toutes les pages d’une DLL ne sont pas forcément partagées

Les pages qui ne changent jamais, comme le code, peuvent partager la même page physique entre de nombreux processus. Les pages qui ont besoin de modifications propres au processus divergent via CoW.

Cela dit, la relocalisation ASLR, les correctifs du chargeur, le hotpatching, les attributs de section PE réels et d’autres facteurs ont tous un effet. Même lorsque la même DLL est chargée, toutes les pages ne sont pas forcément partagées.

Ce qui compte, c’est la conception : partager d’abord les pages partageables, et ne copier paresseusement que les pages qui ont besoin de modifications.

5. La copie à l’écriture de bout en bout

Suivons la séquence depuis l’état où deux processus lisent la même page CoW jusqu’au moment où seul le processus A écrit un octet.

5.1. Avant l’écriture

Avant toute écriture, les PTE des deux processus atteignent conceptuellement la même page partagée, et les lectures réussissent simplement.

État partagé avant la copie à l'écritureAvant toute écriture, 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 simplementPTE du processus APFN X partagé (lecture / copie à l'é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, pour commencer, une page partagée inscriptible ordinaire. Lorsque le processus A tente d’écrire, le CPU lève un défaut de protection. Le gestionnaire de mémoire, en recevant le contrôle, détermine que ce n’est pas une écriture illégale mais une écriture vers une page avec l’attribut CoW.

5.3. Créer une nouvelle page physique

Ayant fait cette détermination, 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. Remplacer le PTE du processus A pour pointer vers PFN Y.
  4. Changer la protection du côté du processus A en lecture/écriture ordinaire.
  5. Réexécuter l’instruction d’écriture qui a échoué.
État divergé après la copie à l'écritureEn réponse à l'écriture du processus A, seul le PTE du processus A est remplacé 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 vers la page partagée d'origine PFN XContenu copié au moment de l'écriturePTE du processus APFN Y privé (lecture/écriture, après la modification)PTE du processus BPFN X partagé (contenu d'origine)

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

Le processus B continue de lire le contenu d’origine et ne voit jamais la modification du processus A. C’est la copie à l’écriture. Le partage de DLL et FILE_MAP_COPY utilisent tous deux le même principe : ne pas copier tant qu’une écriture n’a pas lieu.56

5.4. La différence avec FILE_MAP_WRITE

Le critère de choix est si vous voulez que l’autre côté voie vos écritures, ou que les modifications soient à vous seuls.6

Vue Ce qui se passe après une écriture
FILE_MAP_WRITE Comme écriture partagée, la modification est aussi visible depuis d’autres vues qui utilisent le même mappage de fichier
FILE_MAP_COPY 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

Voulez-vous « propager les mises à jour via la mémoire partagée », ou « que chaque processus fasse ses propres modifications privées à partir de données initiales communes » ? Selon le but, le choix est exactement l’opposé.

6. Toujours MEM_MAPPED / MEM_IMAGE après CoW

Ici nous séparons d’où une région vient de l’état de partage actuel de ses pages physiques.

Une page après CoW est physiquement privée, mais le Type de VirtualQuery ne passe pas à MEM_PRIVATE. Une vue de données reste MEM_MAPPED, et une image exécutable reste MEM_IMAGE. C’est parce que Type indique de quelle allocation initiale la région est issue.7

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

  1. Accédez à la page cible pour qu’elle devienne résidente.
  2. Obtenez les informations de Working Set de la page avec QueryWorkingSetEx.
  3. Regardez le bit Shared.
  4. Si Shared == 0, cette page résidente est privée.

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

6.1. Private Bytes peut ne pas augmenter

FILE_MAP_COPY ne privatise que les pages qui sont écrites. Quand le Commit est facturé, toutefois, est une autre affaire.

Pour se préparer à la possibilité que chaque page de la vue soit écrite à l’avenir, Windows facture un Commit équivalent à la vue entière au moment du mappage.6 Donc Private Bytes n’augmente pas forcément de 4 Kio à l’instant où la première page est écrite.

Les indicateurs à prioriser pour observer CoW sont les suivants.

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

Si votre seul critère de réussite/échec est « Private Bytes a-t-il augmenté au moment de l’écriture », vous manquerez CoW qui fonctionne correctement.

7. Où intervient le Cache Manager — séparer trois chemins

Dire « le chargement d’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 une structure SECTION_OBJECT_POINTERS utilisée par le gestionnaire de mémoire et le Cache Manager.

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

Cette structure lie un objet fichier aux sections d’un flux de fichier et suit les contenus en mémoire et les informations de cache.4 L’état que chaque champ pointe, et le chemin de traitement, sont toutefois séparés.

Chemin Champ correspondant Rôle dans le traitement
ReadFile / WriteFile mis en cache SharedCacheMap Le Cache Manager utilise des vues de cache
Défaut de mappage sur un fichier de données DataSectionObject Le gestionnaire de mémoire le traite du côté de la section de données et se coordonne avec les E/S mises en cache sur le même flux de fichier pour garder les contenus cohérents
Défaut d’image sur un EXE/DLL ImageSectionObject Le gestionnaire de mémoire le traite avec la section image et les E/S de pagination. Ce chemin ne passe pas par SharedCacheMap

Le lien avec la série E/S, c’est que ces trois-là sont liés sur le même flux de fichier. Ne lisez pas cela comme « chaque chemin passe par le Cache Manager ».

Trois chemins liés au même flux de fichierReadFile/WriteFile mis en cache utilise SharedCacheMap, un défaut de mappage de données utilise DataSectionObject, et un défaut d'image EXE/DLL utilise ImageSectionObject, et les trois sont liés au même flux de fichier via SECTION_OBJECT_POINTERSReadFile / WriteFile mis en cacheSharedCacheMapDéfaut de mappage de donnéesDataSectionObjectDéfaut d'image EXE/DLLImageSectionObjectMême flux de fichier (SECTION_OBJECT_POINTERS)

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

Ce que les trois ont en commun, ce n’est pas que « tout entre dans le Cache Manager » ; c’est que le même flux de fichier relie des états séparés, le cache, la section de données et la section image, via SECTION_OBJECT_POINTERS.4 Les lectures et écritures de cache, le Lazy Writer, et la relation entre Cc et Mm sont traités dans « Les profondeurs de l’E/S Windows (partie 4) — Le gestionnaire de cache et WriteFile ».

Notez que lorsqu’une vue mappée en mémoire est mélangée avec ReadFile/WriteFile, rien ne garantit que les deux voient toujours les contenus du même instant. La conception doit inclure la synchronisation, le vidage et le mode de partage du fichier.36

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

Fermer un handle et démonter une vue sont des choses distinctes. Fermer le handle de CreateFileMapping seul ne retire pas les vues existantes.

Une vue conserve une référence interne à la section. Ce n’est qu’après que chaque vue a été démontée avec UnmapViewOfFile et chaque handle fermé avec CloseHandle que l’objet devient éligible à la destruction.3

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

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

Pour la relation avec le nettoyage/fermeture côté E/S, voir « Les profondeurs de l’E/S Windows (partie 1) » ; pour les pièges d’implémentation, voir « Pièges de la mémoire partagée et bonnes pratiques concrètes ».

9. Voyez-le par vous-même

9.1. Regarder la même DLL dans 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 instances de cmd.exe.
  3. Choisissez Affichage > Vue du volet inférieur > DLL.
  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 les chiffres Working Set, Private et Shareable pour Images.

Voir la même DLL seule ne prouve pas que les PFN coïncident

Voir la même DLL dans Process Explorer est la preuve que les deux processus ont mappé la même image. À elle seule, toutefois, elle ne prouve pas que le PFN de chaque page coïncide. Combinez la ventilation Shareable dans VMMap, RAMMap et QueryWorkingSetEx pour confirmer le partage à la granularité de la page. Process Explorer et VMMap sont fournis par Sysinternals.1011

9.2. Observer FILE_MAP_COPY dans 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 du côté de la vue déclenche 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);
}

Le compiler et ouvrir le même fichier dans deux processus

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

Séparer l’ordre d’appui sur Entrée et les valeurs à regarder

Ordre Action Ce qu’il faut confirmer
1 Démarrer les deux, puis appuyer sur Entrée d’abord du côté lecture puis du côté écriture Shared est positionné dans before des deux côtés
2 Appuyer encore une fois sur Entrée du côté écriture La page du processus qui écrit a maintenant Shared 0
3 Puis appuyer sur Entrée du côté lecture Le lecteur continue de lire la page d’origine

Le Type de VirtualQuery reste MEM_MAPPED même après l’écriture. Vérifiez le changement d’état de partage et la valeur de Type séparément.

N’interprétez que les résultats où Valid est positionné

Notez que PrintPage retouche la page cible immédiatement avant d’interroger, et si Valid == 0 il réessaie jusqu’à trois fois sans interpréter Shared et ShareCount. Si la page n’est toujours pas résidente, il ne produit pas de résultat et le dit. Parce que ShareCount peut varier avec le timing et la pression mémoire, regardez le changement du bit Shared confirmé avec 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 les pages physiques. Parce que l’adresse virtuelle d’une vue peut différer par processus, stockez des décalages, pas des pointeurs bruts.

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

Les pages de code propres sont faciles à partager, mais la relocalisation, les sections inscriptibles, CoW et l’état de résidence au moment de la mesure produisent aussi des pages privées.

10.3. « Après 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 Private Bytes n’a pas augmenté, CoW n’a pas eu lieu »

FILE_MAP_COPY facture le Commit pour la vue entière d’avance. Priorisez Private WS et le bit Shared.6

10.5. « Si la page est partagée, aucune synchronisation n’est nécessaire »

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 pour l’atomicité, l’ordre mémoire, l’exclusion mutuelle, l’état intermédiaire au moment d’un crash, et la compatibilité de version.

L’idée de suivre les références et les durées de vie séparément 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# ».

11. Synthèse

  • Un objet section représente une plage de mémoire partageable, et chaque processus la mappe dans son propre espace d’adressage 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 est soutenue par un vrai fichier ; une section adossée au fichier d’échange soutient notamment la mémoire partagée nommée.9
  • Les EXE/DLL sont traités comme des sections image et les fichiers ordinaires comme des sections de données ; leur protection et leurs destinations de réécriture diffèrent.3
  • CoW partage les pages physiques pendant la lecture, et à la première écriture copie seulement cette page et remplace le PTE.5
  • Parce que VirtualQuery renvoie encore MEM_MAPPED/MEM_IMAGE après CoW, confirmez avec le bit Shared de QueryWorkingSetEx.7
  • Avec FILE_MAP_COPY, le Commit de la vue entière est facturé d’avance, donc CoW ne peut pas se juger d’après Private Bytes seuls.6
  • Les E/S mises en cache du Cache Manager, le mappage de données et le mappage image sont des chemins séparés qui utilisent respectivement SharedCacheMap, DataSectionObject et ImageSectionObject, liés sur le même flux de fichier.4
  • La mémoire partagée ne devient sûre que lorsque les durées de vie des vues et des handles, la synchronisation, les ACL et la conception des décalages sont toutes prises en compte.

Cela achève les trois parties de « Les profondeurs de la mémoire Windows ». Réserver et valider (Commit) une adresse virtuelle, obtenir une page physique par un défaut de page, la faire passer du Working Set aux listes de pages, la partager via une section, et ne faire diverger que les pages écrites via CoW — la gestion de la mémoire Windows est reliée comme ce flux continu unique.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge l’enquête des défauts d’applications Windows impliquant 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.

Références

  1. Microsoft Learn, Section Objects and Views. Sur le fait qu’un objet section représente une plage de mémoire partageable, et que chaque processus 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 adossées au fichier d’échange, 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 les objets 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 des vues qui soutiennent le même fichier. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

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

  5. Microsoft Learn, Memory Protection. Sur le fait que plusieurs processus partagent les pages physiques de la même DLL, et que CoW copie vers une nouvelle page physique et met à jour le PTE lorsque l’un d’eux écrit. ↩ ↩2 ↩3

  6. Microsoft Learn, MapViewOfFileEx function. Sur CoW avec FILE_MAP_COPY, les pages privées soutenues par le fichier d’échange, le Commit facturé pour la vue entière, et le stockage de décalages plutôt que d’adresses virtuelles. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  7. Microsoft Learn, VirtualQuery function. Sur le Type qui reste MEM_MAPPED/MEM_IMAGE après CoW, et la privatisation confirmable via 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 allouée tant que la vue n’est pas accédée, et que le défaut de page au 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 synchronisation requise à part. ↩ ↩2 ↩3

  10. Microsoft Learn, Process Explorer - Sysinternals. Sur le fait que Process Explorer peut afficher les handles d’un processus ainsi que ses DLL chargées et fichiers mappés en mémoire. ↩

  11. Microsoft Learn, VMMap - Sysinternals. Sur le fait que VMMap 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 propres au processus, via la copie à l'é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 d'adressage 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 à l'é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 puis 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 pour 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