Kedalaman memori Windows (bagian 3) — Objek section dan copy-on-write: wujud DLL dan pemetaan berkas

· Diperbarui pada: · · Windows, Manajemen memori, Memori bersama, Pemetaan berkas, Copy-on-Write, DLL, Cache Manager

Riwayat revisi (5 pembaruan, terakhir pada 31 Aug 2026)

Catatan perubahan yang dilakukan pada artikel ini. Jika versi sebelumnya telah diarsipkan, versi itu tetap dapat dibaca melalui tautan permanen dengan DOI.

Diterjemahkan ulang sebagai terjemahan lengkap dari naskah Jepang. Versi bahasa Indonesia sebelumnya adalah ringkasan yang hanya memindahkan sebagian naskah, sehingga bagian, tabel, gambar Mermaid, keterangan gambar, dan FAQ tidak ada. Semuanya dipulihkan sesuai naskah Jepang, dan klaim teknisnya sama dengan versi Jepang.
Relasi peta pengetahuan ditinjau ulang secara menyeluruh, dan relasi yang tidak selaras dengan teks penjelasan (arah terbalik, generalisasi berlebih, predikat yang tertukar) diperbaiki. Penjelasan di tubuh artikel tidak diubah.
Relasi peta pengetahuan ditinjau ulang secara menyeluruh, dan relasi yang tidak selaras dengan teks penjelasan (arah terbalik, generalisasi berlebih, predikat yang tertukar) diperbaiki. Penjelasan di tubuh artikel tidak diubah.
Relasi peta pengetahuan «SECTION_OBJECT_POINTERS tidak kompatibel dengan Cache Manager» diubah menjadi «Cache Manager memakai struktur ini lewat SharedCacheMap (jalur image fault tidak melewati Cache Manager)». Penjelasan di tubuh artikel tidak diubah.
Ritme tulisan dan susunan paragraf ditinjau ulang agar lebih mudah dibaca, dan diagram ditambahkan pada penjelasan mekanisme. Isi teknis, kode, dan tautan referensi tidak diubah.
Publikasi pertama
Mengutip artikel ini(DOI (arsip terdaftar): 10.5281/zenodo.22176184)

DOI di bawah mengarah ke versi yang telah diarsipkan sebelumnya dan mungkin berbeda dari teks saat ini. Gunakan URL halaman ini untuk merujuk teks saat ini.

Go Komura (2026). Kedalaman memori Windows (bagian 3) — Objek section dan copy-on-write: wujud DLL dan pemetaan berkas. KomuraSoft LLC. https://comcomponent.com/id/blog/windows-memory-internals-section-copy-on-write/

DOI (arsip terdaftar)
10.5281/zenodo.22176184
DOI (versi terakhir yang didaftarkan)
10.5281/zenodo.22176185

Di artikel sebelumnya, «Kedalaman memori Windows (bagian 2) — Hidup sebuah halaman fisik», kita mengikuti bagaimana halaman fisik yang meninggalkan Working Set bergerak lewat Modified, Standby, Free, dan Zeroed. Standby itu juga menahan halaman dari DLL, EXE, berkas yang dipetakan, dan cache berkas.

Dari situ muncul pertanyaan. Ketika 100 proses memakai kernel32.dll yang sama, apakah Windows menaruh 100 set halaman kode di RAM? Ketika dua proses memetakan berkas yang sama ke memori, dapatkah proses kedua memakai halaman yang sudah dibaca proses pertama?

Jawabannya: beberapa alamat virtual dipetakan ke halaman fisik yang sama. Satuan berbagi itu diwakili objek section, dan mekanisme yang memecah berbagi hanya pada saat menulis adalah copy-on-write (CoW).

Artikel ini mengikuti bagaimana EXE dan DLL, berkas data, memori bersama yang ditopang page file, dan cache berkas menempel pada aliran berkas yang sama, serta di mana jalur pemrosesan berpisah. Cara membaca angkanya mengandaikan artikel pengantar «Apa arti “pemakaian memori” Windows sebenarnya?».

