mmap() berbasis file yang berhasil membuat mapping virtual memory yang tidak bergantung pada file descriptor sumber agar tetap terbuka. Linux secara eksplisit mengizinkan descriptor ditutup segera setelah mmap() selesai tanpa membatalkan mapping. Mapping dan descriptor dengan demikian merupakan referensi terpisah dengan masa hidup yang terpisah.

Pemisahan ini mudah terlewat karena keduanya berasal dari file terbuka yang sama. Dampaknya menjadi penting ketika kode menutup descriptor secara agresif, mengganti pathname, melakukan truncate pada file, atau membawa mapping melewati fork(). Alamat mapped bukan read() tertunda melalui descriptor awal; alamat tersebut beroperasi dalam virtual-memory system dengan kontrak mapping tersendiri.

Untuk mapping berbasis file, mmap() membentuk page dalam address space proses dari objek yang dirujuk descriptor dan offset file yang diminta. Setelah pemanggilan berhasil, menutup descriptor tersebut tidak menjalankan munmap() secara implisit.

Proses dapat memakai bentuk seperti berikut:

int fd = open(path, O_RDONLY);
void *p = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);

/* p tetap mapped sampai munmap(), replacement, atau process teardown */

Ini adalah properti API Linux, bukan properti bahasa C. C hanya menyediakan operasi dan tipe pada source code; kernel menetapkan semantik mapping dan descriptor.

Arah sebaliknya juga terpisah. munmap() menghapus page dari rentang alamat yang ditentukan, tetapi tidak menutup descriptor lain yang masih merujuk file tersebut. Resource accounting perlu memperlakukan file descriptor dan virtual-memory area sebagai resource yang berbeda.

Penggantian pathname tidak mengarahkan ulang mapping lama

Pathname dipakai saat open() untuk memperoleh descriptor. Descriptor kemudian mengidentifikasi opened file object, lalu mmap() membentuk mapping dari objek tersebut. Operasi namespace berikutnya tidak membuat mapping lama melakukan resolusi ulang terhadap pathname awal.

Misalkan sebuah proses memetakan config.bin, lalu proses lain melakukan atomic rename untuk menempatkan file pengganti pada pathname itu. Pemanggilan open() baru dapat mencapai file pengganti, sedangkan mapping yang sudah ada tetap merujuk objek yang dipetakan sebelumnya.

Perilaku ini serupa dengan descriptor yang sudah terbuka saat rename terjadi, tetapi mapping tidak memerlukan descriptor tersebut untuk terus terbuka. Mapping itu sendiri mempertahankan relasi kernel yang relevan.

Konsekuensinya adalah batas versi: publikasi pathname dapat mengalihkan consumer baru ke file lain tanpa memaksa consumer mapped yang sudah ada berpindah ke objek baru. Aplikasi yang memerlukan perpindahan serentak bagi seluruh consumer membutuhkan protokol koordinasi eksplisit; semantik rename dan mapping tidak menyediakannya.

Ukuran file tetap membatasi akses mapped yang valid

Mapping memiliki panjang alamat virtual, tetapi hal itu tidak membuat byte di luar backing file selalu valid. Linux mendokumentasikan SIGBUS untuk akses ke page pada file mapping yang berada di luar akhir mapped file.

Hal ini penting ketika actor lain dapat melakukan truncate pada backing file. Sebuah proses dapat memiliki mapping dengan rentang virtual yang masih ada, sementara akses di dalam rentang tersebut tidak lagi memiliki backing file yang valid. Masa hidup mapping pada address space belum berakhir, tetapi ukuran backing object telah mengubah validitas akses tertentu.

Failure mode ini berbeda dari SIGSEGV akibat akses ke alamat yang tidak mapped. Mapping virtual masih dapat tersedia; fault terkait dengan backing object untuk page yang direferensikan.

Pada partial page terakhir sebuah file, granularitas page menambahkan detail lain. Linux mengisi nol pada bagian page di luar akhir file saat mapping dibuat, dan modifikasi pada bagian di luar akhir tersebut tidak ditulis ke file. Whole page di luar akhir objek menjadi batas SIGBUS yang relevan.

MAP_PRIVATE bukan snapshot file yang beku

MAP_PRIVATE membuat copy-on-write mapping: write melalui mapping bersifat privat bagi proses dan tidak diteruskan ke underlying file. Properti tersebut tidak mendefinisikan snapshot stabil untuk setiap byte terhadap mutasi file secara concurrent.

