Interioridades de la memoria de Windows (parte 3) — Objetos de sección y copia en escritura: qué son realmente las DLL y la asignación de archivos
· Actualizado el: · Go Komura · Windows, Gestión de memoria, Memoria compartida, Asignación de archivos, Copia en escritura, DLL, Cache Manager
Historial de revisiones (primera versión, publicada el 20 Aug 2026)
- Primera publicación
Citar este artículo(DOI (archivo registrado): 10.5281/zenodo.22176173)
Los DOI siguientes remiten a versiones ya archivadas y pueden diferir del texto actual. Para citar el texto actual, utilice la URL de esta página.
Go Komura (2026). Interioridades de la memoria de Windows (parte 3) — Objetos de sección y copia en escritura: qué son realmente las DLL y la asignación de archivos. KomuraSoft LLC. https://comcomponent.com/es/blog/windows-memory-internals-section-copy-on-write/
- DOI (archivo registrado)
- 10.5281/zenodo.22176173
- DOI (última versión registrada)
- 10.5281/zenodo.22176174
«Si 100 procesos usan el mismo kernel32.dll, ¿hacen falta 100 juegos de páginas de código?». El punto de partida de la parte 3 es la duda que surge cuando varios procesos usan el mismo contenido. Cuando dos procesos asignan el mismo archivo en memoria, ¿puede el otro usar una página que uno de ellos ya leyó?
La respuesta está en un mecanismo que hace corresponder distintas direcciones virtuales con la misma página física. El centro de la unidad de compartición es el objeto de sección. Y el mecanismo que privatiza solo las páginas que lo necesitan, en el momento de escribir, es la copia en escritura (Copy-on-Write, CoW).
En la entrega anterior, «Interioridades de la memoria de Windows (parte 2) — La vida de una página física», seguimos cómo una página que sale del Working Set se mueve por Modified, Standby, Free y Zeroed. En Standby también quedan páginas de DLL, EXE, archivos asignados y la caché de archivos. Esta vez añadimos la perspectiva de la compartición entre varios procesos.
Tras ver en orden EXE y DLL, archivos de datos, memoria compartida respaldada por el archivo de paginación y la caché de archivos, distinguimos las rutas de caché, datos e imagen sobre el mismo flujo de archivo. Para leer las cifras se da por sentado el artículo introductorio «Qué representa realmente el “uso de memoria” de Windows».
«Interioridades de la memoria de Windows»: las 3 partes
Esta serie avanza en el orden obtener una página física → seguir el flujo de residencia y recuperación → entender la compartición y la privatización.
| Parte | Tema | Lo que sigue esta parte |
|---|---|---|
| Parte 1 | Direcciones virtuales y errores de página | Cuándo una región de VirtualAlloc obtiene RAM física |
| Parte 2 | La vida de una página física | Las transiciones de estado de una página que sale del Working Set y el papel del archivo de paginación |
| Parte 3 (este artículo) | Objetos de sección y copia en escritura | Cómo las DLL, la asignación de archivos y la memoria compartida comparten páginas físicas |
La pregunta a la que responde la parte 3 es una sola.
¿Por qué varios procesos pueden usar el mismo DLL o archivo como un único conjunto de páginas físicas?
| Antes de empezar | Contenido |
|---|---|
| Público | Desarrolladores y responsables de operación que quieren entender, desde la estructura interna, la relación entre la compartición de DLL, CreateFileMapping, MapViewOfFile, la memoria compartida, CoW y la caché de archivos |
| Entorno previsto | Windows 10/11 o una versión actual de Windows Server |
| Conocimientos previos | Fundamentos de direcciones virtuales, errores de página y Working Set |
| Dificultad | Intermedio. También se usan términos internos como Control Area y Prototype PTE |
Para observar se usan VMMap, Process Explorer y QueryWorkingSetEx.
En el diagrama, una línea continua marca una relación que siempre se cumple y una línea discontinua una relación condicional (las condiciones están en la explicación de cada relación en la página de detalle). La lista completa de relaciones (19 en total, con evidencia y grado de certeza) y las definiciones de los conceptos principales están reunidas en la página de detalle del mapa de conocimiento (en japonés). Datos: JSON-LD / Turtle
1. Primero la conclusión
El panorama se resume en estos tres puntos.
- Separe el contenido que se comparte de la dirección que ve cada proceso. El objeto de sección representa un intervalo de memoria que se puede compartir, y cada proceso asigna una parte como vista. Aunque usen el mismo desplazamiento de sección, las direcciones virtuales de las vistas no tienen por qué coincidir.12
- Separe con qué se sostiene el contenido y por qué ruta se usa. Hay secciones sostenidas por un archivo real y otras por el archivo de paginación. EXE/DLL es una sección de imagen; la asignación habitual de un archivo es una sección de datos. Con
SEC_IMAGE, la protección de página la deciden los atributos del propio PE. Tampoco hay que juntar en una sola las rutas de caché, datos e imagen.234 - La privatización de CoW se confirma con el estado de compartición por página. Durante la lectura se comparte; solo la página escrita se copia y se sustituye el PTE de ese proceso. Como
FILE_MAP_COPYcarga Commit de antemano para toda la vista, Private Bytes por sí solo no puede establecer que ocurrió CoW. Para confirmarlo se usa el bit Shared deQueryWorkingSetEx.567
En una frase: lo que se comparte no es la dirección virtual, sino el contenido de la sección y las páginas físicas que le corresponden en ese momento.
| Lo que se quiere saber | Apartados que leer |
|---|---|
| Relación entre sección, vista y almacén de respaldo | Apartados 2 a 3 |
| Compartición de DLL y CoW al escribir | Apartados 4 a 6 |
| Diferencia con Cache Manager y por qué sigue en uso tras cerrar | Apartados 7 a 8 |
| Confirmar compartición y privatización con dos procesos | Apartados 9 a 10 |
2. Objetos de sección y vistas
En la definición de Microsoft, un objeto de sección representa un intervalo de memoria que se puede compartir, y es también el mecanismo que asigna un archivo en el espacio de direcciones de un proceso.1
La clave para entenderlo es pensar por separado la sección en sí y la vista que ve cada proceso.
| Concepto | Función |
|---|---|
| Objeto de sección | Representa el contenido que se comparte, el tamaño, el almacén de respaldo y el tope de protección |
| Vista | Muestra una parte de la sección como un intervalo de direcciones virtuales de un proceso |
| PTE | Une cada página virtual de la vista con la página física actual o con un estado aún no materializado |
| PFN | Representa una página física que existe de verdad en la RAM |
La API de Win32 también se lee a lo largo de esta diferencia de funciones.3
| Etapa | Lo que se obtiene |
|---|---|
CreateFileMapping |
Un identificador del objeto de asignación de archivo |
MapViewOfFile |
Una vista colocada en el espacio virtual del proceso |
| Primer acceso a la vista | La correspondencia de la página accedida con memoria física a través de un error de página |
Empecemos con un ejemplo que asigna un archivo de solo lectura.
HANDLE mapping = CreateFileMappingW(
file,
nullptr,
PAGE_READONLY,
0,
0,
nullptr);
void* view = MapViewOfFile(
mapping,
FILE_MAP_READ,
0,
0,
0);
Solo con llamar a CreateFileMapping aún no se obtiene una dirección que el proceso pueda leer. Y crear una vista tampoco mete de inmediato todas las páginas en la RAM. A partir de la primera página que se toca, los errores de página leen el contenido del archivo y unen páginas físicas a los PTE.8 Es decir, la paginación por demanda que vimos en la parte 1 se aplica tal cual a las vistas de sección.
2.1. La misma sección, distintas direcciones virtuales
Aunque el proceso A y el proceso B asignen el mismo desplazamiento de la misma sección, la dirección de inicio de la vista puede diferir.
flowchart LR
accTitle: Correspondencia de distintas direcciones virtuales con la misma página física
accDescr: El proceso A y el proceso B tienen cada uno una vista en una dirección virtual distinta, pero ambos llegan a la misma página física PFN X a través del mismo desplazamiento de sección
viewA["Process A: 0x000001A00000 + 0x3000"] --> offset["Desplazamiento de sección 0x3000"]
viewB["Process B: 0x000002700000 + 0x3000"] --> offset
offset --> pfnX["La misma página física PFN X"]
Figura 1: Lo que se comparte es el contenido de la sección y la página física, no la dirección virtual.
La razón para no guardar punteros crudos en memoria compartida está aquí. El valor de un puntero del proceso A puede ser una dirección sin relación en el proceso B.
En una estructura compartida se usan desplazamientos desde el inicio de la vista, enteros de ancho fijo, y una versión y un alineamiento explícitos. La documentación de MapViewOfFileEx de Microsoft también recomienda guardar desplazamientos desde la base, no punteros, porque no hay garantía de que la misma dirección esté disponible en el futuro.6
3. Respaldo por archivo y por archivo de paginación
Las secciones se dividen a grandes rasgos en dos tipos según el lugar desde el que se puede restaurar el contenido.
flowchart TB
accTitle: Cómo se separan el respaldo por archivo y por archivo de paginación
accDescr: Si se pasa un archivo real a CreateFileMapping se obtiene una sección respaldada por archivo, cuyas páginas clean se pueden releer del archivo original. Si se pasa INVALID_HANDLE_VALUE se obtiene una sección respaldada por el archivo de paginación, cuyo contenido lo sostiene el archivo de paginación y desaparece al destruir el objeto
create["CreateFileMapping"] -->|Pasar el identificador de un archivo real| fileBacked["Sección respaldada por archivo"]
create -->|Pasar INVALID_HANDLE_VALUE| pfBacked["Sección respaldada por el archivo de paginación"]
fileBacked --> restore1["Las páginas clean se pueden releer del archivo original"]
pfBacked --> restore2["El contenido lo sostiene el archivo de paginación y desaparece al destruir"]
Figura 2: La diferencia de almacén de respaldo decide desde dónde se puede restaurar el contenido y cuánto dura.
3.1. Secciones respaldadas por archivo
Si se pasa un archivo real a CreateFileMapping, se obtiene una sección respaldada por archivo.
- Una vista de solo lectura lee del archivo las páginas que necesita.
- Los cambios de una vista de lectura y escritura se tratan como datos de ese archivo.
- Los cambios de una vista CoW no se escriben en el archivo original; se convierten en páginas privadas.
Si una página respaldada por archivo está clean, se puede descartar la página física y releerla después del archivo original. Esta propiedad es la que sostiene la eficiencia de Standby y de la caché de archivos que vimos en la parte 2.
3.2. Secciones respaldadas por el archivo de paginación
Si se pasa INVALID_HANDLE_VALUE como hFile de CreateFileMapping y se indica un tamaño, se obtiene una sección respaldada por el archivo de paginación.
HANDLE mapping = CreateFileMappingW(
INVALID_HANDLE_VALUE,
nullptr,
PAGE_READWRITE,
0,
64 * 1024,
L"Local\\KomuraMemoryDemo");
Esta sección no tiene un archivo de datos explícito; el contenido lo sostiene el archivo de paginación. El contenido inicial es cero. Para usar el mismo objeto desde varios procesos se usan un nombre, la herencia de identificadores, DuplicateHandle y similares.93
Los cambios en una página compartida se ven, pero no son almacenamiento persistente. Los procesos que asignan la misma página compartida pueden ver los cambios. En cambio, si se destruye el objeto de sección el contenido no queda, así que no sirve como sustituto de un archivo persistente.2
Una precaución: la memoria compartida no trae exclusión mutua de forma automática. Hay que diseñar por separado un mutex, un semáforo, un evento, un protocolo lock-free u otro mecanismo.9
4. Asignación de imagen y asignación de datos
Cuando se dice «un EXE o una DLL también es una asignación de archivo», hay que conservar las diferencias respecto a un archivo de datos habitual.
| Elemento | Asignación de imagen | Asignación de datos |
|---|---|---|
| Uso principal | Carga de EXE y DLL | Archivos habituales, datos compartidos |
| Atributo de creación | SEC_IMAGE |
PAGE_READONLY, PAGE_READWRITE, etc. |
| Protección de página | La deciden los atributos del interior de la imagen PE | La deciden la asignación y la especificación de la vista |
| Escritura | Se puede privatizar con una sección writable o con CoW | Se puede elegir escritura compartida o CoW |
Type de VirtualQuery |
MEM_IMAGE |
MEM_MAPPED |
Con SEC_IMAGE, más que el valor de protección habitual pasado a CreateFileMapping, los atributos de sección de la propia imagen ejecutable deciden la protección de página de la vista.3
4.1. No todas las páginas de una DLL se comparten necesariamente
Las páginas que no cambian, como el código, pueden compartir la misma página física entre muchos procesos. Las páginas que necesitan cambios propios del proceso se ramifican con CoW.
Sin embargo influyen la reubicación por ASLR, las correcciones del cargador, las revisiones en caliente y los atributos reales de las secciones PE. Aunque se cargue el mismo DLL, no todas las páginas se comparten necesariamente.
Lo importante es el diseño de compartir primero las páginas que se pueden compartir y copiar con retraso solo las que necesitan cambios.
5. La copia en escritura de principio a fin
Sigamos el recorrido desde el estado en que dos procesos leen la misma página CoW hasta que solo el proceso A escribe un byte.
5.1. Antes de escribir
Antes de que ocurra la escritura, los PTE de ambos procesos llegan, en lo conceptual, a la misma página compartida, y la lectura tiene éxito tal cual.
flowchart LR
accTitle: Estado compartido antes de la copia en escritura
accDescr: Antes de que ocurra la escritura, los PTE del proceso A y del proceso B llegan ambos a la misma página compartida PFN X, y la lectura tiene éxito tal cual
pteA["Process A PTE"] --> pfnX["PFN X compartido (read / copy-on-write)"]
pteB["Process B PTE"] --> pfnX
Figura 3: Antes de escribir, los PTE de ambos procesos apuntan a la misma página física.
5.2. Error de protección al escribir
Una página CoW no es de entrada una página compartida de escritura habitual. Cuando el proceso A intenta escribir, la CPU genera un error de protección. El administrador de memoria que recibe el control determina que no es una escritura ilegal, sino una escritura sobre un atributo CoW.
5.3. Crear una página física nueva
Tras esa determinación, Windows hace lo siguiente.
- Obtiene una página física para el proceso A.
- Copia el contenido de PFN X a la nueva página PFN Y.
- Sustituye el PTE del proceso A para que apunte a PFN Y.
- Cambia la protección del lado del proceso A a lectura y escritura habituales.
- Vuelve a ejecutar la instrucción de escritura que falló.
flowchart LR
accTitle: Estado ramificado después de la copia en escritura
accDescr: Tras la escritura del proceso A, solo el PTE del proceso A se sustituye hacia la página privada PFN Y con el contenido copiado, y el PTE del proceso B sigue apuntando a la página compartida original PFN X
pteA2["Process A PTE"] --> pfnY["PFN Y privado (read/write, ya modificado)"]
pteB2["Process B PTE"] --> pfnX2["PFN X compartido (contenido original)"]
pfnX2 -.->|Al escribir se copia el contenido| pfnY
Figura 4: Solo el PTE del proceso que escribió se sustituye hacia una página privada nueva; el otro sigue leyendo el contenido original.
El proceso B sigue leyendo el contenido original y no ve los cambios del proceso A. Eso es Copy-on-Write. Tanto la compartición de DLL como FILE_MAP_COPY usan el mismo principio de no copiar hasta que se escribe.56
5.4. Diferencia con FILE_MAP_WRITE
El criterio de elección es si se quiere que la escritura también la vea la otra parte, o si se quiere un cambio solo propio.6
| Vista | Lo que ocurre después de escribir |
|---|---|
FILE_MAP_WRITE |
Como escritura compartida, el cambio también se ve desde otra vista que usa la misma asignación de archivo |
FILE_MAP_COPY |
Solo las páginas escritas se vuelven exclusivas del proceso. Los cambios no se reescriben en el archivo original y se pierden al quitar la vista |
«Quiero transmitir actualizaciones con memoria compartida» o «quiero que cada proceso haga cambios privados a partir de unos datos iniciales comunes». Según el objetivo, la elección es la opuesta.
6. Tras CoW sigue siendo MEM_MAPPED / MEM_IMAGE
Aquí se separan el origen de la región y el estado de compartición actual de la página física.
La página tras CoW es físicamente Private, pero Type de VirtualQuery no pasa a MEM_PRIVATE. Sigue siendo MEM_MAPPED en una vista de datos y MEM_IMAGE en una imagen ejecutable. Type indica de qué asignación inicial proviene esa región.7
Para ver, página a página, si ya se hizo CoW, se usa este procedimiento.
- Acceder a la página de destino y dejarla residente.
- Obtener la información de Working Set de la página con
QueryWorkingSetEx. - Mirar el bit
Shared. - Si
Shared == 0, esa página residente es Private.
Si se confirma con VMMap, además del Type de la región se mira el desglose Private/Shareable del Working Set.
6.1. A veces Private Bytes no aumenta
FILE_MAP_COPY privatiza solo las páginas escritas. El momento en que se carga Commit, sin embargo, es otro.
Para cubrir la posibilidad de escribir en el futuro todas las páginas de la vista, Windows toma en el momento de asignar un Commit charge equivalente a toda la vista.6 Por eso, el instante de escribir la primera página no tiene por qué aumentar Private Bytes en 4 KiB.
Los indicadores que priorizar al observar CoW son estos.
- El bit Shared de
QueryWorkingSetEx - Private WS / Shareable WS de VMMap
- La información de páginas físicas de RAMMap
- Private Bytes como información auxiliar
Si se usa como criterio de apto o no apto solo «si Private Bytes aumentó en el instante de escribir», se pasa por alto un CoW que está funcionando bien.
7. El punto de contacto con Cache Manager — Separar las tres rutas
Si se dice «la carga de EXE y DLL, la caché de archivos y la memoria compartida son todas secciones», se capta el panorama, pero no hay que aplastar la implementación en un solo objeto.
El flujo de archivo tiene SECTION_OBJECT_POINTERS, que usan el administrador de memoria y Cache Manager.
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject;
PVOID SharedCacheMap;
PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;
Esta estructura une el objeto de archivo con las secciones del flujo de archivo y sigue el contenido en memoria y la información de caché.4 Sin embargo, el estado al que apunta cada campo y la ruta de procesamiento son distintos.
| Ruta | Campo correspondiente | Función del procesamiento |
|---|---|---|
ReadFile / WriteFile con caché |
SharedCacheMap |
Cache Manager usa una vista de caché |
| Error de asignación de un archivo de datos | DataSectionObject |
El administrador de memoria lo trata en el lado de la sección de datos y coordina para mantener la coherencia del contenido con la E/S con caché del mismo flujo de archivo |
| Error de imagen de EXE/DLL | ImageSectionObject |
El administrador de memoria lo trata con la sección de imagen y la E/S de paginación. No es una ruta que pase por SharedCacheMap |
El punto de contacto con la serie de E/S es que estas tres se unen sobre el mismo flujo de archivo. No hay que leerlo como «todas las rutas pasan por Cache Manager».
flowchart TB
accTitle: Tres rutas unidas al mismo flujo de archivo
accDescr: ReadFile/WriteFile con caché usa SharedCacheMap, el error de asignación de datos usa DataSectionObject y el error de imagen de EXE/DLL usa ImageSectionObject; las tres se unen al mismo flujo de archivo a través de SECTION_OBJECT_POINTERS
cached["ReadFile / WriteFile con caché"] --> scm["SharedCacheMap"]
dataFault["Error de asignación de datos"] --> dso["DataSectionObject"]
imageFault["Error de imagen de EXE/DLL"] --> iso["ImageSectionObject"]
scm --> stream["El mismo flujo de archivo (SECTION_OBJECT_POINTERS)"]
dso --> stream
iso --> stream
Figura 5: Las tres rutas se procesan por separado, pero están unidas sobre el mismo flujo de archivo.
El punto común de las tres no es «todas entran en Cache Manager», sino que el mismo flujo de archivo une, a través de SECTION_OBJECT_POINTERS, estados distintos: caché, sección de datos y sección de imagen.4 La relación entre lectura y escritura de caché, Lazy Writer y Cc/Mm se trata en «Interioridades de la E/S de Windows (parte 4) — Cache Manager y WriteFile».
Además, si se mezclan una vista asignada en memoria y ReadFile/WriteFile, no se garantiza que se vea siempre el contenido del mismo instante. Hace falta un diseño que incluya sincronización, vaciado (flush) y modo de compartición del archivo.36
8. Vida del objeto y de la vista
Cerrar un identificador y quitar una vista son cosas distintas. Solo con cerrar el identificador de CreateFileMapping las vistas existentes no desaparecen.
La vista tiene una referencia interna a la sección. Solo cuando se hace UnmapViewOfFile de todas las vistas y CloseHandle de todos los identificadores el objeto queda en un estado en el que se puede destruir.3
UnmapViewOfFile(view);
CloseHandle(mapping);
CloseHandle(file);
Esta separación de vidas es la causa del fenómeno «cerré el archivo y sigue en uso». Aunque se cierre el identificador de archivo, si una sección de imagen o una vista de datos sigue haciendo referencia al flujo de archivo, el close definitivo del archivo llega más tarde.
La relación con cleanup/close del lado de E/S está en «Interioridades de la E/S de Windows (parte 1)», y las trampas de implementación, en «Trampas de la memoria compartida y buenas prácticas para producción».
9. Comprobarlo con los propios ojos
9.1. Ver el mismo DLL en dos procesos
Primero, con procesos ya existentes, se confirma la compartición de DLL.
- Inicie Process Explorer como administrador.
- Inicie dos
cmd.exe. - Elija View > Lower Pane View > DLLs.
- Confirme en ambos procesos la ruta y la asignación del mismo DLL.
- Abra cada
cmd.exeen VMMap y compare Working Set, Private y Shareable de Images.
Ver el mismo DLL no demuestra por sí solo que coincidan los PFN
Que Process Explorer muestre el mismo DLL es prueba de que ambos tienen asignada la misma imagen. Sin embargo, eso solo no demuestra que coincidan los PFN de cada página. Se combina el desglose Shareable de VMMap, RAMMap y QueryWorkingSetEx para confirmar la compartición por página. Process Explorer y VMMap los ofrece Sysinternals.1011
9.2. Observar FILE_MAP_COPY con dos procesos
El programa siguiente asigna el mismo archivo como vista CoW y muestra el bit Shared de QueryWorkingSetEx. El objeto de asignación de archivo se crea con PAGE_READONLY, pero esta protección es compatible con una vista FILE_MAP_COPY, y la primera escritura del lado de la vista provoca 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);
}
Compilar y abrir el mismo archivo en dos procesos
Se compila y se prepara.
cl /std:c++20 /EHsc /W4 cow_demo.cpp
$path = "$env:TEMP\\cow-demo.bin"
[IO.File]::WriteAllBytes($path, [byte[]]::new(65536))
A continuación, en dos consolas se abre el mismo archivo.
.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" read
.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" write
Separar el orden de pulsar Enter y los valores que se miran
| Orden | Operación | Qué confirmar |
|---|---|---|
| 1 | Iniciar ambos y pulsar Enter primero en el lado read y después en el lado write | En el before de ambos, Shared está activado |
| 2 | Pulsar Enter otra vez en el lado write | La página del proceso que escribió pasa a Shared 0 |
| 3 | Después pulsar Enter en el lado read | El lado reader sigue leyendo la página original |
Type de VirtualQuery sigue siendo MEM_MAPPED después de escribir. Confirme por separado el cambio del estado de compartición y el valor de Type.
Interpretar solo el resultado en el que Valid está activado
Además, PrintPage vuelve a tocar la página de destino justo antes de consultar y, si Valid == 0, no interpreta Shared ni ShareCount y reintenta hasta tres veces. Si aun así no queda residente, no emite resultado y lo indica. ShareCount puede cambiar con el momento y la presión de memoria, así que no mire un valor fijo, sino el cambio del bit Shared confirmado con Valid == 1.
10. Cinco lecturas erróneas que conviene evitar en la práctica
10.1. «La memoria compartida queda en la misma dirección virtual»
Lo que se comparte son la sección y la página física. La dirección virtual de la vista puede diferir entre procesos, así que se guardan desplazamientos, no punteros crudos.
10.2. «Si es el mismo DLL, todas las páginas se comparten necesariamente»
Las páginas de código clean se comparten con facilidad, pero la reubicación, las secciones writable, CoW y el estado de residencia en el momento de medir también generan páginas Private.
10.3. «Tras CoW pasa a MEM_PRIVATE»
Type de VirtualQuery sigue siendo MEM_MAPPED o MEM_IMAGE. El estado real de compartición se confirma con QueryWorkingSetEx.7
10.4. «Si Private Bytes no aumenta, no hay CoW»
FILE_MAP_COPY carga Commit de antemano para toda la vista. Se priorizan Private WS y el bit Shared.6
10.5. «Si la página está compartida, no hace falta sincronización»
Que se vea la misma página física y que se pueda actualizar con seguridad desde varios núcleos de CPU son problemas distintos. Se diseñan atomicidad, orden de memoria, exclusión, el estado intermedio en caso de bloqueo y la compatibilidad de versiones.
La idea de seguir por separado las referencias y la vida también se aplica al problema de que un proceso quede residual en la interoperabilidad COM de Excel. Consulte también «Por qué queda un proceso tras la interoperabilidad COM de Excel».
11. Resumen
- El objeto de sección representa un intervalo de memoria que se puede compartir, y cada proceso lo asigna como vista en su propio espacio virtual.1
- El mismo desplazamiento de la misma sección se hace corresponder, desde distintas direcciones virtuales, con la misma página física.2
- La sección respaldada por archivo sostiene un archivo real; la respaldada por el archivo de paginación sostiene, entre otras cosas, memoria compartida con nombre.9
- EXE/DLL se trata como sección de imagen y un archivo habitual como sección de datos, y la protección y el destino de reescritura difieren.3
- CoW comparte la página física durante la lectura y, en la primera escritura, copia solo esa página y sustituye el PTE.5
- Tras CoW,
VirtualQuerysigue devolviendoMEM_MAPPED/MEM_IMAGE, así que se confirma con el bit Shared deQueryWorkingSetEx.7 - Con
FILE_MAP_COPYse carga de antemano el Commit de toda la vista, así que no se puede juzgar CoW solo con Private Bytes.6 - La E/S con caché de Cache Manager, la asignación de datos y la asignación de imagen son rutas distintas que usan respectivamente
SharedCacheMap,DataSectionObjecteImageSectionObject, y se unen sobre el mismo flujo de archivo.4 - Solo al incluir la vida de vistas e identificadores, la sincronización, las ACL y el diseño de desplazamientos la memoria compartida resulta segura.
Con esto se completa «Interioridades de la memoria de Windows» en sus 3 partes. Reservar y confirmar (Reserve/Commit) una dirección virtual, obtener una página física con un error de página, pasar del Working Set a las listas de páginas, compartir a través de una sección y ramificar con CoW solo la página escrita: la gestión de memoria de Windows se encadena como este flujo.
Artículos relacionados
- Interioridades de la memoria de Windows (parte 1) — El instante en que una dirección virtual se convierte en RAM física
- Interioridades de la memoria de Windows (parte 2) — La vida de una página física
- Interioridades de la E/S de Windows (parte 4) — Cache Manager y WriteFile
- Trampas de la memoria compartida y buenas prácticas para producción
- Por qué queda un proceso tras la interoperabilidad COM de Excel
- Process Explorer / Handle / VMMap en la práctica
Ámbitos de consulta relacionados
En KomuraSoft LLC tratamos la investigación de fallos de aplicaciones Windows en memoria compartida, asignación de archivos, carga de DLL, bloqueo de archivos, comunicación entre procesos y uso de memoria.
- Desarrollo de aplicaciones Windows
- Investigación de fallos y análisis de causas
- Aprovechamiento de activos existentes y apoyo a la migración
- Contacto
Referencias
-
Microsoft Learn, Section Objects and Views. Sobre que un objeto de sección representa un intervalo de memoria que se puede compartir y que cada proceso asigna una parte de la sección como vista. ↩ ↩2 ↩3
-
Microsoft Learn, File-Backed and Page-File-Backed Sections. Sobre las secciones respaldadas por archivo y por el archivo de paginación, CoW, y que se puede compartir la misma memoria física desde las direcciones virtuales de distintos procesos. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, CreateFileMappingW function. Sobre el objeto de asignación de archivo, el respaldo por archivo de paginación,
SEC_IMAGE, la vida de vistas e identificadores, y la coherencia de las vistas que sostienen el mismo archivo. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Microsoft Learn, SECTION_OBJECT_POINTERS structure. Sobre que DataSectionObject, SharedCacheMap e ImageSectionObject unen la asignación y la información de caché del flujo de archivo con el administrador de memoria y Cache Manager. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Memory Protection. Sobre que varios procesos comparten las páginas físicas del mismo DLL y que, al escribir uno de ellos, CoW copia a una página física nueva y actualiza el PTE. ↩ ↩2 ↩3
-
Microsoft Learn, MapViewOfFileEx function. Sobre CoW de
FILE_MAP_COPY, que las páginas privadas las sostiene el archivo de paginación, que se carga Commit charge para toda la vista, y que hay que guardar desplazamientos, no direcciones virtuales. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Microsoft Learn, VirtualQuery function. Sobre que tras CoW Type sigue siendo
MEM_MAPPED/MEM_IMAGEy que la privatización se puede confirmar con el bit Shared deQueryWorkingSetEx. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Managing Memory Sections. Sobre que no se asigna memoria física hasta que se accede a la vista, y que el error de página del primer acceso lee el contenido del archivo. ↩
-
Microsoft Learn, Sharing Files and Memory. Sobre compartir el mismo objeto de asignación de archivo por nombre o identificador, crear memoria compartida respaldada por el archivo de paginación con
INVALID_HANDLE_VALUE, y que la sincronización hace falta por separado. ↩ ↩2 ↩3 -
Microsoft Learn, Process Explorer - Sysinternals. Sobre que Process Explorer puede mostrar los identificadores de un proceso y las DLL y archivos asignados en memoria cargados. ↩
-
Microsoft Learn, VMMap - Sysinternals. Sobre que VMMap descompone la memoria virtual de un proceso en Image, Mapped File, Private y similares, y muestra el desglose Private/Shareable del Working Set. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
DllMain y el bloqueo del cargador — La razón real de que le digan «no haga nada en la inicialización de la DLL»
Por qué no debe llamar a LoadLibrary ni sincronizar con otros hilos desde DllMain. A partir de fuentes primarias, este artículo explica e...
Interioridades de la memoria de Windows (parte 2) — La vida de una página física: las cinco listas y la verdad del archivo de paginación
Conecta la base de datos PFN, Standby, Modified, la compresión de memoria y el archivo de paginación, y explica adónde va una página físi...
Interioridades de la memoria de Windows (1.ª parte) — El instante en que una dirección virtual se convierte en RAM física: un Page Fault de principio a fin
Une VirtualAlloc, VAD, tablas de páginas, TLB, demand-zero y hard faults para explicar el instante en que una dirección virtual se convie...
¿Qué representa el «uso de memoria» de Windows? — Cómo leer Working Set, Private Bytes, Commit y el archivo de paginación
La memoria del Administrador de tareas, Working Set, Private Bytes y Commit no son el mismo valor. Se explica la relación entre memoria v...
Cómo elegir la comunicación entre procesos en Windows — canalizaciones con nombre / TCP / gRPC / memoria compartida / COM: tabla de decisión
Cómo elegir la comunicación entre procesos en Windows: comparamos en una tabla de decisión las canalizaciones con nombre, el TCP local, g...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Se duplica todo el DLL en la RAM por cada proceso que lo usa?
- Normalmente no se duplica. Las páginas sin modificar de la misma imagen se corresponden, desde distintas direcciones virtuales de cada proceso, con la misma página física. Solo las páginas que necesitan escritura se convierten en páginas físicas propias del proceso, por ejemplo mediante la copia en escritura.
- ¿CreateFileMapping asigna memoria al proceso en ese mismo momento?
- CreateFileMapping crea el objeto de asignación de archivo, pero es MapViewOfFile quien lo hace visible en el espacio virtual del proceso. Además, las páginas físicas de la vista normalmente se materializan mediante errores de página a partir de la primera página a la que se accede.
- ¿En qué se diferencian FILE_MAP_WRITE y FILE_MAP_COPY?
- Los cambios con FILE_MAP_WRITE son escrituras que se aplican a los datos de archivo compartidos. FILE_MAP_COPY comparte las páginas iniciales, pero solo las páginas escritas se convierten en una copia exclusiva del proceso: los cambios no se reescriben en el archivo original y se pierden al quitar la vista.
- ¿VirtualQuery devuelve MEM_PRIVATE después de la copia en escritura?
- No. Sigue siendo MEM_MAPPED para una vista de datos o MEM_IMAGE para una vista de imagen. Para saber con certeza si una página se privatizó, lo más seguro es dejar la página residente y observar el bit Shared de QueryWorkingSetEx.
- ¿Se puede guardar un puntero crudo en memoria compartida?
- Normalmente se evita. Aunque sea la misma sección, no hay garantía de que la vista de cada proceso se coloque en la misma dirección virtual. En las estructuras compartidas se usan desplazamientos desde la base, enteros de ancho fijo, y un diseño explícito de disposición y sincronización.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.