«Kedalaman memori Windows» — ketiga bagian

  1. Bagian 1: alamat virtual dan page fault
    Mengikuti saat halaman virtual yang sudah di-Commit memperoleh RAM fisik.
  2. Bagian 2: hidup sebuah halaman fisik
    Mengikuti transisi keadaan halaman yang meninggalkan Working Set.
  3. Bagian 3 (artikel ini): objek section dan copy-on-write
    Mengikuti mekanisme DLL, pemetaan berkas, dan memori bersama berbagi halaman fisik.

Pertanyaan yang dijawab bagian 3 hanya satu.

Mengapa beberapa proses dapat memakai DLL atau berkas yang sama sebagai satu set halaman fisik?

Pembaca yang dituju adalah pengembang dan operator yang ingin memahami berbagi DLL, CreateFileMapping, MapViewOfFile, memori bersama, CoW, dan hubungan dengan cache berkas bukan hanya dari pemakaian API, melainkan dari struktur internal. Prasyarat adalah Windows 10/11 atau Windows Server terkini, dan latar yang dibutuhkan adalah dasar alamat virtual, page fault, dan Working Set. Tingkat kesulitannya menengah. Istilah internal seperti Control Area dan Prototype PTE ikut dipakai, tetapi pengamatan dapat diulang dengan VMMap, Process Explorer, dan QueryWorkingSetEx.

Pada diagram, garis utuh menunjukkan relasi yang selalu berlaku dan garis putus-putus menunjukkan relasi bersyarat (syaratnya ada pada penjelasan masing-masing relasi di halaman rincian). Daftar lengkap relasi (total 19, beserta bukti dan tingkat kepastian) serta definisi konsep utama dikumpulkan di halaman rincian peta pengetahuan (dalam bahasa Jepang). Data: JSON-LD / Turtle

1. Kesimpulan dulu

Berikut gambaran keseluruhannya.

  • Objek section mewakili rentang memori yang dapat dibagi.
    Tiap proses memetakan sebagian section itu ke ruang virtualnya sendiri sebagai «view».1
  • View dari section yang sama tidak harus berada di alamat virtual yang sama.
    0x000001... pada proses A dan 0x000002... pada proses B dapat menunjuk offset section yang sama dan halaman fisik yang sama.2
  • Ada section file-backed dan section pagefile-backed.
    Yang pertama memakai berkas nyata; yang kedua dipakai untuk memori bersama tanpa berkas eksplisit, dan sejenisnya.2
  • Pemuatan EXE/DLL adalah section image; pemetaan berkas biasa adalah section data.
    Dengan SEC_IMAGE, atribut section di dalam PE yang menentukan proteksi halaman.3
  • Selama dibaca, halaman fisik yang sama dapat dibagi.
    Ketika salah satu sisi menulis ke halaman CoW, hanya halaman itu yang disalin dan PTE proses penulis diganti.4
  • Meski pada aliran berkas yang sama, jalur cache, data, dan image berpisah.
    Cached I/O Cache Manager memakai SharedCacheMap, pemetaan data memakai DataSectionObject, dan EXE/DLL memakai ImageSectionObject. Ketiganya menempel pada aliran berkas yang sama lewat SECTION_OBJECT_POINTERS, tetapi image fault tidak melewati Cache Manager.5
  • Private Bytes saja tidak dapat memastikan bahwa CoW terjadi.
    FILE_MAP_COPY membebankan Commit untuk seluruh view di muka, untuk berjaga-jaga jika kelak setiap halaman diprivatisasi. Pemeriksaan per halaman memakai bit Shared pada QueryWorkingSetEx.67

Dalam satu kalimat: yang dibagi bukan alamat virtual, melainkan isi di dalam section dan halaman fisik yang sesuai pada saat itu.

2. Objek section dan view

Menurut definisi Microsoft, objek section mewakili wilayah memori yang dapat dibagi, dan sekaligus merupakan mekanisme yang memetakan berkas ke ruang alamat proses.1

Kuncinya adalah memisahkan section itu sendiri dari view yang terlihat dari tiap proses.

Konsep Peran
Objek section Mewakili isi yang dibagi, ukuran, backing store, dan batas atas proteksi
View Menunjukkan sebagian section sebagai rentang alamat virtual pada suatu proses
PTE Mengikat tiap halaman virtual dalam view ke halaman fisik saat ini atau ke keadaan yang belum diwujudkan
PFN Mewakili halaman fisik yang benar-benar ada di RAM