POSIX dan dokumentasi Linux mmap() membiarkan visibilitas modifikasi pada underlying file setelah MAP_PRIVATE dibuat sebagai unspecified. Kode yang memerlukan semantik immutable snapshot tidak dapat menyimpulkannya hanya dari istilah “private.”

Copy-on-write terutama menjelaskan perlakuan terhadap write yang dilakukan melalui private mapping. Mekanisme tersebut bukan jaminan versioning umum terhadap write eksternal pada backing file.

Snapshot stabil memerlukan premis yang lebih kuat, seperti kontrol eksklusif atas backing object, objek immutable, filesystem snapshot dengan kontrak terdokumentasi, atau mekanisme versioning lain pada tingkat aplikasi.

MAP_SHARED menghubungkan mapped write dengan backing object

MAP_SHARED memiliki kontrak write yang berbeda. Update melalui mapping diteruskan ke underlying mapped object dan dapat terlihat oleh proses lain yang memetakan region yang sama, sesuai aturan interface dan sinkronisasi yang berlaku.

Visibilitas dan durable storage tetap terpisah. msync() tersedia untuk mengendalikan sinkronisasi perubahan mapped dengan backing file berdasarkan flag terdokumentasi dan semantik platform. Store yang terlihat melalui shared mapping bukan jaminan universal crash durability.

Batas ini juga memisahkan memory ordering dari storage persistence. Primitive sinkronisasi CPU dapat mengurutkan memory access antar-thread, sedangkan persistensi data berbasis file diatur oleh interface mapping, filesystem, dan storage. Satu kontrak tidak dapat menggantikan kontrak lain tanpa jaminan platform yang eksplisit.

fork membawa relasi mapping ke child

Linux mempertahankan memory mapping melewati fork() dengan atribut mapping yang sama. Child memperoleh mapped region yang sesuai walaupun descriptor yang semula dipakai untuk mmap() sudah ditutup sebelum fork.

Untuk MAP_PRIVATE, parent dan child mempertahankan semantik copy-on-write privat bagi modifikasi masing-masing. Untuk MAP_SHARED, kedua mapping mempertahankan sifat shared yang terkait dengan mapped object.

Kondisi ini membentuk pemisahan masa hidup lain. Aturan pewarisan descriptor dan aturan pewarisan mapping terkait dengan event pembuatan proses yang sama, tetapi berlaku pada kumpulan resource berbeda. Menandai descriptor sebagai close-on-exec, menutupnya sebelum fork(), atau mengubah state descriptor dengan cara lain tidak menghapus mapping yang sudah terbentuk.

Transisi execve() berbeda: penggantian program yang berhasil membuang memory mapping lama sebagai bagian dari penggantian address space. Masa hidup mapping dengan demikian mengikuti aturan masa hidup address space, bukan aturan masa hidup descriptor.

Ownership mapping memerlukan batas cleanup tersendiri

Kode yang membungkus file mapping sebagai resource perlu merepresentasikan panjang mapping dan mapped address secara terpisah dari descriptor sumber. Destructor atau cleanup path yang hanya menutup descriptor membiarkan virtual-memory area tetap ada; path yang hanya melakukan unmap membiarkan descriptor yang masih terbuka tetap ada.

Pemisahan ini juga memengaruhi diagnosis. Descriptor leak terlihat pada view dan limit yang berorientasi descriptor, sedangkan mapping berlebihan dapat menghabiskan virtual address space dan bookkeeping virtual-memory area walaupun tidak ada descriptor sumber yang tetap terbuka.

Interface kernel sengaja mengizinkan kedua masa hidup tersebut berbeda. Descriptor adalah capability yang dipakai untuk meminta mapping; descriptor bukan handle permanen yang dilalui setiap mapped access berikutnya. Setelah terbentuk, mapping menjadi objek address space dengan validitas yang bergantung pada mapping protection, state backing object, dan masa hidup address space.

Batas tersebut menjelaskan beberapa observasi yang tampak mengejutkan dalam satu model: menutup descriptor tidak menghapus mapped page, penggantian pathname tidak mengarahkan ulang mapping, truncate dapat membatalkan akses tanpa menghapus mapping, dan pembuatan proses dapat mempertahankan mapping secara independen dari descriptor yang semula membuatnya.