Itu mungkin, meskipun ada masalah konsistensi cache khusus arsitektur yang mungkin perlu Anda pertimbangkan. Beberapa arsitektur tidak mengizinkan halaman yang sama diakses dari beberapa alamat virtual secara bersamaan tanpa kehilangan koherensi. Jadi, beberapa arsitektur akan mengelola ini dengan baik, yang lainnya tidak.
Diedit untuk menambahkan:Manual Pemrogram Arsitektur AMD64 vol. 2, Pemrograman Sistem, bagian 7.8.7 Mengubah Jenis Memori, menyatakan:
Halaman fisik tidak boleh memiliki jenis cache yang berbeda yang ditetapkan melalui pemetaan virtual yang berbeda; mereka harus berupa semua tipe yang dapat di-cache (WB, WT, WP) atau semua tipe yang tidak dapat di-cache (UC, WC, CD). Jika tidak, hal ini dapat mengakibatkan hilangnya koherensi cache, yang menyebabkan data basi dan perilaku yang tidak dapat diprediksi.
Jadi, pada AMD64, seharusnya aman untuk mmap()
file yang sama atau wilayah memori bersama lagi, selama prot
yang sama dan flags
digunakan; itu akan menyebabkan kernel menggunakan tipe yang dapat di-cache yang sama untuk setiap pemetaan.
Langkah pertama adalah selalu menggunakan dukungan file untuk peta memori. Gunakan mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0)
sehingga pemetaan tidak memesan swap. (Jika Anda lupa ini, Anda akan mengalami batas swap jauh lebih cepat daripada Anda mencapai batas kehidupan nyata yang sebenarnya untuk banyak beban kerja.) Biaya tambahan yang disebabkan oleh dukungan file benar-benar dapat diabaikan.
Diedit untuk menambahkan:Pengguna strcmp menunjukkan bahwa kernel saat ini tidak menerapkan pengacakan ruang alamat ke alamat. Untungnya, ini mudah diperbaiki, hanya dengan memberikan alamat yang dibuat secara acak ke mmap()
bukannya NULL
. Pada x86-64, ruang alamat pengguna adalah 47-bit, dan alamatnya harus disejajarkan dengan halaman; anda bisa menggunakan mis. Xorshift* untuk menghasilkan alamat, lalu sembunyikan bit yang tidak diinginkan:& 0x00007FFFFE00000
akan memberikan alamat 47-bit selaras 2097152-byte, misalnya.
Karena backing adalah file, Anda dapat membuat pemetaan kedua ke file yang sama, setelah memperbesar file backing menggunakan ftruncate()
. Hanya setelah masa tenggang yang sesuai -- saat Anda mengetahui bahwa tidak ada lagi utas yang menggunakan pemetaan (mungkin menggunakan penghitung atom untuk melacaknya?) --, Anda menghapus pemetaan aslinya.
Dalam praktiknya, saat pemetaan perlu diperbesar, Anda memperbesar file pendukung terlebih dahulu, lalu coba mremap(mapping, oldsize, newsize, 0)
untuk melihat apakah pemetaan dapat dikembangkan, tanpa memindahkan pemetaan. Hanya jika pemetaan ulang di tempat gagal, Anda perlu beralih ke pemetaan baru.
Diedit untuk menambahkan:Anda pasti ingin menggunakan mremap()
daripada hanya menggunakan mmap()
dan MAP_FIXED
untuk membuat pemetaan yang lebih besar, karena mmap()
unmaps (atomically) setiap pemetaan yang ada, termasuk milik file lain atau wilayah memori bersama. Dengan mremap()
, Anda mendapatkan kesalahan jika pemetaan yang diperbesar akan tumpang tindih dengan pemetaan yang sudah ada; dengan mmap()
dan MAP_FIXED
, setiap pemetaan yang ada yang tumpang tindih dengan pemetaan baru akan diabaikan (tidak dipetakan).
Sayangnya, harus saya akui bahwa saya belum memverifikasi jika kernel mendeteksi tabrakan antara pemetaan yang ada, atau jika hanya menganggap pemrogram tahu tentang tabrakan tersebut -- lagipula, pemrogram harus mengetahui alamat dan panjang setiap pemetaan, dan oleh karena itu harus tahu apakah pemetaan akan bertabrakan dengan yang lain yang sudah ada. Diedit untuk ditambahkan:Kernel seri 3.8 berfungsi, mengembalikan MAP_FAILED
dengan errno==ENOMEM
jika pemetaan yang diperbesar akan bertabrakan dengan peta yang ada. Saya berharap semua kernel Linux berperilaku dengan cara yang sama, tetapi tidak memiliki bukti, selain pengujian pada 3.8.0-30-generik pada x86_64.
Perhatikan juga bahwa di Linux, memori bersama POSIX diimplementasikan menggunakan sistem file khusus, biasanya tmpfs dipasang di /dev/shm
(atau /run/shm
dengan /dev/shm
menjadi symlink). shm_open()
et. al diimplementasikan oleh pustaka C. Alih-alih memiliki kemampuan memori bersama POSIX yang besar, saya pribadi menggunakan tmpfs yang dipasang khusus untuk digunakan dalam aplikasi khusus. Jika bukan karena hal lain, kontrol keamanan (pengguna dan grup dapat membuat "file" baru di sana) jauh lebih mudah dan lebih jelas untuk dikelola.
Jika pemetaannya, dan harus, anonim, Anda tetap dapat menggunakan mremap(mapping, oldsize, newsize, 0)
untuk mencoba dan ubah ukurannya; itu mungkin gagal.
Bahkan dengan ratusan ribu pemetaan, ruang alamat 64-bit sangatlah luas, dan kasus kegagalan jarang terjadi. Jadi, meskipun Anda harus menangani kasus kegagalan juga, tidak harus cepat . Diedit untuk memodifikasi:Pada x86-64, ruang alamat adalah 47-bit, dan pemetaan harus dimulai pada batas halaman (12 bit untuk halaman normal, 21 bit untuk 2M hugepage, dan 30 bit untuk 1G hugepage), jadi hanya ada 35, 26, atau 17 bit tersedia di ruang alamat untuk pemetaan. Jadi, tabrakan lebih sering terjadi, bahkan jika alamat acak disarankan. (Untuk pemetaan 2M, 1024 peta mengalami benturan sesekali, tetapi pada 65536 peta, kemungkinan benturan (kegagalan pengubahan ukuran) adalah sekitar 2,3%.)
Diedit untuk menambahkan:Pengguna strcmp menunjukkan dalam komentar bahwa secara default Linux mmap()
akan mengembalikan alamat berurutan, dalam hal ini mengembangkan pemetaan akan selalu gagal kecuali itu yang terakhir, atau peta tidak dipetakan di sana.
Pendekatan yang saya tahu bekerja di Linux rumit dan sangat spesifik arsitektur. Anda dapat memetakan ulang pemetaan asli hanya-baca, membuat peta anonim baru, dan menyalin konten lama di sana. Anda membutuhkan SIGSEGV
penangan (SIGSEGV
sinyal dinaikkan untuk utas tertentu yang mencoba menulis ke pemetaan yang sekarang hanya dapat dibaca, ini menjadi salah satu dari sedikit SIGSEGV
yang dapat dipulihkan situasi di Linux bahkan jika POSIX tidak setuju) yang memeriksa instruksi yang menyebabkan masalah, mensimulasikannya (sebagai gantinya memodifikasi konten pemetaan baru), dan kemudian melewatkan instruksi yang bermasalah. Setelah masa tenggang, saat tidak ada lagi utas yang mengakses pemetaan lama yang kini hanya dapat dibaca, Anda dapat menghapus pemetaan tersebut.
Semua kekotoran ada di SIGSEGV
pengurus, tentu saja. Tidak hanya harus dapat memecahkan kode semua instruksi mesin dan mensimulasikannya (atau setidaknya yang menulis ke memori), tetapi juga harus menunggu jika pemetaan baru belum sepenuhnya disalin. Ini rumit, benar-benar tidak dapat dibawa-bawa, dan sangat spesifik arsitektur.. tetapi mungkin.
Ini ditambahkan di kernel 5.7 sebagai flag baru untuk mremap(2) yang disebut MREMAP_DONTUNMAP. Ini membuat pemetaan yang ada tetap di tempatnya setelah memindahkan entri tabel halaman.
Lihat https://github.com/torvalds/linux/commit/e346b3813067d4b17383f975f197a9aa28a3b077#diff-14bbdb979be70309bb5e7818efccacc8
Ya, Anda bisa melakukan ini.
mremap(old_address, old_size, new_size, flags)
menghapus pemetaan lama hanya dengan ukuran "ukuran_lama". Jadi, jika Anda meneruskan 0 sebagai "ukuran_lama", itu tidak akan menghapus peta apa pun.
Perhatian:ini berfungsi seperti yang diharapkan hanya dengan pemetaan bersama, jadi mremap() seperti itu harus digunakan pada wilayah yang sebelumnya dipetakan dengan MAP_SHARED. Ini sebenarnya semua itu, yaitu Anda bahkan tidak memerlukan pemetaan yang didukung file, Anda dapat berhasil menggunakan Kombinasi "MAP_SHARED | MAP_ANONYMOUS" untuk flag mmap(). Beberapa OS yang sangat lama mungkin tidak mendukung "MAP_SHARED | MAP_ANONYMOUS", tetapi di linux Anda aman.
Jika Anda mencobanya di wilayah MAP_PRIVATE, hasilnya kira-kira akan mirip dengan memcpy(), yaitu tidak ada alias memori yang akan dibuat. Namun tetap menggunakan mesin KK. Tidak jelas dari pertanyaan awal Anda apakah Anda memerlukan alias, atau salinan Kontrak Karya juga boleh.
PEMBARUAN:agar ini berfungsi, Anda juga perlu menentukan bendera MREMAP_MAYMOVE.