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: · Go Komura · 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.
- 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
- 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 - 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_COPYfacture 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 deQueryWorkingSetExpour 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.
flowchart LR
accTitle: Mapper des adresses virtuelles différentes sur la même page physique
accDescr: Le 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 section
viewA["Processus A : 0x000001A00000 + 0x3000"] --> offset["Décalage de section 0x3000"]
viewB["Processus B : 0x000002700000 + 0x3000"] --> offset
offset --> pfnX["Mê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.
flowchart TB
accTitle: Comment les sections adossées à un fichier et au fichier d'échange divergent
accDescr: Passer 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étruit
create["CreateFileMapping"] -->|Passer un handle vers un vrai fichier| fileBacked["Section adossée à un fichier"]
create -->|Passer INVALID_HANDLE_VALUE| pfBacked["Section adossée au fichier d'échange"]
fileBacked --> restore1["Les pages propres peuvent être relues depuis le fichier d'origine"]
pfBacked --> restore2["Les 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.
flowchart LR
accTitle: État partagé avant la copie à l'écriture
accDescr: Avant 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 simplement
pteA["PTE du processus A"] --> pfnX["PFN X partagé (lecture / copie à l'écriture)"]
pteB["PTE du processus B"] --> pfnX
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.
- Obtenir une page physique pour le processus A.
- Copier le contenu de PFN X vers la nouvelle page, PFN Y.
- Remplacer le PTE du processus A pour pointer vers PFN Y.
- Changer la protection du côté du processus A en lecture/écriture ordinaire.
- Réexécuter l’instruction d’écriture qui a échoué.
flowchart LR
accTitle: État divergé après la copie à l'écriture
accDescr: En 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 X
pteA2["PTE du processus A"] --> pfnY["PFN Y privé (lecture/écriture, après la modification)"]
pteB2["PTE du processus B"] --> pfnX2["PFN X partagé (contenu d'origine)"]
pfnX2 -.->|Contenu copié au moment de l'écriture| pfnY
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.
- Accédez à la page cible pour qu’elle devienne résidente.
- Obtenez les informations de Working Set de la page avec
QueryWorkingSetEx. - Regardez le bit
Shared. - 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 ».
flowchart TB
accTitle: Trois chemins liés au même flux de fichier
accDescr: ReadFile/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_POINTERS
cached["ReadFile / WriteFile mis en cache"] --> scm["SharedCacheMap"]
dataFault["Défaut de mappage de données"] --> dso["DataSectionObject"]
imageFault["Défaut d'image EXE/DLL"] --> iso["ImageSectionObject"]
scm --> stream["Même flux de fichier (SECTION_OBJECT_POINTERS)"]
dso --> stream
iso --> stream
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.
- Démarrez Process Explorer en tant qu’administrateur.
- Démarrez deux instances de
cmd.exe. - Choisissez Affichage > Vue du volet inférieur > DLL.
- Confirmez le chemin et le mappage de la même DLL dans les deux processus.
- Ouvrez chaque
cmd.exedans 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
VirtualQueryrenvoie encoreMEM_MAPPED/MEM_IMAGEaprès CoW, confirmez avec le bit Shared deQueryWorkingSetEx.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,DataSectionObjectetImageSectionObject, 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
- Les profondeurs de la mémoire Windows (partie 1) — L’instant où une adresse virtuelle devient de la RAM physique
- Les profondeurs de la mémoire Windows (partie 2) — La vie d’une page physique
- Les profondeurs de l’E/S Windows (partie 4) — Le gestionnaire de cache et WriteFile
- Pièges de la mémoire partagée et bonnes pratiques concrètes
- Le problème d’EXCEL.EXE qui reste actif lors de la manipulation d’Excel en C#
- Process Explorer / Handle / VMMap en pratique
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.
- Développement d’applications Windows
- Investigation de bugs et analyse des causes
- Valorisation et migration d’actifs existants
- Nous contacter
Références
-
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
-
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
-
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 -
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
-
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
-
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 -
Microsoft Learn, VirtualQuery function. Sur le Type qui reste
MEM_MAPPED/MEM_IMAGEaprès CoW, et la privatisation confirmable via le bit Shared deQueryWorkingSetEx. ↩ ↩2 ↩3 ↩4 -
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. ↩
-
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 -
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. ↩
-
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 associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
DllMain et le verrou du chargeur — la vraie raison pour laquelle on vous dit de « ne rien faire dans l'initialisation d'une DLL »
Pourquoi il ne faut pas appeler LoadLibrary ni synchroniser avec d'autres threads depuis DllMain. À partir des sources primaires, cet art...
Les profondeurs de la mémoire Windows (partie 2) — La vie d'une page physique : cinq listes et la vérité sur le fichier d'échange
Cet article relie la base PFN, Standby, Modified, la compression mémoire et le fichier d'échange pour expliquer où va une page physique u...
Les profondeurs de la mémoire Windows (partie 1) — L'instant où une adresse virtuelle devient de la RAM physique : un défaut de page de bout en bout
Cet article relie VirtualAlloc, les VAD, les tables de pages, le TLB, les défauts demand-zero et les défauts matériels pour expliquer l'i...
Que représente réellement l'« utilisation mémoire » sous Windows — Lire correctement Working Set, Private Bytes, Commit et le fichier d'échange
La « mémoire » du Gestionnaire des tâches, Working Set, Private Bytes et Commit ne sont pas la même valeur. Cet article explique le rappo...
Comment choisir la communication inter-processus sous Windows ── Tableau de décision : tubes nommés / TCP / gRPC / mémoire partagée / COM
Comment choisir le moyen de faire communiquer des applications Windows entre elles ? Cet article organise les tubes nommés, le TCP local,...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Applications métier, intégration d'équipements et outils de communication, des besoins au développement.
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.