Sebuah proses dapat mempertahankan virtual memory mapping yang valid setelah pihak lain memperpendek file yang dipetakan. Rentang alamatnya masih ada di dalam proses, tetapi backing object mungkin tidak lagi memiliki setiap page yang sebelumnya direpresentasikan oleh rentang tersebut. Pada sistem POSIX, akses ke satu page terpetakan penuh yang berada di luar akhir file baru dapat menghasilkan SIGBUS, bukan berperilaku seperti pembacaan file biasa yang gagal.

Batas ini membuat file-backed mmap() berbeda dari menyalin byte ke private heap storage. Pointer ke dalam mapping bukan bukti bahwa extent file yang sesuai masih ada. Alamat virtual, lifetime mapping, identitas file, dan ukuran file saat ini merupakan state yang saling berkaitan, tetapi bukan satu objek yang tidak dapat dipisahkan.

Lifetime mapping terpisah dari lifetime descriptor

mmap() yang berhasil membuat address-space mapping ke memory object yang direpresentasikan oleh file descriptor. POSIX menetapkan bahwa mapping menambahkan referensinya sendiri ke file terkait. Menutup descriptor setelahnya tidak menghapus mapping tersebut.

Urutan berikut karena itu valid:

int fd = open("index.bin", O_RDONLY);
void *p = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
close(fd);

/* p can still refer to the mapping here. */

Dengan asumsi pemanggilan berhasil dan permission akses sesuai, close(fd) melepaskan descriptor tetapi bukan mapping. munmap() atau berakhirnya proses menentukan lifetime mapping di address space.

Pemisahan ini berguna karena akses terpetakan tidak mengharuskan descriptor yang tidak lagi dipakai tetap terbuka. Namun ini juga menghilangkan asumsi sinkronisasi yang menggoda: penutupan descriptor tidak membekukan file, mempertahankan ukurannya, atau memisahkan mapping menjadi snapshot independen.

Pathname adalah lapisan lain yang terpisah. Rename atau unlink pada pathname tidak membuat mapping yang sudah ada melakukan resolusi nama kembali. Mapping tetap mengacu ke object yang dibuka. Karena itu, penggantian file dengan inode baru dan mutasi in-place pada inode lama memberikan efek yang berbeda secara material terhadap mapping yang sudah ada.

Memperkecil object yang sama mengubah extent yang dapat diakses

ftruncate() dapat mengurangi regular file ke panjang tertentu. Ketika operasi tersebut menghapus whole page yang sebelumnya terpetakan, POSIX mengharuskan page itu dibuang. Referensi berikutnya ke page yang telah dibuang menghasilkan SIGBUS.

Pertimbangkan proses yang memetakan file 8 MiB dan mempertahankan mapping sementara proses lain memangkas file yang sama menjadi 1 MiB:

mapping:  [0 -------------------------------- 8 MiB)
file:     [0 ---- 1 MiB)

mapped virtual addresses still exist
backing file pages after the new end do not

Panjang mapping yang tercatat pada proses tidak otomatis berubah menjadi 1 MiB. Namun akses di seluruh rentang awal tidak lagi memiliki validitas backing file yang sama.

Di Linux, mmap(2) mendokumentasikan SIGBUS ketika terjadi percobaan akses ke page dari mapped buffer yang berada di luar akhir mapped file. Ini adalah kegagalan akses memori, sehingga API yang mengekspos mapped bytes sebagai pointer biasa dapat memunculkan race ukuran file sebagai synchronous signal di dalam kode yang tampaknya hanya melakukan load.

Batas persis pada final partial page perlu ditangani dengan hati-hati. POSIX menetapkan zero filling untuk partial page di akhir object dan menyatakan bahwa modifikasi setelah akhir object pada page tersebut tidak ditulis kembali. Whole page setelah akhir object memiliki aturan SIGBUS yang lebih kuat. Kode sebaiknya tidak mengubah detail page rounding menjadi kontrak data aplikasi.

Pemeriksaan ukuran sebelumnya tidak mengunci ukuran

Membaca st_size sebelum mapping berguna untuk menentukan panjang mapping, tetapi tidak membentuk invariant permanen kecuali ada protokol terpisah yang mencegah perubahan ukuran yang tidak kompatibel.

Urutan umum memiliki dua observasi terpisah:

struct stat st;
fstat(fd, &st);

void *p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);

Hasil fstat() menggambarkan metadata file pada titik observasi tersebut. Operasi mapping berlangsung kemudian. Pihak lain dengan akses yang sesuai dapat mengubah object di antara kedua operasi itu atau setelah keduanya selesai.

Bahkan jika mapping awal tepat sama dengan ukuran file yang diamati, truncation berikutnya dapat membatalkan asumsi bahwa setiap mapped page masih memiliki backing. Bounds check terhadap st_size awal melindungi dari indexing di luar mapping awal; pemeriksaan tersebut tidak membuktikan bahwa file saat ini masih mencapai offset itu.

Ini adalah batas time-of-check terhadap penggunaan berikutnya, tetapi failure mode-nya khusus untuk mapped storage. pread() biasa pada offset di luar akhir file saat ini melaporkan end-of-file melalui return value. Load melalui mapped pointer tidak memiliki return channel untuk kondisi itu. Virtual-memory subsystem melaporkan akses backing yang tidak valid melalui mekanisme memory fault platform.

Penggantian pathname secara atomik menghindari mutasi mapping yang sudah ada

Mempublikasikan file baru dengan membuat object terpisah lalu mengganti pathname secara atomik memiliki semantik mapping yang berbeda dari melakukan truncate dan rewrite terhadap object yang sedang dipetakan reader.

