Saya ingin membuat program yang akan mensimulasikan situasi out-of-memory (OOM) pada server Unix. Saya membuat pemakan memori yang sangat sederhana ini:
#include <stdio.h>
#include <stdlib.h>
unsigned long long memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
void *memory = NULL;
int eat_kilobyte()
{
memory = realloc(memory, (eaten_memory * 1024) + 1024);
if (memory == NULL)
{
// realloc failed here - we probably can't allocate more memory for whatever reason
return 1;
}
else
{
eaten_memory++;
return 0;
}
}
int main(int argc, char **argv)
{
printf("I will try to eat %i kb of ram\n", memory_to_eat);
int megabyte = 0;
while (memory_to_eat > 0)
{
memory_to_eat--;
if (eat_kilobyte())
{
printf("Failed to allocate more memory! Stucked at %i kb :(\n", eaten_memory);
return 200;
}
if (megabyte++ >= 1024)
{
printf("Eaten 1 MB of ram\n");
megabyte = 0;
}
}
printf("Successfully eaten requested memory!\n");
free(memory);
return 0;
}
Ini memakan banyak memori seperti yang didefinisikan di memory_to_eat
mana sekarang adalah tepat 50 GB RAM. Ini mengalokasikan memori sebesar 1 MB dan mencetak titik di mana ia gagal mengalokasikan lebih banyak, sehingga saya tahu nilai maksimum mana yang berhasil dimakan.
Masalahnya adalah itu berfungsi. Bahkan pada sistem dengan memori fisik 1 GB.
Ketika saya memeriksa bagian atas saya melihat bahwa proses memakan 50 GB memori virtual dan hanya kurang dari 1 MB memori penduduk. Apakah ada cara untuk membuat pemakan memori yang benar-benar mengkonsumsinya?
Spesifikasi sistem: Linux kernel 3.16 ( Debian ) kemungkinan besar dengan overcommit diaktifkan (tidak yakin bagaimana cara memeriksanya) tanpa swap dan tervirtualisasi.
sumber
sysctl -w vm.overcommit_memory=2
sebagai root; lihat mjmwired.net/kernel/Documentation/vm/overcommit-accounting . Perhatikan bahwa ini mungkin memiliki konsekuensi lain; khususnya, program yang sangat besar (mis. browser web Anda) mungkin gagal menelurkan program pembantu (mis. pembaca PDF).Jawaban:
Ketika
malloc()
implementasi Anda meminta memori dari kernel sistem (melalui panggilan sistemsbrk()
ataummap()
), kernel hanya membuat catatan bahwa Anda telah meminta memori dan di mana memori akan ditempatkan di dalam ruang alamat Anda. Itu sebenarnya belum memetakan halaman-halaman itu .Ketika proses selanjutnya mengakses memori dalam wilayah baru, perangkat keras mengenali kesalahan segmentasi dan memperingatkan kernel untuk kondisi tersebut. Kernel kemudian mencari halaman di struktur datanya sendiri, dan menemukan bahwa Anda harus memiliki halaman nol di sana, sehingga peta di halaman nol (mungkin pertama kali mengusir halaman dari cache halaman) dan kembali dari interupsi. Proses Anda tidak menyadari bahwa semua ini terjadi, operasi kernel sangat transparan (kecuali untuk penundaan singkat saat kernel melakukan tugasnya).
Pengoptimalan ini memungkinkan panggilan sistem untuk kembali dengan sangat cepat, dan, yang paling penting, itu menghindari sumber daya apa pun untuk berkomitmen pada proses Anda ketika pemetaan dilakukan. Ini memungkinkan proses untuk menyimpan buffer yang agak besar yang tidak pernah mereka butuhkan dalam keadaan normal, tanpa takut menelan terlalu banyak memori.
Jadi, jika Anda ingin memprogram pemakan memori, Anda benar-benar harus melakukan sesuatu dengan memori yang Anda alokasikan. Untuk ini, Anda hanya perlu menambahkan satu baris ke kode Anda:
Perhatikan bahwa cukup memadai untuk menulis satu byte dalam setiap halaman (yang berisi 4096 byte pada X86). Itu karena semua alokasi memori dari kernel untuk suatu proses dilakukan pada granularity halaman memori, yang pada gilirannya, karena perangkat keras yang tidak memungkinkan paging pada granularities yang lebih kecil.
sumber
mmap
danMAP_POPULATE
(meskipun perhatikan bahwa halaman manual mengatakan " MAP_POPULATE didukung untuk pemetaan pribadi hanya sejak Linux 2.6.23 ").mlockall(MCL_FUTURE)
. menelepon . (Ini membutuhkan root, karenaulimit -l
hanya 64kiB untuk akun pengguna pada instalasi default Debian / Ubuntu.) Saya baru mencobanya di Linux 3.19 dengan sysctl defaultvm/overcommit_memory = 0
, dan halaman yang dikunci menggunakan swap / fisik RAM.malloc()
panggilan itu kembalinull
. Itu jelas kelemahan dari pendekatan manajemen memori ini. Namun, sudah ada salinan-on-tulis-pemetaan (pikirkan perpustakaan dinamis danfork()
) yang membuatnya tidak mungkin bagi kernel untuk mengetahui berapa banyak memori yang sebenarnya akan dibutuhkan. Jadi, jika itu tidak terlalu banyak memori, Anda akan kehabisan memori peta jauh sebelum Anda benar-benar menggunakan semua memori fisik.SIGSEGV
sinyal harus dikirim ke proses.Semua halaman virtual memulai copy-on-write dipetakan ke halaman fisik zeroed yang sama. Untuk menggunakan halaman fisik, Anda dapat mengotori dengan menulis sesuatu untuk setiap halaman virtual.
Jika berjalan sebagai root, Anda dapat menggunakan
mlock(2)
ataumlockall(2)
meminta kernel memasang halaman ketika dialokasikan, tanpa harus mengotori mereka. (pengguna non-root normalulimit -l
hanya memiliki 64kiB.)Versi kode yang ditingkatkan, yang melakukan apa yang diinginkan OP:
Ini juga memperbaiki ketidakcocokan string format printf dengan jenis memory_to_eat dan dimakan_mory, gunakan
%zi
untuk mencetaksize_t
bilangan bulat. Ukuran memori untuk dimakan, dalam kiB, secara opsional dapat ditentukan sebagai baris perintah arg.Desain berantakan menggunakan variabel global, dan tumbuh dengan 1k bukannya halaman 4k, tidak berubah.
sumber
Optimalisasi yang masuk akal sedang dilakukan di sini. Runtime tidak benar-benar mendapatkan memori sampai Anda menggunakannya.
Sederhana
memcpy
akan cukup untuk menghindari optimasi ini. (Anda mungkin menemukan bahwacalloc
masih mengoptimalkan alokasi memori hingga titik penggunaan.)sumber
malloc
mengalokasikan pada batas halaman apa yang tampaknya cukup mungkin bagi saya.calloc
mengambil keuntungan dari mmap (MAP_ANONYMOUS) memberikan halaman-halaman yang di-zeroed, jadi itu tidak menduplikasi pekerjaan kernel-page-zeroing.Tidak yakin tentang yang satu ini tetapi satu-satunya penjelasan yang dapat saya lakukan adalah bahwa linux adalah sistem operasi copy-on-write. Ketika seseorang memanggilfork
kedua proses menunjuk ke memori fisik yang sama. Memori hanya disalin setelah satu proses benar-benar MENULIS ke memori.Saya pikir di sini, memori fisik yang sebenarnya hanya dialokasikan ketika seseorang mencoba menulis sesuatu untuk itu. Memanggil
sbrk
ataummap
mungkin hanya memperbarui pembukuan memori kernel. RAM yang sebenarnya hanya dapat dialokasikan ketika kita benar-benar mencoba mengakses memori.sumber
fork
tidak ada hubungannya dengan ini. Anda akan melihat perilaku yang sama jika Anda mem-boot Linux dengan program ini sebagai/sbin/init
. (yaitu PID 1, proses mode pengguna pertama). Anda memiliki ide umum yang tepat dengan copy-on-write, meskipun: Sampai Anda mengotori mereka, halaman yang baru dialokasikan semuanya copy-on-write dipetakan ke halaman zeroed yang sama.