Di Win32, CreateFileMapping mengembalikan handle objek pemetaan berkas, dan MapViewOfFile membuat view di ruang virtual proses.3

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

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

Memanggil CreateFileMapping saja belum memberi alamat yang dapat dibaca proses. Membuat view pun tidak langsung menaruh semua halaman ke RAM. Dari halaman yang pertama kali disentuh, page fault membaca isi berkas dan mengikat halaman fisik ke PTE.8 Dengan kata lain, demand paging yang dibahas di bagian 1 berlaku juga untuk view section.

2.1. Section yang sama, alamat virtual berbeda

Bahkan ketika proses A dan proses B memetakan offset yang sama dari section yang sama, alamat awal view dapat berbeda.

Pemetaan dari alamat virtual berbeda ke halaman fisik yang samaProses A dan proses B masing-masing punya view di alamat virtual berbeda, tetapi keduanya mencapai halaman fisik PFN X yang sama lewat offset section yang samaProcess A: 0x000001A00000 + 0x3000Offset section 0x3000Process B: 0x000002700000 + 0x3000Halaman fisik yang sama PFN X

Gambar 1: Yang dibagi adalah isi di dalam section dan halaman fisik, bukan alamat virtual.

Itulah sebabnya pointer mentah tidak boleh disimpan di memori bersama. Nilai pointer proses A bisa menjadi alamat yang tidak berkaitan di proses B.

Dalam struktur bersama, pakai offset dari awal view, integer lebar tetap, serta versi dan perataan yang dinyatakan secara eksplisit. Dokumentasi MapViewOfFileEx Microsoft juga menganjurkan menyimpan offset dari basis, bukan pointer, karena tidak ada jaminan alamat yang sama akan tersedia di kemudian hari.6

3. File-backed dan pagefile-backed

Section terbagi menjadi dua jenis besar menurut tempat isi dipulihkan.

Pemisahan file-backed dan pagefile-backedMemberi berkas nyata ke CreateFileMapping menghasilkan section file-backed, dan halaman bersih dapat dibaca ulang dari berkas asli. Memberi INVALID_HANDLE_VALUE menghasilkan section pagefile-backed; page file menopang isi, yang hilang saat objek dihancurkanBerikan handle berkas nyataBerikan INVALID_HANDLE_VALUECreateFileMappingSection file-backedSection pagefile-backedHalaman bersih dapat dibaca ulang dari berkas asliIsi ditopang page file dan hilang saat dihancurkan

Gambar 2: Perbedaan backing store menentukan dari mana isi dapat dipulihkan dan berapa lama ia hidup.

3.1. Section file-backed

Memberi berkas nyata ke CreateFileMapping menghasilkan section file-backed.

  • View hanya-baca membaca halaman yang dibutuhkan dari berkas.
  • Perubahan pada view baca/tulis diperlakukan sebagai data berkas itu.
  • Perubahan pada view CoW tidak ditulis ke berkas asli; halaman itu menjadi privat.

Jika halaman file-backed bersih, halaman fisik dapat dibuang dan dibaca ulang dari berkas asli. Sifat itu yang menopang efisiensi Standby dan cache berkas yang dibahas di bagian 2.

3.2. Section pagefile-backed

Memberi INVALID_HANDLE_VALUE sebagai hFile pada CreateFileMapping dan menentukan ukuran menghasilkan section pagefile-backed.

HANDLE mapping = CreateFileMappingW(
    INVALID_HANDLE_VALUE,
    nullptr,
    PAGE_READWRITE,
    0,
    64 * 1024,
    L"Local\\KomuraMemoryDemo");

Ini section tanpa berkas data eksplisit, ditopang page file. Isi awal adalah nol, dan beberapa proses dapat membuka objek yang sama lewat nama, pewarisan handle, DuplicateHandle, dan sejenisnya.93 Perubahan terlihat oleh proses yang memetakan halaman bersama yang sama. Di sisi lain, ketika objek section dihancurkan isinya tidak tersisa, jadi tidak cocok untuk meninggalkan berkas yang persisten.2

Catatan: memori bersama tidak otomatis membawa saling-kecualian. Mutex, semaphore, event, protokol lock-free, atau sejenisnya dirancang terpisah.9

