#define ONE_K (1024)
int main {
char *some_memory;
int size_to_allocate = ONE_K;
int megs_obtained = 0;
int ks_obtained = 0;
while (1) {
for (ks_obtained = 0; ks_obtained < 1024; ks_obtained++) {
some_memory = (char *)malloc(size_to_allocate);
if (some_memory == NULL) exit(EXIT_FAILURE);
sprintf(some_memory, "Hello World");
}
megs_obtained++;
printf("Now allocated %d Megabytes\n", megs_obtained);
}
exit(EXIT_SUCCESS);
}
На этот раз вывод, также сокращенный, выглядит следующим образом:
$ ./memory3
Now allocated 1 Megabytes
...
Now allocated 1535 Megabytes
Now allocated 1536 Megabytes
Out of Memory: Killed process 2365
Killed
После этого программа завершается. Она выполняется несколько секунд и существенно замедляется при приближении к размеру, равному объему физической памяти на компьютере, а также активно использует жесткий диск. Тем не менее программа выделяла и получала доступ к области памяти, большей по размеру объема физической памяти, которая была установлена на машине одного из авторов во время написания этой главы. В конце концов, система защищает себя от этой довольно агрессивной программы и уничтожает ее. В некоторых системах она может тихо закончить выполнение, когда функция malloc
завершается аварийно.
Как это работает
Память, выделяемая приложению, управляется ядром системы Linux. Каждый раз, когда программа запрашивает память, пытается записывать в память или считывать из памяти, которая была выделена, ядро Linux решает, как обрабатывать этот запрос.
Сначала ядро может использовать свободную физическую память для удовлетворения запроса приложения на выделение памяти, но когда физическая память исчерпана, ядро начинает использовать так называемую
Ядро перемещает данные и программный код между физической памятью и областью свопинга так, что при каждом чтении из памяти или записи в нее данные кажутся находящимися в физической памяти, где бы они не находились на самом деле перед вашей попыткой обратиться к ним.
Говоря более профессиональным языком, система Linux реализует систему виртуальной памяти с подкачкой страниц по требованию. Вся память, видимая программами пользователя, — виртуальная, т. е. реально не существующая в физическом адресном пространстве, используемом программой. Система Linux делит всю память на страницы, обычно размером 4096 байтов. Когда программа пытается обратиться к памяти, выполняется преобразование виртуальной памяти в физическую, конкретный способ реализации которого и затрачиваемое на преобразование время зависят от конкретного оборудования, применяемого вами. Когда выполняется обращение к области памяти, физически нерезидентной, возникает ошибка страницы памяти и управление передается ядру.
Ядро Linux проверяет адрес, к которому обратилась программа, и, если это допустимый для нее адрес, определяет, какую страницу физической памяти сделать доступной. Затем оно либо выделяет память для страницы, если она еще не записывалась ни разу, либо, если страница хранится на диске в области свопинга, считывает страницу памяти, содержащую данные, в физическую память (возможно выгружая на диск имеющуюся в памяти страницу). Затем после преобразования адресов виртуальной памяти в соответствующие физические адреса ядро разрешает пользовательской программе продолжить выполнение. Приложениям в ОС Linux не нужно заботиться об этих действиях, поскольку их реализация полностью скрыта в ядре.
В итоге, когда приложение исчерпает и физическую память, и область свопинга или когда она превысит максимальный размер стека, ядро откажется выполнить запрос на дальнейшее выделение памяти, может завершить программу и выгрузить ее.