Dengan replacement, mapping yang sudah ada mempertahankan referensinya ke object lama yang telah dibuka. open() berikutnya melalui pathname dapat mencapai replacement object, sementara mapping lama tetap mengekspos object sebelumnya selama referensi yang ada membuat object itu tetap hidup.

Topologinya kira-kira:

existing mapping ------> old file object

pathname --------------> new file object
new open --------------> new file object

Properti ini dapat mendukung desain immutable generation. Writer membuat file baru yang lengkap, memublikasikannya melalui namespace replacement, lalu membiarkan reader yang sudah ada tetap terhubung ke generation lama sampai mereka melepaskannya. Operasi publikasi tidak mengubah ukuran object di bawah reader tersebut.

Hal ini tidak membuat rename menjadi protokol durability atau concurrency yang lengkap. Persistensi saat crash memiliki requirement sinkronisasi tersendiri, dan aplikasi tetap membutuhkan aturan untuk mendeteksi atau memilih generation. Poin yang relevan di sini lebih sempit: mengganti pathname dapat mempertahankan ukuran dan isi object yang sudah direferensikan oleh mapping yang ada, sedangkan truncation in-place mengubah object yang sama.

Perbedaan ini juga bergantung pada penggunaan file object yang benar-benar terpisah. Menulis ulang inode yang sama melalui pathname lain atau hard link tidak menciptakan isolasi generation. Identitas object, bukan ejaan pathname, menentukan apakah mapping yang sudah ada berbagi target mutasi.

MAP_PRIVATE bukan batas snapshot penuh

MAP_PRIVATE kadang dibaca seolah-olah seluruh file disalin pada saat mapping dibuat. Kontraknya adalah visibilitas copy-on-write untuk modifikasi melalui mapping, bukan pembuatan snapshot eager dari setiap mapped byte.

POSIX secara eksplisit membiarkan efeknya unspecified ketika ukuran mapped file berubah setelah mmap() untuk referensi yang berkaitan dengan bagian yang ditambah atau dihapus. Linux mendokumentasikan perilaku SIGBUS di luar akhir file untuk file mapping. Memilih MAP_PRIVATE karena itu tidak membenarkan anggapan bahwa truncation file berikutnya tidak relevan terhadap page yang belum menjadi private state independen.

Perbedaan semantik yang berguna adalah antara visibilitas modifikasi dan extent backing object. MAP_PRIVATE mencegah write melalui mapping diteruskan ke underlying object sebagai shared update. Ia tidak mengubah mapping menjadi salinan heap byte-for-byte dengan lifetime yang independen dari perubahan ukuran file.

Aplikasi yang membutuhkan immutable byte snapshot memerlukan mekanisme yang benar-benar menyediakan properti tersebut dalam asumsi operasionalnya. Bergantung pada sistem, caranya dapat berupa menyalin byte, memetakan object yang writer-nya mengikuti protokol immutable generation, atau menggunakan storage primitive lain dengan kontrak snapshot eksplisit.

Pemulihan melalui signal adalah batas yang sempit dan sulit

Memasang handler SIGBUS dapat membuat kegagalan dapat diamati, tetapi tidak otomatis membuat sembarang mapped access dapat dipulihkan.

Synchronous fault dapat terjadi pada banyak machine instruction yang dihasilkan dari ekspresi source yang tampak sederhana. Signal handler juga berjalan dengan constraint ketat: secara umum hanya operasi async-signal-safe yang cocok digunakan di dalam POSIX signal handler, dan kembali ke instruksi yang fault tanpa mengubah kondisinya dapat memicu fault yang sama lagi.

Desain yang ingin pulih dari mapping yang tidak valid karena itu membutuhkan model control transfer dan ownership yang eksplisit, bukan handler generik yang hanya melakukan logging lalu melanjutkan eksekusi. Thread yang mengakses mapping yang sama secara konkuren semakin memperumit model tersebut karena pemulihan satu thread tidak dengan sendirinya memastikan thread lain sudah berhenti menyentuh page yang invalid.

Untuk banyak format file, mencegah resize in-place yang destruktif merupakan batas yang lebih bersih daripada mencoba mengubah arbitrary mapped load menjadi operasi I/O yang dapat dipulihkan. Ini adalah constraint arsitektural, bukan aturan universal: sebagian sistem sengaja memakai fault handling sebagai bagian dari memory model, tetapi pendekatan itu mengharuskan semantik signal menjadi bagian dari kontrak interface.

Keamanan mapping bergantung pada protokol mutasi

Mapped file yang dibagi antar-komponen memiliki setidaknya empat properti yang dapat berubah secara independen: identitas object, ukuran object, lifetime mapping, dan state format pada level aplikasi. Akses yang benar bergantung pada hubungan di antara semuanya.

Jika writer dapat memperkecil object yang sama sementara reader mempertahankan mapping, reader harus memperhitungkan backing page yang menjadi invalid. Jika writer memublikasikan object immutable yang terpisah, mapping yang sudah ada dapat tetap melekat pada generation stabil sementara resolusi pathname bergerak ke depan. Jika format mendukung update in-place tanpa pengurangan ukuran, pertanyaan concurrency yang tersisa bergeser ke visibilitas, ordering, atomicity, dan konsistensi format, bukan discarded page.

Semua ini adalah protokol berbeda meskipun setiap reader memakai pemanggilan mmap() yang sama.

Constraint utamanya adalah bahwa validitas address space tidak sama dengan validitas backing object. Mapping dapat tetap terpasang sementara file extent yang sebelumnya dicakup berubah di bawahnya. Memperlakukan mapped bytes sebagai process memory yang durable menghapus batas tersebut dan mengubah mutasi storage menjadi memory fault pada titik akses.