4. Pemetaan image dan pemetaan data

Ketika dikatakan «EXE atau DLL juga pemetaan berkas», perbedaan dari berkas data biasa tetap perlu diingat.

Butir Pemetaan image Pemetaan data
Penggunaan utama Pemuatan EXE atau DLL Berkas biasa, data bersama
Atribut pembuatan SEC_IMAGE PAGE_READONLY, PAGE_READWRITE, dan sejenisnya
Proteksi halaman Atribut di dalam image PE yang memutuskan Spesifikasi pemetaan dan view yang memutuskan
Penulisan Dapat diprivatisasi lewat section yang dapat ditulis atau CoW Penulisan bersama atau CoW dapat dipilih
Type VirtualQuery MEM_IMAGE MEM_MAPPED

Dengan SEC_IMAGE, atribut section milik image yang dieksekusi sendirilah yang lebih menentukan proteksi halaman view daripada nilai proteksi biasa yang diberi ke CreateFileMapping.3

Lewat mekanisme ini, halaman yang tidak diubah seperti kode dapat berbagi halaman fisik yang sama di banyak proses, dan hanya halaman yang membutuhkan perubahan milik proses yang bercabang lewat CoW. Meski begitu, tidak setiap halaman DLL pasti dibagi — karena relokasi ASLR, perbaikan loader, hotpatch, atribut section PE yang sebenarnya, dan sebagainya.

Rancangan yang penting adalah bagi dulu halaman yang dapat dibagi, lalu salin secara malas hanya halaman yang perlu diubah.

5. Copy-on-write dari awal sampai akhir

Mari diikuti dari keadaan dua proses sedang membaca halaman CoW yang sama hingga proses A menulis satu byte.

5.1. Sebelum penulisan

Sebelum penulisan terjadi, PTE kedua proses secara konseptual mencapai halaman bersama yang sama, dan pembacaan berhasil apa adanya.

Keadaan bersama sebelum copy-on-writeSebelum penulisan terjadi, PTE proses A dan PTE proses B keduanya mencapai halaman bersama PFN X yang sama, dan pembacaan berhasil apa adanyaProcess A PTEPFN X bersama (read / copy-on-write)Process B PTE

Gambar 3: Sebelum penulisan, PTE kedua proses menunjuk ke halaman fisik yang sama.

5.2. Protection fault saat menulis

Halaman CoW sejak awal bukan halaman bersama yang dapat ditulis secara biasa. Ketika proses A mencoba menulis, CPU menaikkan protection fault. Memory manager yang menerima kendali menilai ini bukan penulisan ilegal, melainkan penulisan ke atribut CoW.

5.3. Membuat halaman fisik baru

Atas penilaian itu, Windows melakukan hal berikut.

  1. Memperoleh satu halaman fisik untuk proses A.
  2. Menyalin isi PFN X ke halaman baru PFN Y.
  3. Mengganti PTE proses A ke PFN Y.
  4. Mengubah proteksi sisi proses A menjadi baca/tulis biasa.
  5. Mengeksekusi ulang instruksi tulis yang gagal.
Keadaan terpisah setelah copy-on-writeSetelah penulisan proses A, hanya PTE proses A yang diganti ke halaman privat PFN Y yang menerima salinan isi, sementara PTE proses B terus menunjuk ke halaman bersama asli PFN XIsi disalin saat menulisProcess A PTEPFN Y privat (read/write, setelah diubah)Process B PTEPFN X bersama (isi asli)

Gambar 4: Hanya PTE proses yang menulis yang diganti ke halaman privat baru; sisi lain terus membaca isi asli.

Proses B terus membaca isi asli dan tidak melihat perubahan proses A. Itulah Copy-on-Write. Berbagi DLL dan FILE_MAP_COPY memakai prinsip yang sama: jangan salin sampai ada penulisan.46

5.4. Perbedaan dari FILE_MAP_WRITE

Halaman yang ditulis bersama lewat FILE_MAP_WRITE dirancang agar perubahan satu sisi juga terlihat dari view lain yang memakai pemetaan berkas yang sama. Dengan FILE_MAP_COPY, sebaliknya, hanya halaman yang ditulis yang menjadi milik proses; perubahan tidak ditulis kembali ke berkas asli dan hilang ketika view dilepas.6

