WriteFile mengembalikan sukses. Jadi, di mana data sekarang?
Jawabannya, hampir pasti belum ada di disk. Hanya disalin ke cache di memori. Karena itu «sudah disimpan, tetapi setelah pemutusan daya kembali, hilang» terjadi, karena itu tolok ukur salin file mengetuk kecepatan yang secara fisik mustahil, dan karena itu basis data dengan taat memanggil fsync.
Bagian 4 seri «Kedalaman I/O Windows» adalah Cache Manager yang berdiri di antara ini. Di bagian 2 kita menulis «jika ada di cache, I/O asinkron pun selesai secara sinkron», dan di bagian 1 kita menjadikan «jalan pintas yang tidak membuat IRP (Fast I/O)» sebagai pekerjaan rumah. Kali ini semua benang itu ditarik.
1. Intinya dulu
- Cache file Windows adalah cara write-back. Pembacaan dulu dari cache file sistem, penulisan juga dulu ke cache. Pencerminan ke disk dilakukan OS kemudian.1
- Identitas cache adalah file mapping. Cache Manager memetakan rentang 256 KB file ke ruang alamat sistem, dan baca tulis menjadi «salinan memori antara tampilan itu dan buffer aplikasi» (bab 2).1
- Penulisan dicerminkan kemudian oleh penulisan tertunda (lazy writer) setiap detik. Crash aplikasi tidak kehilangan data, tetapi pemutusan daya atau crash OS kehilangan cache dirty (bab 4).1
- Ada tiga alat «menulis dengan andal».
FlushFileBuffers(=Flush(true).NET),FILE_FLAG_WRITE_THROUGH,FILE_FLAG_NO_BUFFERING. Flush setiap kali pada penulisan yang sering tidak efisien, dan dokumentasi resmi mencantumkan kombinasi NO_BUFFERING+WRITE_THROUGH (bab 5).21 - NO_BUFFERING punya persyaratan penyelarasan. Ukuran dan offset adalah kelipatan ukuran sektor, alamat buffer juga diselaraskan ke batas sektor fisik. Dan bahkan dengan NO_BUFFERING, metadata terus di-cache (bab 5.3).31
- Tampilan yang dipetakan dan cache berbagi data yang sama. Memory-mapped file dan I/O cache biasa koheren, dan persistensi peta adalah dua tahap
FlushViewOfFile+FlushFileBuffers(bab 6).45 - Baca tulis sinkron yang ada di cache kadang bahkan tidak membuat IRP. Jalan pintas bernama Fast I/O pergi langsung ke Cache Manager — jawaban pekerjaan rumah bagian 1 (bab 7).6
Peta pengetahuan artikel ini
Sukses WriteFile tidak berarti persistensi ke disk; cache file Windows pertama-tama menyalin ke tampilan cache sistem, dan pencerminan ke disk diikuti kemudian setiap detik oleh lazy writer. Saat pemutusan daya atau crash OS, halaman dirty yang belum ditulis hilang, jadi untuk data yang ingin ditulis dengan andal perlu memilah FlushFileBuffers, FILE_FLAG_WRITE_THROUGH, dan FILE_FLAG_NO_BUFFERING.
flowchart LR
accTitle: Peta pengetahuan Cache Manager dan write-behind
accDescr: Diagram yang menunjukkan hubungan Cache Manager, cache write-back, tampilan cache sistem 256 KB, read-ahead, penulisan tertunda oleh lazy writer, kehilangan halaman dirty saat pemutusan daya, penulisan andal dengan FlushFileBuffers, FILE_FLAG_WRITE_THROUGH, dan FILE_FLAG_NO_BUFFERING, persyaratan penyelarasan, koherensi dengan memory-mapped file, serta Fast I/O
cache_manager["Cache Manager"]
write_behind_caching["cache write-back (metode penulisan tertunda)"]
memory_mapped_file["file mapping (memory-mapped file)"]
system_cache_view["tampilan cache sistem (slot 256 KB)"]
file_object["objek file"]
lazy_writer["lazy writer (thread penulisan tertunda)"]
unflushed_write_loss["kehilangan data yang belum ditulis ke disk"]
temp_file_attribute["FILE_ATTRIBUTE_TEMPORARY"]
dirty_page["halaman dirty"]
power_loss["pemutusan daya atau crash OS"]
read_ahead["read-ahead (pembacaan spekulatif)"]
sequential_scan_hint["FILE_FLAG_SEQUENTIAL_SCAN"]
random_access_hint["FILE_FLAG_RANDOM_ACCESS"]
flushfilebuffers["FlushFileBuffers"]
write_through["FILE_FLAG_WRITE_THROUGH"]
no_buffering["FILE_FLAG_NO_BUFFERING"]
sector_alignment_requirement["persyaratan penyelarasan sektor"]
invalid_parameter_error["ERROR_INVALID_PARAMETER(87)"]
frequent_durable_write["persistensi andal pada penulisan yang sering"]
flushviewoffile["FlushViewOfFile"]
fast_io["Fast I/O"]
procmon["Process Monitor (procmon.exe)"]
irp["IRP (paket permintaan I/O)"]
synchronous_io["I/O sinkron"]
cache_manager -->|"mengimplementasikan"| write_behind_caching
write_behind_caching -->|"menggunakan"| memory_mapped_file
cache_manager -->|"menggunakan"| system_cache_view
system_cache_view -->|"menggunakan"| memory_mapped_file
cache_manager -->|"menggunakan"| file_object
cache_manager -->|"menggunakan"| lazy_writer
lazy_writer -->|"mengotomatiskan"| write_behind_caching
lazy_writer -->|"mengurangi"| unflushed_write_loss
temp_file_attribute -.->|"tidak kompatibel"| lazy_writer
write_behind_caching -->|"dapat menyebabkan"| dirty_page
dirty_page -->|"disimpan di"| system_cache_view
power_loss -.->|"dapat menyebabkan"| unflushed_write_loss
cache_manager -.->|"mencegah"| unflushed_write_loss
cache_manager -->|"menggunakan"| read_ahead
read_ahead -.->|"dikonfigurasi dengan"| sequential_scan_hint
read_ahead -.->|"dikonfigurasi dengan"| random_access_hint
flushfilebuffers -->|"mencegah"| unflushed_write_loss
write_through -->|"mencegah"| unflushed_write_loss
no_buffering -.->|"mengurangi"| unflushed_write_loss
no_buffering -->|"mensyaratkan"| sector_alignment_requirement
sector_alignment_requirement -->|"mencegah"| invalid_parameter_error
no_buffering -->|"dapat menyebabkan"| invalid_parameter_error
flushfilebuffers -->|"tidak disarankan"| frequent_durable_write
no_buffering -->|"disarankan untuk"| frequent_durable_write
write_through -->|"disarankan untuk"| frequent_durable_write
no_buffering -->|"tidak kompatibel"| system_cache_view
flushviewoffile -->|"mensyaratkan"| memory_mapped_file
flushviewoffile -->|"sebaiknya didahului"| flushfilebuffers
fast_io -->|"diverifikasi dengan"| procmon
fast_io -->|"mensyaratkan"| system_cache_view
fast_io -->|"tidak kompatibel"| irp
fast_io -->|"mensyaratkan"| synchronous_io
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 32, beserta bukti dan tingkat kepastian) serta definisi konsep utama dikumpulkan di halaman rincian peta pengetahuan (dalam bahasa Jepang). Data: JSON-LD / Turtle
2. Identitas cache — file dipetakan ke memori
2.1. Slot 256 KB dan salinan memori
Jika cache file Windows dipikirkan sebagai «wadah blok disk», berbagai perilaku tidak dapat dijelaskan. Gambar yang benar adalah ini — Cache Manager memetakan rentang 256 KB file ke «slot» di ruang alamat sistem, dan baca tulis dengan cache aktif dieksekusi sebagai salinan memori antara slot itu dan buffer aplikasi.1
flowchart TB
subgraph U["Aplikasi (mode pengguna)"]
BUF["Buffer aplikasi<br/>(area yang diserahkan ke ReadFile/WriteFile)"]
end
subgraph S["Ruang alamat sistem"]
SLOT["Cache file sistem<br/>slot yang memetakan rentang 256 KB file"]
end
DISK[("File di disk")]
BUF <-->|"ReadFile/WriteFile =<br/>salinan memori dengan slot"| SLOT
SLOT <-->|"Pembacaan saat akses pertama dan<br/>penulisan kembali kemudian per halaman"| DISK
Gambar 1: Wajah nyata I/O dengan cache aktif. «Baca tulis file» dari sudut aplikasi, dalam banyak kasus, hanyalah salinan memori.
Satu poin yang mudah disalahpahami. 256 KB adalah granularitas tampilan (peta), bukan berarti I/O disk selalu dilakukan dalam satuan 256 KB. Halaman di dalam slot dibaca sesuai kebutuhan, dan jumlah I/O yang benar-benar terbang ke disk berubah menurut ukuran permintaan dan pola akses. Jika rentang yang dibaca pertama kali, I/O disk terjadi untuk mengisi (di sini IRP bagian 1 terbang ke storage stack di bawah). Jika sudah ada di cache, pembacaan selesai hanya dengan salinan. «Cache hit membuat penerbitan asinkron pun selesai secara sinkron» yang kita lihat di bab 5 bagian 2 adalah perwujudan gerakan «permintaan yang dapat dijawab segera diselesaikan di tempat». Sebaliknya, jika cache aktif tetapi halaman tidak ada di memori, tidak ada mekanisme asinkron untuk penanganan page fault, jadi pembacaan asinkron kadang diproses secara sinkron — jebakan yang juga kita lihat di bagian 2.7
2.2. Identitas «memori bebas berkurang»
Apakah cache dipakai dan keadaan read-ahead dikelola per cara membuka (per objek file),1 tetapi isi data yang di-cache dibagi per file (stream). Membuka file yang sama berkali-kali tidak membuat cache terpisah; isi cache yang sama terlihat dari handle mana pun (fondasi koherensi bab 6). Cache bergerak di bawah komando Cache Manager selama Windows berjalan.1 Jika file besar disalin atau banyak baca tulis, memori fisik kosong terus dialihkan ke cache. Meski memori bebas Task Manager tampak berkurang, sebagian besar adalah «memori siaga yang dipakai dengan cara bernilai, yang segera diserahkan jika aplikasi meminta». Agar diagnosis kekurangan memori tidak salah membedakan ini, praktik pengamatan juga dibahas di «Membedakan menunggu GC dan kebocoran memori di .NET».
Gerakan ini dapat dikonfirmasi di layar. Buka Task Manager > Kinerja > Memori, dan bilah «komposisi memori» di bawah terbagi menjadi sedang dipakai / diubah / siaga / kosong. Sebagian besar cache file masuk ke siaga ini, dan di daftar kanan dijumlahkan sebagai «sudah di-cache». Untuk melihat lebih rinci, tab Memori Resource Monitor menyusun pembagian yang sama dengan kapasitas. Setelah menyalin satu file beberapa GB lalu melihat lagi, siaga bertambah dan kosong berkurang, tetapi «sedang dipakai» hampir tidak berubah — yaitu, bukan «memori dimakan habis» melainkan «memori yang kosong dipakai sebagai cache» dapat dikonfirmasi dengan mata.
3. Read-ahead — spekulasi pembacaan
Cache Manager, dari pola akses masa lalu, membaca lebih dulu rentang yang kemungkinan dibaca berikutnya (read-ahead). Jika file dibaca berurutan, data lanjutan sudah ada di cache sebelum aplikasi memintanya — inilah rahasia kecepatan pembacaan sekuensial. Jumlah read-ahead tidak tetap; ia berubah menurut pola yang terdeteksi dan ukuran permintaan.
flowchart LR
A["Riwayat permintaan baca aplikasi<br/>membaca berurutan dari awal"]
D{"Cache Manager<br/>mendeteksi pola"}
R["Read-ahead: rentang lanjutan<br/>dibaca sebelum diminta<br/>(jumlah berubah menurut pola dan ukuran permintaan)"]
H1["Petunjuk FILE_FLAG_SEQUENTIAL_SCAN<br/>= read-ahead secara aktif"]
H2["Petunjuk FILE_FLAG_RANDOM_ACCESS<br/>= read-ahead menjadi sia-sia jadi ditekan"]
A --> D
D --> R
H1 -.-> D
H2 -.-> D
Gambar 2: Read-ahead. Selain deteksi pola akses, petunjuk dapat diberi lewat bendera CreateFile.
FileOptions.SequentialScan / RandomAccess yang diletakkan di tabel korespondensi bagian 1 adalah petunjuk ke mesin read-ahead ini. Untuk pemrosesan batch «menjilat semuanya» yang pertama, untuk akses seperti menelusuri indeks yang kedua — jika dipikirkan sebagai bendera untuk mengajarkan masa depan yang hanya diketahui aplikasi kepada OS, tempat pemakaiannya menjadi jelas.
4. Penulisan tertunda — arti «sukses» WriteFile
4.1. Lazy writer datang setiap detik
Sisi penulisan adalah cache write-back. WriteFile mengembalikan sukses pada saat data disalin ke slot, dan pencerminan ke disk ditunda. Kebijakan «tunda lalu tulis» ini adalah penulisan tertunda (lazy writing).1
Yang menanggung pencerminan adalah lazy writer yang dijalankan Cache Manager setiap detik. Ia menumpuk seperdelapan halaman yang belum di-flush baru-baru ini ke antrean dan menuliskannya, dan jika data yang harus ditulis banyak, ia menumpuk lebih banyak. Selain itu, file sementara yang dibuat dengan atribut FILE_ATTRIBUTE_TEMPORARY dikeluarkan dari sasaran flush lazy writer — karena menulis yang akan segera dihapus hanya sia-sia.1 Namun ini petunjuk lewat atribut, dan jika memori terdesak ia dapat ditulis kembali; file yang «namanya saja tampak sementara» tidak mendapatkannya.
sequenceDiagram
participant App as Aplikasi
participant C as Cache sistem
participant LW as lazy writer (berjalan setiap detik)
participant D as Disk
App->>C: WriteFile(data)
Note over C: Menyalin ke slot dan<br/>menandai halaman dirty (belum ditulis)
C-->>App: TRUE segera dikembalikan
Note over App,C: Dari sini sampai penulisan kembali adalah «jendela berbahaya»<br/>jika pemutusan daya / crash OS, data ini hilang
LW->>C: Memilih 1/8 halaman dirty
LW->>D: Menulis kembali secara massal
Note over D: Baru di sini dibuat tahan lama
Gambar 3: Penulisan tertunda. Sukses WriteFile adalah «diserahkan ke OS», bukan «dibuat tahan lama».
4.2. Jika apa yang terjadi, sampai di mana yang hilang
Mari buat arti «jendela berbahaya» tepat. Nasib terpecah menurut jenis kegagalan.
flowchart TB
W["Data segera setelah WriteFile berhasil<br/>(halaman dirty di cache)"]
Q{"Apa yang terjadi"}
A1["Proses aplikasi<br/>crash / dihentikan paksa"]
A2["Seluruh OS berhenti<br/>(pemutusan daya / layar biru)"]
S["Data tetap ada<br/>cache milik OS jadi<br/>lazy writer menulis kembali sesuai rencana"]
L["Halaman dirty hilang<br/>hanya yang sudah sampai ke disk yang tersisa"]
W --> Q
Q --> A1
Q --> A2
A1 --> S
A2 --> L
Gambar 4: Jenis kegagalan dan garis pemisah kelangsungan. Cache adalah «milik OS», bukan «milik proses».
- Meski aplikasi mati, data tidak hilang. Pada saat salinan ke cache selesai, pemilik data adalah OS. «Aplikasi jatuh segera setelah menyimpan tetapi file selamat» berkat ini.
- Jika seluruh OS mati, bagian dirty hilang. Frekuensi flush disesuaikan sebagai trade-off kinerja dan keandalan, dan dokumentasi juga menyatakan dengan tegas bahwa «jika kegagalan sistem tiba-tiba seperti kehilangan daya terjadi, data yang di-cache hilang».1
Jadi pertanyaan dalam desain aplikasi bisnis adalah, «apakah data ini boleh hilang pada saat pemutusan daya». Beberapa detik log mungkin boleh. Rekaman penegasan data pesanan mungkin tidak. Hanya yang tidak boleh, memakai alat bab berikutnya.
5. Kotak alat membuat «sudah ditulis dengan andal»
5.1. FlushFileBuffers — tulis habis sekarang
FlushFileBuffers menulis habis data yang di-buffer untuk file yang ditetapkan ke perangkat. Metadata sistem file selalu di-cache, jadi untuk memastikan metadata juga sampai, flush (atau WRITE_THROUGH) diperlukan; itu juga poin yang harus dipegang.12 Di .NET, FileStream.Flush(true) adalah padanannya (Flush() saja hanya menyerahkan buffer internal .NET ke OS, dan cache OS tetap apa adanya).8
Namun dokumentasi resmi dengan jelas menancapkan paku — memanggil setiap kali pada setiap penulisan tidak efisien. Jika persistensi setiap kali diperlukan pada banyak penulisan, pakailah NO_BUFFERING+WRITE_THROUGH yang dibahas kemudian, katanya.2
5.2. FILE_FLAG_WRITE_THROUGH — hanya menghilangkan penundaan
Jika dibuka dengan FILE_FLAG_WRITE_THROUGH, penulisan ditulis juga ke cache, sekaligus ditulis segera ke disk tanpa menunggu lazy writer.1 Poinnya adalah pembacaan tetap mendapat manfaat cache, jawaban lurus terhadap «biarkan pembacaan cepat, hanya hilangkan penundaan penulisan».
5.3. FILE_FLAG_NO_BUFFERING — tidak lewat cache
FILE_FLAG_NO_BUFFERING mengeluarkan cache sistem itu sendiri dari baca tulis. Setiap baca tulis menjadi I/O ke perangkat disk setiap kali, tanpa lewat cache.1 Namun yang dapat dilewati hanya sampai cache sistem Windows, dan seperti Gambar 5 cache tulis di dalam perangkat adalah tahap terpisah. Jika ketahanan sampai pemutusan daya dituntut, kombinasi WRITE_THROUGH atau FlushFileBuffers tetap diperlukan. Ini alat untuk transfer massal data besar atau mesin basis data yang mengelola buffer sendiri, tetapi perjanjian ketat menyertainya.3
- Ukuran dan offset file baca tulis adalah kelipatan ukuran sektor volume (jika sektor 512 byte: 512, 1024, 1536…).
- Alamat buffer juga diselaraskan ke ukuran sektor fisik (pertimbangan disk «Advanced Format» dengan sektor fisik 4096 byte juga diperlukan).
- Meski begitu metadata terus di-cache, jadi untuk persistensi penuh kombinasi WRITE_THROUGH atau
FlushFileBuffersdiperlukan.12
«Perjanjian» ini adalah tempat orang yang mencoba selesai hanya dengan menambah bendera jatuh pertama kali. Jika baca tulis tanpa menaati penyelarasan, gagal dengan ERROR_INVALID_PARAMETER (87). Tiga poin yang harus ditaati ditata.3
| Yang diselaraskan | Syarat | Cara memenuhinya |
|---|---|---|
| Ukuran baca tulis | Kelipatan ukuran sektor volume | Ambil lpBytesPerSector GetDiskFreeSpace dan bulatkan ke kelipatannya |
| Offset file | Sama (juga ketika ditetapkan dengan Offset OVERLAPPED) |
Maju kelipatan ukuran sektor |
| Alamat buffer | Diselaraskan ke ukuran sektor fisik | Alokasikan dengan VirtualAlloc (area yang diselaraskan ke batas halaman = biasanya 4096 byte dikembalikan) |
Yang ketiga khususnya terlewat. Alamat yang dikembalikan malloc atau new, atau array C#, tidak punya jaminan penyelarasan ke batas sektor. Memakai VirtualAlloc yang dapat mengalokasikan di batas halaman memenuhi juga persyaratan disk «Advanced Format» dengan sektor fisik 4096 byte. Bentuk minimumnya seperti ini.
// C++ / Win32. Penanganan error dibuat seminimal mungkin
DWORD sectorsPerCluster = 0, bytesPerSector = 0, freeClusters = 0, totalClusters = 0;
if (!GetDiskFreeSpaceW(L"C:\\", §orsPerCluster, &bytesPerSector,
&freeClusters, &totalClusters))
{
return GetLastError();
}
// Jadikan satuan baca tulis kelipatan ukuran sektor (di sini setara 1MiB)
const DWORD chunk = (1024 * 1024 / bytesPerSector) * bytesPerSector;
// Buffer mengambil area yang diselaraskan ke batas halaman (malloc/new tidak menjamin ini)
BYTE* buffer = static_cast<BYTE*>(
VirtualAlloc(nullptr, chunk, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
if (buffer == nullptr) { return GetLastError(); }
HANDLE h = CreateFileW(L"C:\\temp\\big.bin", GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, nullptr);
if (h == INVALID_HANDLE_VALUE)
{
// GetLastError mengembalikan hasil «panggilan Win32 terakhir». Jika
// VirtualFree dipanggil lebih dulu, alasan gagalnya CreateFileW (akses ditolak,
// path tidak ada, dll.) ditimpa hasil pembersihan, dan yang kembali hanyalah
// kode yang tidak diketahui penyebabnya
const DWORD err = GetLastError();
VirtualFree(buffer, 0, MEM_RELEASE);
return err;
}
// Karena setiap kali maju dalam satuan chunk byte, ukuran maupun offset tetap selaras
DWORD read = 0;
while (ReadFile(h, buffer, chunk, &read, nullptr) && read > 0)
{
// Proses read byte pertama dari buffer
// (di akhir file, read < chunk. Ini normal)
}
CloseHandle(h);
VirtualFree(buffer, 0, MEM_RELEASE);
Selain itu, FileOptions .NET tidak punya nilai yang sesuai FILE_FLAG_NO_BUFFERING. Jika benar-benar diperlukan, Anda akan memanggil CreateFile secara langsung, tetapi dalam kasus itu pun persyaratan penyelarasan di atas harus ditaati sendiri. Pertimbangkan dalam urutan «NO_BUFFERING karena mengelola buffer sendiri», bukan «NO_BUFFERING karena ingin cepat».
5.4. Penataan pemilahan
flowchart TB
A["Buffer aplikasi"]
B["Cache file sistem<br/>(halaman dirty)"]
C["Cache di dalam perangkat disk"]
D[("Media rekam non-volatile")]
A -->|"WriteFile default: sukses dikembalikan sampai di sini"| B
B -->|"lazy writer (setiap detik) / WRITE_THROUGH (segera)"| C
C -->|"Waktu perangkat /<br/>FlushFileBuffers menuntut penulisan habis"| D
A -.->|"NO_BUFFERING melewati cache dan pergi langsung"| C
Gambar 5: Hierarki data, dan seberapa jauh setiap alat mendorongnya. Perhatikan juga tahap terakhir «cache di dalam perangkat disk».
| Metode | Apa yang terjadi | Adegan yang cocok |
|---|---|---|
| Default (cache aktif) | Selesai dengan salinan cache. Pencerminan oleh lazy writer | Hampir semua I/O file |
FlushFileBuffers / Flush(true) |
Menulis habis data + metadata pada saat itu | Penegasan di titik (commit transaksi dll.) |
FILE_FLAG_WRITE_THROUGH |
Ke disk segera per penulisan (pembacaan tetap cache) | Log / jurnal dengan penulisan yang tidak boleh hilang yang berlanjut |
FILE_FLAG_NO_BUFFERING (+WRITE_THROUGH) |
Tidak lewat cache. Ada persyaratan penyelarasan | Pengelolaan buffer sendiri / I/O massal besar |
Urutan memilih adalah dua tahap: pertama putuskan «berapa banyak yang boleh hilang saat pemutusan daya», lalu konfirmasikan «seberapa lambat yang boleh sebagai gantinya». Jangan memandang tabel dari atas; telusuri percabangan ini.
flowchart TB
S["Akan menulis data ini"]
Q1{"Boleh hilang pada saat<br/>pemutusan daya / layar biru?"}
A0["Tetap default (cache aktif)<br/>paling cepat. Hampir semua I/O di sini"]
Q2{"Yang tidak boleh hilang<br/>«titik» atau «setiap entri»?"}
A1["FlushFileBuffers di titik<br/>.NET: Flush(true)<br/>biaya: hanya menunggu di titik"]
Q3{"Dapat mengelola buffer sendiri dan<br/>memenuhi persyaratan penyelarasan 5.3?"}
A2["FILE_FLAG_WRITE_THROUGH<br/>ke disk segera per penulisan<br/>pembacaan tetap cepat dengan cache"]
A3["FILE_FLAG_NO_BUFFERING<br/>+ FILE_FLAG_WRITE_THROUGH<br/>bentuk «persistensi sering» yang dicantumkan resmi"]
S --> Q1
Q1 -->|"Boleh<br/>(log beberapa detik terakhir dll.)"| A0
Q1 -->|"Tidak boleh"| Q2
Q2 -->|"Titik<br/>(penegasan transaksi dll.)"| A1
Q2 -->|"Setiap entri"| Q3
Q3 -->|"Tidak (aplikasi biasa)"| A2
Q3 -->|"Ya (mesin DB dll.)"| A3
Gambar 6: Cara memilih alat. Percabangan tahap 1 adalah tuntutan keandalan, tahap 2 adalah biaya yang dapat dibayar. Tidak ada jalan «FlushFileBuffers setiap entri» karena, seperti yang kita lihat di 5.1, dokumentasi resmi menganggapnya tidak efisien.
Tiga pola praktis saja dicantumkan.
- «Tulis ke file sementara → flush → ganti nama» adalah praktik standar agar tidak meninggalkan file setengah rusak. Menulis habis isi lalu menegaskan dengan nama — serah terima atomik ini dibahas secara rinci di «Pengetahuan dasar pengendalian eksklusif integrasi file».
- Menyerahkan ke basis data juga desain yang layak. Cerita SQLite membangun ketahanan dengan WAL dan flush lihat «Memakai SQLite di aplikasi bisnis dengan C#». Pilihan «tidak menulis strategi flush sendiri» selalu ada.
- Curigai cache pada tolok ukur. Pengukuran «pembacaan terlalu cepat» biasanya mengukur cache hit dari kali kedua dan seterusnya. Tata cara pengukuran dirangkum di «Cara membandingkan kecepatan per versi program dengan benar di Windows».
Selain itu jangan lupa tahap terakhir Gambar 5 — cache di dalam perangkat disk. FlushFileBuffers menuntut penulisan habis termasuk itu, tetapi pada USB dan disk eksternal kebijakan cache tulis sisi perangkat («pelepasan cepat» dan «kinerja tinggi») terlibat. Penanganan perangkat yang dapat dilepas juga lihat «Cara menangani perangkat USB di aplikasi Windows».
6. Koherensi dengan memory-mapped file
Di bagian 1 kita mendengar «identitas cache adalah file mapping», dan pasti ada yang berpikir — lalu, apakah tampilan yang Anda MapViewOfFile sendiri dan cache ReadFile/WriteFile bertengkar?
Tidak. Karena mereka duduk di atas mekanisme yang sama. Objek file mapping ditopang file, dan pengusiran halaman dilakukan sebagai penulisan kembali ke file. Bahkan jika beberapa proses membuat tampilan terhadap file lokal yang sama, isi yang terlihat koheren (konsisten).4
flowchart TB
subgraph P1["Ruang alamat proses A"]
V1["Tampilan MapViewOfFile"]
end
subgraph SYS["Ruang alamat sistem"]
SC["Tampilan Cache Manager<br/>(slot yang dipakai ReadFile/WriteFile)"]
end
PAGES["Kelompok halaman fisik yang sama<br/>(memori yang ditopang file)"]
DISK[("File di disk")]
V1 --> PAGES
SC --> PAGES
PAGES --> DISK
NB["I/O FILE_FLAG_NO_BUFFERING<br/>di luar kerangka berbagi ini (langsung ke disk)"]
NB -.-> DISK
Gambar 7: Tampilan yang dipetakan maupun cache melihat «halaman yang ditopang file» yang sama. Yang di luar kerangka hanyalah NO_BUFFERING.
Dua perhatian.
- I/O
FILE_FLAG_NO_BUFFERINGdi luar kerangka koherensi ini. Baca tulis yang tidak lewat cache dan isi lewat tampilan yang dipetakan/cache tidak dicocokkan. Jika dicampur, Anda sendiri yang harus mengambil keselarasan. - Persistensi tampilan yang dipetakan adalah dua tahap.
FlushViewOfFilememulai penulisan halaman dirty dalam rentang, tetapi tidak menulis metadata, dan tidak menunggu penulisan fisik dari cache perangkat disk. Untuk memastikan sampai, panggilFlushFileBufferssetelahFlushViewOfFile.5
Praktik file mapping sebagai memori bersama (berbagi bernama, sinkronisasi, pola kecelakaan) dibahas di «Jebakan memori bersama dan praktik terbaik».
7. Fast I/O — menarik pekerjaan rumah bagian 1
Dua baris dulu. Fast I/O adalah jalan pintas yang disediakan untuk baca tulis sinkron ke file yang ada di cache, yang bertukar data langsung dengan cache tanpa merakit IRP (I/O Request Packet; wadah permintaan yang kernel serahkan ke driver). IRP_MJ_READ dan FASTIO_READ yang tercampur di kolom Operation Procmon adalah perbedaan apakah «pembacaan» yang sama lewat jalur biasa atau jalan pintas.
Di bab 5.2 bagian 1 kita menulis «tidak semua I/O menjadi IRP». Ini jawaban ujiannya.
Sudah diketahui bahwa baca tulis file yang ada di cache dapat selesai dengan salinan memori dengan cache, tanpa merakit IRP dan mengalirkannya di device stack. Karena itu Windows menyediakan jalan pintas bernama Fast I/O untuk I/O sinkron ke file yang di-cache — jalur yang tidak membuat IRP, memanggil «titik masuk Fast I/O» sistem file secara langsung, dan menyalin langsung dari Cache Manager.6 Jika tidak dapat diproses dengan Fast I/O (tidak ada di cache, kunci terlibat, filter menyela, dll.), ia berbalik ke jalur IRP biasa. Selain itu, ini jalur cepat untuk permintaan sinkron, bukan «cache hit = selalu Fast I/O». Operasi handle asinkron (FILE_FLAG_OVERLAPPED) kadang diproses di jalur IRP bahkan ketika selesai di tempat dari cache (bab 5 bagian 2).
flowchart TB
REQ["Baca tulis sinkron ke handle dengan cache aktif"]
Q{"Dapat diproses dengan Fast I/O<br/>(ada di cache dll.)?"}
FAST["Fast I/O<br/>menyalin langsung dengan cache tanpa membuat IRP<br/>di Procmon ditampilkan sebagai FASTIO_"]
IRP["Jalur biasa<br/>merakit IRP ke device stack<br/>(dunia Gambar 6 bagian 1)"]
REQ --> Q
Q -->|Dapat| FAST
Q -->|Tidak dapat| IRP
Gambar 8: Percabangan Fast I/O. Inilah sebabnya FASTIO_READ dan IRP_MJ_READ tampak tercampur di Procmon.
Alasan baris FASTIO_ tercampur di pengamatan Procmon bab 7 bagian 1 kini dapat dijelaskan. Pembacaan cache hit, bahkan IRP pun barang mewah. Keberadaan jalur ini juga memengaruhi driver filter yang dibahas di bagian 6 (minifilter dibuat agar dapat menyela Fast I/O juga).
8. Ringkasan
- Cache file Windows adalah write-back, dan identitasnya adalah pemetaan rentang 256 KB file. Baca tulis dengan cache aktif menjadi salinan memori dengan slot.1
- Pembacaan read-ahead berspekulasi, dan
SequentialScan/RandomAccessadalah petunjuknya.1 - Penulisan diikuti kemudian oleh lazy writer setiap detik. Meski aplikasi mati data tetap ada, jika seluruh OS mati hanya bagian dirty yang hilang. Pertanyaan desain adalah «apakah data ini boleh hilang pada saat pemutusan daya».1
- Alat menulis dengan andal adalah
FlushFileBuffers(penegasan di titik) /WRITE_THROUGH(per penulisan) /NO_BUFFERING(tidak lewat cache + persyaratan penyelarasan). Flush setiap kali tidak efisien, dan untuk persistensi yang sering kombinasi NO_BUFFERING+WRITE_THROUGH adalah rekomendasi resmi. Perhatikan juga bahwa metadata selalu di-cache.231 - Tampilan yang dipetakan dan cache berbagi halaman yang sama dan koheren. Yang di luar kerangka hanya NO_BUFFERING. Persistensi peta adalah dua tahap
FlushViewOfFile+FlushFileBuffers.45 - Baca tulis sinkron cache hit bahkan IRP dihilangkan dengan Fast I/O. Identitas
FASTIO_yang dilihat di Procmon bagian 1.6
Lanjutannya adalah bagian 5 «Interior NTFS — memahami sistem file lewat MFT». Sampai kali ini file diperlakukan sebagai «offset dan rangkaian byte», tetapi di belakangnya bagaimana NTFS menata data — MFT, beberapa data stream, jurnal, hard link — kita turun ke struktur statis di atas disk.
Artikel terkait
- Kedalaman I/O Windows (bagian 1) — setiap baca tulis menjadi IRP: gambaran keseluruhan sistem I/O
- Kedalaman I/O Windows (bagian 2) — I/O sinkron dan asinkron: arti sesungguhnya OVERLAPPED
- Kedalaman I/O Windows (bagian 3) — I/O Completion Port (IOCP) dan thread pool .NET: basement async/await
- Pengetahuan dasar pengendalian eksklusif integrasi file — praktik terbaik kunci file dan klaim atomik
- Jebakan memori bersama dan praktik terbaik
- Memakai SQLite di aplikasi bisnis dengan C# — mode WAL, pengendalian eksklusif, penanggulangan kerusakan, pemilahan dengan EF Core
- Cara membandingkan kecepatan per versi program dengan benar di Windows
- Cara menangani perangkat USB di aplikasi Windows — memilih COM virtual, HID, WinUSB, SDK khusus
Area konsultasi terkait
KomuraSoft LLC menangani desain dan investigasi bug I/O file aplikasi bisnis Windows, seperti «data yang seharusnya sudah disimpan hilang» atau «penulisan file lambat / terlalu cepat sehingga mencurigakan».
- Pengembangan aplikasi Windows
- Investigasi bug dan akar masalah
- Pemanfaatan aset yang ada dan dukungan migrasi
- Hubungi kami
Tautan referensi
-
Microsoft Learn, File Caching. Tentang Windows yang secara default meng-cache data file, pembacaan yang dilakukan dari cache file sistem, dan penulisan juga ke cache sebagai cache write-back; cache yang dikelola per objek file dan bergerak di bawah komando Cache Manager; kebijakan menunda penulisan ke disk dan menahannya di cache yang disebut penulisan tertunda (lazy writing); saat pembacaan file, rentang 256 KB yang dibaca ke slot 256 KB di ruang alamat sistem, dan proses pengguna yang menyalin data dengan slot itu; Cache Manager yang menjalankan lazy writer setiap detik, menumpuk seperdelapan halaman yang belum di-flush baru-baru ini ke antrean penulisan disk, dan menumpuk lebih banyak jika perlu; file sementara yang tidak di-flush; bahwa jika kegagalan sistem tiba-tiba seperti kehilangan daya terjadi, data cache yang belum ditulis hilang; bahwa menonaktifkan cache dengan FILE_FLAG_NO_BUFFERING pun metadata file masih dapat di-cache; bahwa dengan FILE_FLAG_WRITE_THROUGH data ditulis juga ke cache sekaligus ditulis segera ke disk tanpa penundaan lazy writer; dan bahwa metadata sistem file selalu di-cache jadi persistensi metadata mensyaratkan flush atau FILE_FLAG_WRITE_THROUGH. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19
-
Microsoft Learn, FlushFileBuffers function. Tentang WriteFile yang biasanya menulis ke buffer internal, dan OS yang secara berkala menulis ke disk; FlushFileBuffers yang menulis semua informasi yang di-buffer untuk file yang ditetapkan ke perangkat; bahwa memanggil setiap kali pada banyak penulisan tidak efisien, dan aplikasi yang memerlukan persistensi data penting pada penulisan yang sering harus memakai I/O tanpa buffer dengan FILE_FLAG_NO_BUFFERING dan FILE_FLAG_WRITE_THROUGH; dan bahwa memanggil terhadap handle volume (dengan hak administrator) dapat mem-flush semua file terbuka di volume. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, File Buffering. Tentang persyaratan akses ke file yang dibuka dengan FILE_FLAG_NO_BUFFERING: ukuran baca tulis dan offset file (termasuk ketika ditetapkan dengan OVERLAPPED) harus kelipatan ukuran sektor volume; alamat buffer baca tulis harus diselaraskan ke ukuran sektor fisik; dan pertimbangan perangkat Advanced Format dengan sektor fisik 4.096 byte yang diperlukan. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, File Mapping. Tentang objek file mapping yang ditopang file di disk, dan swap-out halaman yang dilakukan sebagai penulisan perubahan ke file; dan bahwa ketika beberapa proses membuat tampilan file lokal dari objek file mapping yang sama, data koheren (isi yang sama dengan file di disk). ↩ ↩2 ↩3
-
Microsoft Learn, FlushViewOfFile function. Tentang FlushViewOfFile yang memulai penulisan halaman dirty dalam rentang tampilan yang dipetakan ke disk; fungsi ini yang tidak mem-flush metadata file, dan tidak menunggu penyelesaian penulisan fisik dari cache disk perangkat keras; dan bahwa untuk menulis habis semua halaman dirty dan metadata secara fisik, FlushFileBuffers harus dipanggil setelah FlushViewOfFile. ↩ ↩2 ↩3
-
Microsoft Learn, IRPs Are Different From Fast I/O. Tentang Fast I/O sebagai jalur cepat I/O sinkron untuk file yang di-cache, yang memanggil titik masuk sistem file atau Cache Manager secara langsung tanpa menghasilkan IRP; data yang ditransfer langsung dari cache ke buffer pengguna (atau sebaliknya); dan bahwa jika tidak dapat diproses dengan Fast I/O, jalur biasa berbasis IRP dipakai. ↩ ↩2 ↩3
-
Microsoft Learn, Asynchronous disk I/O appears as synchronous on Windows. Tentang permintaan yang selesai di tempat dan mengembalikan TRUE ketika data ada di cache; cache Windows yang diimplementasikan sebagai file mapping, dan karena tidak ada mekanisme page fault asinkron ketika halaman tidak ada, pembacaan asinkron dengan cache aktif kadang diproses secara sinkron. ↩
-
Microsoft Learn, FileStream.Flush method (.NET). Tentang Flush() yang menulis buffer internal stream ke OS; dan bahwa menetapkan Flush(true) di atas itu juga mem-flush semua buffer file perantara (buffer OS). ↩
Artikel terkait
Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.
Kedalaman I/O Windows (bagian 5) — interior NTFS: memahami sistem file lewat MFT
Bagian 5 seri yang menjelaskan interior NTFS dengan gambar. Menata MFT dan rekaman file, beberapa data stream (Zone.Identifier), hard lin...
Kedalaman I/O Windows (bagian 6, terakhir) — driver filter dan minifilter: alasan Procmon dan pemindaian virus dapat menyela I/O
Bagian terakhir seri yang menjelaskan driver filter dan minifilter Windows dengan gambar. Menata Filter Manager dan altitude, callback pr...
Praktik terbaik multithreading di lapangan: edisi .NET — apa yang diputuskan sebelum menambah thread
Rangkuman praktis aturan desain yang mencegah kode multithread .NET/C# sesekali crash atau macet: bertumpu pada Task alih-alih membuat th...
Memakai WMI/CIM dari C# dan PowerShell — panduan praktis pengambilan info perangkat keras, pemantauan proses, dan kueri jarak jauh
Cara standar mengambil nomor seri PC, memantau ruang disk, dan mendeteksi proses yang start adalah WMI/CIM. Artikel ini menjelaskan pemak...
Named pipes dalam praktik — IPC standar Windows dari desain hingga keamanan
Panduan praktis tentang named pipe, komunikasi antarpproses standar di Windows. Artikel ini menata, dari sumber primer, pilihan antara mo...
Topik terkait
Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.
Topik teknis Windows
Portal tentang pengembangan Windows, investigasi bug, dan pemanfaatan aset yang ada.
Layanan yang terkait dengan topik ini
Artikel ini berkaitan langsung dengan layanan berikut.
Pengembangan aplikasi Windows
Aplikasi bisnis, integrasi perangkat, dan alat komunikasi, dari kebutuhan hingga pengembangan.
Pertanyaan yang sering diajukan
Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.
- Pada saat WriteFile mengembalikan sukses, apakah data sudah ditulis ke disk?
- Secara default, belum. Cache file Windows adalah cache write-back, dan WriteFile mengembalikan sukses begitu data disalin ke cache file sistem. Penulisan ke disk dilakukan kemudian oleh lazy writer Cache Manager, yang berjalan sekali per detik. Yang penting adalah perbedaan jenis kegagalan. Jika proses aplikasi crash, data yang sudah masuk cache tidak hilang, karena OS kemudian menulisnya kembali selama OS sendiri masih hidup. Jika seluruh OS jatuh — pemutusan daya, layar biru — data cache dirty yang belum ditulis kembali hilang. Cara membaca «WriteFile berhasil» yang akurat bukan «sudah dibuat tahan lama» melainkan «sudah diserahkan ke OS».
- Bagaimana memastikan data benar-benar sampai ke disk?
- Ada tiga alat. Pertama, FlushFileBuffers, yang menulis semua data dan metadata yang di-buffer untuk file itu sampai ke perangkat (di .NET, FileStream.Flush(true) adalah padanannya). Kedua, FILE_FLAG_WRITE_THROUGH, yang menulis ke cache pada setiap penulisan sekaligus menulis ke disk segera. Ketiga, FILE_FLAG_NO_BUFFERING, yang melewati cache sama sekali. Dokumentasi Microsoft sendiri mencatat bahwa memanggil FlushFileBuffers pada setiap penulisan tidak efisien, dan bahwa aplikasi yang memerlukan ketahanan andal dengan penulisan yang sering harus menggabungkan FILE_FLAG_NO_BUFFERING dengan FILE_FLAG_WRITE_THROUGH. Setiap satunya menyerahkan sebagian manfaat cache sehingga menjadi lebih lambat, jadi aturan praktisnya bukan menempelkannya ke semua melainkan menyimpannya untuk penulisan yang benar-benar tidak boleh hilang.
- Apa perbedaan FILE_FLAG_WRITE_THROUGH dan FILE_FLAG_NO_BUFFERING?
- WRITE_THROUGH berarti «tetap menulis ke cache, tetapi juga menulis ke disk sebelum selesai». Pembacaan tetap mendapat manfaat cache; hanya penundaan lazy writer pada penulisan yang dihilangkan. NO_BUFFERING berarti «baca tulis melewati cache sistem sepenuhnya», jadi keduanya menjadi I/O perangkat pada setiap panggilan (meskipun yang dilewati hanya cache Windows sendiri — ia tidak melewati cache tulis yang duduk di dalam perangkat penyimpanan itu sendiri). Sebagai gantinya, ia datang dengan batasan ketat: ukuran dan offset file setiap baca atau tulis harus kelipatan tepat ukuran sektor volume, dan alamat buffer itu sendiri harus diselaraskan ke batas sektor fisik. Selain itu, bahkan dengan NO_BUFFERING, metadata sistem file terus di-cache, jadi membuat metadata tahan lama tetap mensyaratkan FlushFileBuffers atau WRITE_THROUGH di atasnya. Biasanya dipakai perangkat lunak yang mengelola buffering sendiri, seperti mesin basis data; aplikasi biasa pada umumnya harus meraih WRITE_THROUGH atau FlushFileBuffers dulu.
- Task Manager menunjukkan sedikit memori bebas — apakah itu salah cache file?
- Sering, ya, dan itu perilaku normal. Windows secara aktif memakai memori fisik kosong sebagai cache file, jadi menyalin file besar atau banyak membaca dan menulis akan membengkak cache dan membuat pemakaian memori tampak lebih tinggi. Meski begitu, sebagian besar halaman yang dipakai cache adalah jenis yang dapat diambil kembali cukup cepat begitu aplikasi meminta memori, yang perlu dibedakan dari benar-benar kehabisan memori. Ketika Anda mencurigai kekurangan nyata, lebih berguna melihat memori yang dikomit dan frekuensi hard fault daripada jumlah memori bebas yang tampak saja.
- Jika saya menyentuh file yang sama lewat tampilan memory-mapped dan lewat ReadFile/WriteFile, bisakah isinya tidak selaras?
- Tidak terhadap I/O biasa dengan cache aktif. Cache Windows sendiri diimplementasikan sebagai file mapping, jadi tampilan yang dipetakan dan cache untuk file lokal yang sama berbagi data yang sama — perubahan lewat yang satu terlihat lewat yang lain. Beberapa proses yang membuat tampilan dari objek file mapping yang sama juga melihat data yang koheren. Namun, baca tulis pada handle yang dibuka dengan FILE_FLAG_NO_BUFFERING melewati cache dan jatuh di luar koherensi ini. Selain itu, untuk secara andal menahan perubahan yang dibuat lewat tampilan yang dipetakan, FlushViewOfFile saja tidak cukup — ia tidak menulis metadata dan tidak menunggu cache perangkat keras — jadi Anda perlu memanggil FlushFileBuffers setelah FlushViewOfFile.
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.