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: · · 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.

  1. 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
  2. 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
  3. 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_COPY carga 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 de QueryWorkingSetEx.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.

Correspondencia de distintas direcciones virtuales con la misma página físicaEl 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ónProcess A: 0x000001A00000 + 0x3000Desplazamiento de sección 0x3000Process B: 0x000002700000 + 0x3000La 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.

Cómo se separan el respaldo por archivo y por archivo de paginaciónSi 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 objetoPasar el identificador de un archivo realPasar INVALID_HANDLE_VALUECreateFileMappingSección respaldada por archivoSección respaldada por el archivo de paginaciónLas páginas clean se pueden releer del archivo originalEl 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.

Estado compartido antes de la copia en escrituraAntes 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 cualProcess A PTEPFN X compartido (read / copy-on-write)Process B PTE

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.

  1. Obtiene una página física para el proceso A.
  2. Copia el contenido de PFN X a la nueva página PFN Y.
  3. Sustituye el PTE del proceso A para que apunte a PFN Y.
  4. Cambia la protección del lado del proceso A a lectura y escritura habituales.
  5. Vuelve a ejecutar la instrucción de escritura que falló.
Estado ramificado después de la copia en escrituraTras 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 XAl escribir se copia el contenidoProcess A PTEPFN Y privado (read/write, ya modificado)Process B PTEPFN X compartido (contenido original)

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.

  1. Acceder a la página de destino y dejarla residente.
  2. Obtener la información de Working Set de la página con QueryWorkingSetEx.
  3. Mirar el bit Shared.
  4. 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».

Tres rutas unidas al mismo flujo de archivoReadFile/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_POINTERSReadFile / WriteFile con cachéSharedCacheMapError de asignación de datosDataSectionObjectError de imagen de EXE/DLLImageSectionObjectEl mismo flujo de archivo (SECTION_OBJECT_POINTERS)

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.

  1. Inicie Process Explorer como administrador.
  2. Inicie dos cmd.exe.
  3. Elija View > Lower Pane View > DLLs.
  4. Confirme en ambos procesos la ruta y la asignación del mismo DLL.
  5. Abra cada cmd.exe en 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, VirtualQuery sigue devolviendo MEM_MAPPED/MEM_IMAGE, así que se confirma con el bit Shared de QueryWorkingSetEx.7
  • Con FILE_MAP_COPY se 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, DataSectionObject e ImageSectionObject, 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

Á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.

Referencias

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

  7. Microsoft Learn, VirtualQuery function. Sobre que tras CoW Type sigue siendo MEM_MAPPED/MEM_IMAGE y que la privatización se puede confirmar con el bit Shared de QueryWorkingSetEx. ↩ ↩2 ↩3 ↩4

  8. 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. ↩

  9. 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

  10. 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. ↩

  11. 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 recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

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.

Volver al blog