Apakah tujuannya «menyampaikan pembaruan lewat memori bersama», atau «tiap proses membuat perubahan privat dari data awal bersama»? Pilihan yang tepat berlawanan, tergantung tujuannya.

6. Setelah CoW tetap MEM_MAPPED / MEM_IMAGE

Halaman setelah CoW secara fisik sudah Private. Dari situ mudah dikira Type VirtualQuery juga berubah menjadi MEM_PRIVATE, tetapi sebenarnya view data tetap MEM_MAPPED dan image yang dieksekusi tetap MEM_IMAGE. VirtualQuery melaporkan dari alokasi awal mana wilayah itu berasal.7

Untuk melihat per halaman apakah CoW sudah terjadi, pakai prosedur berikut.

  1. Akses halaman sasaran dan jadikan resident.
  2. Ambil informasi Working Set halaman dengan QueryWorkingSetEx.
  3. Lihat bit Shared.
  4. Jika Shared == 0, halaman resident itu Private.

Saat mengonfirmasi di VMMap, lihat bukan hanya Type wilayah, melainkan rincian Private/Shareable Working Set.

6.1. Private Bytes mungkin tidak naik

Dengan FILE_MAP_COPY, proses mungkin kelak menulis ke setiap halaman dalam view. Karena itu Windows mengambil commit charge setara seluruh view pada saat pemetaan.6 Akibatnya, menulis halaman pertama tidak selalu menaikkan Private Bytes sebesar 4KiB pada saat itu.

Metrik yang diutamakan saat mengamati CoW adalah berikut.

  • Bit Shared pada QueryWorkingSetEx
  • Private WS / Shareable WS di VMMap
  • Informasi halaman fisik RAMMap
  • Private Bytes sebagai informasi pelengkap

Jika «apakah Private Bytes naik pada saat menulis» dipakai sebagai tes lulus/gagal, CoW yang bekerja dengan benar bisa terlewat.

7. Titik temu dengan Cache Manager — memisahkan tiga jalur

Mengatakan «pemuatan EXE/DLL, cache berkas, dan memori bersama semuanya section» memberi gambaran keseluruhan, tetapi implementasi tidak boleh diratakan menjadi satu objek.

Aliran berkas memiliki SECTION_OBJECT_POINTERS, yang dipakai memory manager dan Cache Manager.

