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
· Go Komura · 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
- Partie 1 : Adresses virtuelles et défauts de page
Nous suivons l’instant où une page virtuelle commise obtient de la RAM physique. - Partie 2 : La vie d’une page physique
Nous suivons les transitions d’état d’une page qui quitte le Working Set. - 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.
Le0x000001...du processus A et le0x000002...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.
AvecSEC_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 utilisentSharedCacheMap, le mappage de données utiliseDataSectionObject, et les EXE/DLL utilisentImageSectionObject. Les trois s’accrochent au même flux de fichier viaSECTION_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_COPYimpute 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 deQueryWorkingSetEx.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.
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 ils 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["La 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é.
flowchart TB
accTitle: Comment le soutien fichier et le soutien fichier d'échange divergent
accDescr: Passer 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'objet
create["CreateFileMapping"] -->|Passer un handle de vrai fichier| fileBacked["Section adossée à un fichier"]
create -->|Passer INVALID_HANDLE_VALUE| pfBacked["Section adossée au fichier d'échange"]
fileBacked --> restore1["Une page propre peut être relue depuis le fichier d'origine"]
pfBacked --> restore2["Le 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.
flowchart LR
accTitle: État partagé avant la copie sur écriture
accDescr: Avant 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 quelles
pteA["PTE du processus A"] --> pfnX["PFN X partagé(lecture / copie sur é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 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.
- Obtenir une page physique pour le processus A.
- Copier le contenu de PFN X vers la nouvelle page PFN Y.
- Échanger le PTE du processus A vers PFN Y.
- Changer la protection du processus A en lecture/écriture ordinaire.
- Réexécuter l’instruction d’écriture qui a échoué.
flowchart LR
accTitle: État scindé après la copie sur écriture
accDescr: Aprè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 X
pteA2["PTE du processus A"] --> pfnY["PFN Y privé(R/W, après écriture)"]
pteB2["PTE du processus B"] --> pfnX2["PFN X partagé(origine)"]
pfnX2 -.->|Copié à l'écriture| pfnY
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.
- Accéder à la page cible et la rendre résidente.
- Obtenir les informations Working Set de la page avec
QueryWorkingSetEx. - Examiner le bit
Shared. - 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éesSharedCacheMap: la vue de cache que le Cache Manager suitImageSectionObject: é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/WriteFileen cache utilise leSharedCacheMapet 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
ImageSectionObjectet les E/S de pagination. Ce n’est pas un chemin qui passe par leSharedCacheMapdu Cache Manager.
flowchart TB
accTitle: Trois chemins qui s'accrochent au même flux de fichier
accDescr: ReadFile/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_POINTERS
cached["ReadFile / WriteFile 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["Le 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 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.
- Démarrez Process Explorer en tant qu’administrateur.
- Démarrez deux processus
cmd.exe. - Choisissez View > Lower Pane View > DLLs.
- Confirmez le chemin et le mappage de la même DLL dans les deux processus.
- Ouvrez chaque
cmd.exedans 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,
VirtualQueryrenvoie encoreMEM_MAPPED/MEM_IMAGE, donc vous confirmez avec le bit Shared deQueryWorkingSetEx.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,DataSectionObjectetImageSectionObject.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
- 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
- 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
- Les profondeurs de l’E/S Windows (épisode 4) — Le gestionnaire de cache : quand votre WriteFile atteint-il vraiment le disque ?
- 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# — schémas de libération des références COM et décision de remplacement
- Process Explorer / Handle / VMMap en pratique — Diagnostiquer les blocages, fuites et « fichiers en cours d’utilisation » à partir de l’état du système à cet instant précis
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.
- Développement d’applications Windows
- Investigation de bugs et analyse des causes
- Migration d’actifs hérités
- Nous contacter
Références
-
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
-
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
-
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 -
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
-
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
-
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 -
Microsoft Learn, VirtualQuery function. Sur le Type qui reste
MEM_MAPPED/MEM_IMAGEaprès le CoW, et la possibilité de confirmer la privatisation avec le bit Shared deQueryWorkingSetEx. ↩ ↩2 ↩3 ↩4 -
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. ↩
-
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 -
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. ↩
-
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 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. En s'appuyant sur les sources primaires...
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 durs pour expliquer l'instan...
Que représente réellement la « mémoire utilisée » sous Windows ── Bien lire 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 représentent pas la même valeur. Cet article explique ...
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 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.