typedef struct _SECTION_OBJECT_POINTERS {
    PVOID DataSectionObject;
    PVOID SharedCacheMap;
    PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;
  • DataSectionObject: keadaan section untuk berkas data
  • SharedCacheMap: view cache yang dilacak Cache Manager
  • ImageSectionObject: keadaan section untuk image yang dieksekusi

Dokumentasi Microsoft menjelaskan bahwa struktur ini mengikat objek berkas ke section aliran berkas dan mengikuti isi di memori serta informasi cache.5

Di sini seri I/O dan seri memori bertemu. Namun pahami ketiga jalur secara terpisah.

  • ReadFile / WriteFile yang di-cache memakai SharedCacheMap dan view cache Cache Manager.
  • Fault pemetaan pada berkas data ditangani memory manager di sisi DataSectionObject. Ia bekerja sama dengan cached I/O pada aliran berkas yang sama agar isi tetap konsisten.
  • Image fault pada EXE/DLL ditangani memory manager dengan ImageSectionObject dan I/O paging. Itu bukan jalur yang melewati SharedCacheMap Cache Manager.
Tiga jalur yang menempel pada aliran berkas yang samaReadFile/WriteFile yang di-cache memakai SharedCacheMap, fault pemetaan data memakai DataSectionObject, dan image fault EXE/DLL memakai ImageSectionObject; ketiganya menempel pada aliran berkas yang sama lewat SECTION_OBJECT_POINTERScached ReadFile / WriteFileSharedCacheMapFault pemetaan dataDataSectionObjectImage fault EXE/DLLImageSectionObjectAliran berkas yang sama (SECTION_OBJECT_POINTERS)

Gambar 5: Ketiga jalur diproses terpisah, tetapi menempel pada aliran berkas yang sama.

Kesamaan ketiganya bukan bahwa «semuanya masuk Cache Manager», melainkan bahwa aliran berkas yang sama mengikat keadaan terpisah — cache, section data, dan section image — lewat SECTION_OBJECT_POINTERS.5 Pembacaan dan penulisan cache, Lazy Writer, dan hubungan Cc dengan Mm dibahas di «Kedalaman I/O Windows (bagian 4) — Cache Manager dan WriteFile».

Perhatikan bahwa ketika view yang dipetakan ke memori dicampur dengan ReadFile/WriteFile, tidak dijamin selalu terlihat isi pada saat yang sama. Rancangan perlu mencakup sinkronisasi, flush, dan mode berbagi berkas.36

8. Masa hidup objek dan view

Menutup handle CreateFileMapping saja tidak menghilangkan view yang sudah ada. View menahan referensi internal ke section, dan baru setelah setiap view di-UnmapViewOfFile dan setiap handle di-CloseHandle objek dapat dihancurkan.3

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

Pemisahan masa hidup ini menjadi penyebab fenomena «berkas sudah ditutup, tetapi masih dipakai». Bahkan setelah handle berkas ditutup, jika section image atau view data masih merujuk aliran berkas, penutupan akhir berkas datang belakangan.

Hubungan dengan cleanup/close di sisi I/O dibahas di «Kedalaman I/O Windows (bagian 1)», dan jebakan implementasi di «Jebakan memori bersama dan praktik terbaik di lapangan».

9. Lihat sendiri

9.1. Melihat DLL yang sama dari dua proses

Pertama, konfirmasikan berbagi DLL dengan proses yang sudah ada.

  1. Jalankan Process Explorer sebagai administrator.
  2. Jalankan dua proses cmd.exe.
  3. Pilih View > Lower Pane View > DLLs.
  4. Konfirmasikan jalur dan pemetaan DLL yang sama di kedua proses.
  5. Buka tiap cmd.exe di VMMap dan bandingkan Images Working Set, Private, dan Shareable.

Melihat DLL yang sama di Process Explorer adalah bukti keduanya memetakan image yang sama. Namun itu saja tidak membuktikan PFN tiap halaman cocok. Gabungkan rincian Shareable VMMap, RAMMap, dan QueryWorkingSetEx untuk mengonfirmasi berbagi per halaman. Process Explorer dan VMMap disediakan Sysinternals.1011

9.2. Mengamati FILE_MAP_COPY dari dua proses

Program berikut memetakan berkas yang sama sebagai view CoW dan menampilkan bit Shared pada QueryWorkingSetEx. Objek pemetaan berkas dibuat dengan PAGE_READONLY, tetapi proteksi itu kompatibel dengan view FILE_MAP_COPY, dan penulisan pertama di sisi view memicu 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);
}

Bangun dan siapkan.

cl /std:c++20 /EHsc /W4 cow_demo.cpp

$path = "$env:TEMP\\cow-demo.bin"
[IO.File]::WriteAllBytes($path, [byte[]]::new(65536))

Lalu buka berkas yang sama dari dua konsol.

.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" read
.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" write

Setelah keduanya dimulai, tekan Enter dulu di sisi read lalu di sisi write, dan pastikan Shared terpasang pada before di keduanya. Tekan Enter sekali lagi di sisi write dan halaman proses itu menjadi Shared 0. Lalu tekan Enter di sisi read dan dapat dipastikan sisi reader terus membaca halaman asli. Type VirtualQuery tetap MEM_MAPPED setelah penulisan juga.

Perhatikan bahwa PrintPage menyentuh ulang halaman sasaran tepat sebelum kueri, dan jika Valid == 0 ia tidak menafsirkan Shared dan ShareCount serta mencoba ulang hingga tiga kali. Jika halaman masih tidak resident, ia tidak menghasilkan hasil dan melaporkan itu. ShareCount dapat berubah dengan waktu dan tekanan memori, jadi lihat perubahan bit Shared yang dikonfirmasi pada Valid == 1, bukan nilai tetap.

10. Lima salah baca yang dihindari dalam praktik

10.1. «Memori bersama memakai alamat virtual yang sama»

Yang dibagi adalah section dan halaman fisik. Alamat virtual view dapat berbeda per proses, jadi simpan offset, bukan pointer mentah.

10.2. «Jika DLL-nya sama, setiap halaman pasti dibagi»

Halaman kode yang bersih mudah dibagi, sementara relokasi, section yang dapat ditulis, CoW, dan keadaan resident pada saat pengukuran juga menghasilkan halaman Private.

10.3. «Setelah CoW menjadi MEM_PRIVATE»

Type VirtualQuery tetap MEM_MAPPED atau MEM_IMAGE. Konfirmasikan keadaan berbagi yang sebenarnya dengan QueryWorkingSetEx.7

10.4. «Jika Private Bytes tidak naik, CoW tidak terjadi»

FILE_MAP_COPY membebankan Commit untuk seluruh view di muka. Utamakan Private WS dan bit Shared.6

10.5. «Jika halaman dibagi, sinkronisasi tidak perlu»

Melihat halaman fisik yang sama dan dapat memperbaruinya dengan aman dari beberapa inti CPU adalah masalah berbeda. Rancang atomisitas, urutan memori, saling-kecualian, keadaan antara saat crash, dan kompatibilitas versi.

Gagasan mengikuti referensi dan masa hidup secara terpisah juga berlaku pada masalah proses yang tertinggal setelah interop COM Excel. Lihat juga «Penyebab proses tertinggal pada interop COM Excel».

11. Ringkasan

  • Objek section mewakili rentang memori yang dapat dibagi, dan tiap proses memetakannya ke ruang virtualnya sendiri sebagai view.1
  • Offset yang sama dari section yang sama dipetakan dari alamat virtual berbeda ke halaman fisik yang sama.2
  • Section file-backed menopang berkas nyata; section pagefile-backed menopang memori bersama bernama dan sejenisnya.9
  • EXE/DLL diperlakukan sebagai section image dan berkas biasa sebagai section data; proteksi dan tujuan penulisan kembali berbeda.3
  • CoW membagi halaman fisik saat membaca dan, pada penulisan pertama, menyalin hanya halaman itu lalu mengganti PTE.4
  • Setelah CoW, VirtualQuery masih mengembalikan MEM_MAPPED/MEM_IMAGE, jadi konfirmasi memakai bit Shared pada QueryWorkingSetEx.7
  • Dengan FILE_MAP_COPY, Commit seluruh view dibebankan lebih dulu, jadi CoW tidak dapat dinilai dari Private Bytes saja.6
  • Cached I/O Cache Manager, pemetaan data, dan pemetaan image menempel pada aliran berkas yang sama sebagai jalur terpisah yang memakai SharedCacheMap, DataSectionObject, dan ImageSectionObject.5
  • Memori bersama menjadi aman hanya jika masa hidup view dan handle, sinkronisasi, ACL, dan rancangan offset ikut disertakan.

Dengan ini ketiga bagian «Kedalaman memori Windows» selesai. Alamat virtual di-Reserve/Commit, halaman fisik diperoleh lewat page fault, halaman dipindahkan dari Working Set ke daftar halaman, dibagi lewat section, dan hanya halaman yang ditulis yang bercabang lewat CoW — manajemen memori Windows terhubung sebagai satu alur ini.

Artikel terkait

Area konsultasi terkait

KomuraSoft LLC menangani investigasi masalah memori bersama, pemetaan berkas, pemuatan DLL, penguncian berkas, komunikasi antarproses, dan pemakaian memori pada aplikasi Windows.

Tautan referensi

  1. Microsoft Learn, Section Objects and Views. Tentang objek section yang mewakili rentang memori yang dapat dibagi, dan tiap proses yang memetakan sebagian section sebagai view. ↩ ↩2 ↩3

  2. Microsoft Learn, File-Backed and Page-File-Backed Sections. Tentang section file-backed dan pagefile-backed, CoW, dan kemampuan berbagi memori fisik yang sama dari alamat virtual proses berbeda. ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, CreateFileMappingW function. Tentang objek pemetaan berkas, section pagefile-backed, SEC_IMAGE, masa hidup view dan handle, serta konsistensi di antara view yang menopang berkas yang sama. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  4. Microsoft Learn, Memory Protection. Tentang beberapa proses yang berbagi halaman fisik DLL yang sama, dan CoW yang menyalin ke halaman fisik baru lalu memperbarui PTE ketika salah satu sisi menulis. ↩ ↩2 ↩3

  5. Microsoft Learn, SECTION_OBJECT_POINTERS structure. Tentang DataSectionObject, SharedCacheMap, dan ImageSectionObject yang mengikat pemetaan aliran berkas dan informasi cache ke memory manager / Cache Manager. ↩ ↩2 ↩3 ↩4

  6. Microsoft Learn, MapViewOfFileEx function. Tentang CoW dengan FILE_MAP_COPY; halaman privat yang ditopang page file; pembebanan commit charge untuk seluruh view; dan menyimpan offset alih-alih alamat virtual. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  7. Microsoft Learn, VirtualQuery function. Tentang Type yang tetap MEM_MAPPED/MEM_IMAGE setelah CoW, dan kemampuan mengonfirmasi privatisasi dengan bit Shared pada QueryWorkingSetEx. ↩ ↩2 ↩3 ↩4

  8. Microsoft Learn, Managing Memory Sections. Tentang memori fisik yang tidak dialokasikan sampai view diakses, dan page fault akses pertama yang membaca isi berkas. ↩

  9. Microsoft Learn, Sharing Files and Memory. Tentang berbagi objek pemetaan berkas yang sama lewat nama atau handle; membuat memori bersama pagefile-backed dengan INVALID_HANDLE_VALUE; dan sinkronisasi yang diperlukan terpisah. ↩ ↩2 ↩3

  10. Microsoft Learn, Process Explorer - Sysinternals. Tentang Process Explorer yang dapat menampilkan handle proses serta DLL / berkas yang dipetakan ke memori yang dimuat. ↩

  11. Microsoft Learn, VMMap - Sysinternals. Tentang VMMap yang merinci memori virtual proses menjadi Image, Mapped File, Private, dan sejenisnya, serta menampilkan rincian Private/Shareable Working Set. ↩

Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.

Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.

Artikel ini berkaitan langsung dengan layanan berikut.

Pertanyaan yang sering diajukan

Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.

Apakah setiap proses yang memakai DLL yang sama mendapat salinan penuh DLL itu di RAM?
Biasanya tidak. Halaman image yang sama dan belum diubah dipetakan dari alamat virtual berbeda di tiap proses ke halaman fisik yang sama. Hanya halaman yang perlu ditulis yang menjadi halaman fisik milik proses, lewat copy-on-write dan mekanisme sejenis.
Apakah CreateFileMapping mengalokasikan memori ke proses pada saat itu?
CreateFileMapping membuat objek pemetaan berkas, tetapi yang menampilkannya di ruang virtual proses adalah MapViewOfFile. Lebih lanjut, halaman fisik view biasanya diwujudkan oleh page fault, mulai dari halaman yang pertama kali diakses.
Apa perbedaan FILE_MAP_WRITE dan FILE_MAP_COPY?
Perubahan lewat FILE_MAP_WRITE adalah penulisan yang diterapkan ke data berkas yang dibagi. FILE_MAP_COPY membagi halaman awal, tetapi hanya halaman yang ditulis yang menjadi salinan milik proses; perubahan tidak ditulis kembali ke berkas asli dan hilang ketika view dilepas.
Setelah copy-on-write, apakah VirtualQuery mengembalikan MEM_PRIVATE?
Tidak. View data tetap MEM_MAPPED; view image tetap MEM_IMAGE. Untuk melihat apakah halaman benar-benar sudah menjadi privat, jadikan halaman resident lalu lihat bit Shared pada QueryWorkingSetEx.
Bolehkah menyimpan pointer mentah di memori bersama?
Biasanya dihindari. Meski section-nya sama, tidak ada jaminan view tiap proses diletakkan di alamat virtual yang sama. Dalam struktur bersama, pakai offset dari basis, integer lebar tetap, serta tata letak dan skema sinkronisasi yang eksplisit.

Profil penulis

Halaman perkenalan penulis artikel.

Go Komura

Direktur KomuraSoft LLC

Berspesialisasi dalam pengembangan perangkat lunak Windows, konsultasi teknis, dan investigasi bug, terutama pada proyek dengan sistem yang sudah ada dan bug yang sulit direproduksi.

Kembali ke blog