Я не разработчик ядра, но я потратил годы на то, чтобы философствовать по этому вопросу, потому что я сталкивался с таким много раз. Я на самом деле придумал метафору для всей ситуации, поэтому позвольте мне сказать вам это. Я предполагаю, что таких вещей, как "своп", не существует. В наши дни своп не имеет особого смысла с 32 ГБ ОЗУ.
Представьте себе ваш район, где вода подключена к каждому зданию через трубы, и город должен управлять мощностью. Предположим, что вы производите только 100 единиц воды в секунду (и вся неиспользованная емкость уходит в отходы, потому что у вас нет резервуаров). Каждый дом (дом = маленькое приложение, терминал, виджет часов и т.д.) Требует 1 единицу воды в секунду. Это все хорошо и хорошо, потому что вашему населению около 90 лет, поэтому все получают достаточно воды.
Теперь мэр (= вы) решите, что вы хотите открыть большой ресторан (= браузер). В этом ресторане будет несколько поваров (= вкладки браузера).
Каждый повар нуждается в 1 единице воды в секунду. Вы начинаете с 10 поваров, поэтому общее потребление воды для всего района составляет 100 единиц воды, что все еще хорошо.
Теперь начинается самое интересное: вы нанимаете в свой ресторан еще одного повара, который предъявляет 101 потребность в воде, которой, очевидно, у вас нет. Вам нужно что-то сделать.
Управление водой (= ядро) имеет 3 варианта.
1. Первый вариант - просто отключить услугу для домов, которые недавно не пользовались водой. Это нормально, но если отключенный дом захочет снова использовать воду, им придется снова пройти длительный процесс регистрации. Управление может отключить несколько домов, чтобы высвободить больше водных ресурсов. На самом деле, они отключат все дома, в которых вода не использовалась в последнее время, таким образом, всегда будет доступно некоторое количество бесплатной воды.
Хотя ваш город продолжает функционировать, недостатком является то, что прогресс останавливается. Большая часть вашего времени тратится на ожидание управления водными ресурсами, чтобы восстановить ваш сервис.
Это то, что делает ядро со страницами с файловой поддержкой. Если вы запускаете большой исполняемый файл (например, Chrome), его файл копируется в память. Если в памяти недостаточно памяти или есть части, к которым недавно не обращались, ядро может отбросить эти части, потому что оно в любом случае может перезагрузить их с диска. Если это делается чрезмерно, это останавливает ваш рабочий стол, потому что все будет просто ожидать дискового ввода-вывода. Обратите внимание, что ядро также удалит много наименее недавно использованных страниц, когда вы начнете делать много операций ввода-вывода. Вот почему требуются годы, чтобы переключиться на фоновое приложение после того, как вы скопировали несколько больших файлов, таких как образы DVD.
Это самое раздражающее поведение для меня, потому что я ненавижу хип-хопы, а ты не можешь их контролировать. Было бы неплохо иметь возможность выключить его. Я думаю о чем-то вроде
sed -i 's/may_unmap = 1/may_unmap = (vm_swappiness >= 0)/' mm/vmscan.c
и затем вы можете установить vm_swappiness в -1, чтобы отключить это. Это работало довольно хорошо в моих маленьких тестах, но, увы, я не разработчик ядра, поэтому я никому не отправлял (и, очевидно, небольшая модификация выше не завершена).
2. Руководство может отклонить просьбу нового повара о воде. Это изначально звучит как хорошая идея. Однако есть два недостатка.
Во-первых, есть компании, которые запрашивают много подписок на воду, хотя и не пользуются ими. Одна из возможных причин сделать это - избегать лишних разговоров с водохозяйственной службой, когда им требуется дополнительная вода. Их использование воды идет вверх и вниз в зависимости от времени дня. Например, в случае с рестораном компании нужно намного больше воды в полдень по сравнению с полуночью. Таким образом, они просят всю возможную воду, которую они могли бы использовать, но это тратит водные ресурсы в течение полуночи. Проблема заключается в том, что не все компании могут правильно предвидеть свое пиковое использование, поэтому они запрашивают намного больше в надежде, что им никогда не придется беспокоиться о запросе большего. Несмотря на то, что это затрудняет планирование мощностей для управления водными ресурсами, в обмен на это компании могут упростить и ускорить свои внутренние процессы, потому что им больше не понадобится снова работать с водными ресурсами.
Это то, что делает виртуальная машина Java: она выделяет кучу памяти при запуске, а затем работает из этого. По умолчанию ядро выделяет память только тогда, когда ваше Java-приложение фактически начинает ее использовать. Однако, если вы отключите overcommit, ядро будет серьезно относиться к резервированию. Распределение будет успешным, только если у него действительно есть ресурсы для этого.
Однако есть еще одна, более серьезная проблема с этим подходом.
Допустим, одна компания начинает запрашивать одну единицу воды каждый день (а не с шагом 10). В конце концов вы достигнете состояния, в котором у вас будет 0 бесплатных юнитов. Теперь эта компания не сможет выделять больше. Это хорошо, кто заботится о больших компаниях так или иначе. Но проблема в том, что небольшие дома также не смогут запрашивать больше воды! Вы не сможете построить небольшие общественные ванные комнаты, чтобы справиться с внезапным наплывом туристов. Вы не сможете обеспечить аварийную воду для огня в близлежащем лесу.
С точки зрения компьютера: в ситуациях с очень малым объемом памяти без чрезмерной загрузки вы не сможете открыть новый xterm, вы не сможете подключиться к своей машине по ssh, вы не сможете открыть новую вкладку для поиска возможных исправления. Другими словами, отключение overcommit также делает ваш рабочий стол бесполезным, когда мало памяти.
3. Теперь вот интересный способ решения проблемы, когда компания начинает использовать слишком много воды. Управление водными ресурсами взрывает это!
Буквально: он идет на сайт ресторана, бросает в него динамиты и ждет, пока он не взорвется. Это мгновенно сократит потребности города в воде, так что новые люди могут въехать, вы можете создать общественные ванные комнаты и т.д. Вы, как мэр, можете перестроить ресторан в надежде, что на этот раз потребуется меньше воды. Например, вы скажете людям не ходить в рестораны, если внутри слишком много людей (например, вы откроете меньше вкладок браузера).
Это именно то, что делает ядро, когда у него заканчиваются все параметры и ему требуется память: оно вызывает убийцу OOM. Он выбирает большое приложение (основанное на множестве эвристик) и убивает его, освобождая кучу памяти, но поддерживая отзывчивый рабочий стол. На самом деле ядро Android делает это еще более агрессивно: оно убивает наименее используемое приложение, когда памяти мало (по сравнению со стандартным ядром, которое делает это только в крайнем случае). Это называется убийцей викингов в Android.
Я думаю, что это одно из самых простых решений проблемы: у вас не так много вариантов, как это, так почему бы не преодолеть это раньше, чем позже, верно? Проблема в том, что ядро иногда выполняет довольно много работы, чтобы избежать вызова OOM killer. Вот почему вы видите, что ваш рабочий стол очень медленный, и ядро ничего не делает с этим. Но, к счастью, есть возможность вызвать убийцу ООМ самостоятельно! Сначала убедитесь, что включен магический ключ sysrq (например, echo 1 | sudo tee
/proc/sys/kernel/sysrq
), а затем, когда вы чувствуете, что ядру не хватает памяти, просто нажмите Alt+SysRQ, Alt+f.
Хорошо, так что все это хорошо, но вы хотите попробовать? Ситуация с низкой памятью очень просто воспроизвести. У меня есть очень простое приложение для этого. Вам нужно будет запустить его дважды. Первый запуск определит, сколько свободной оперативной памяти у вас есть, второй запуск создаст ситуацию с нехваткой памяти. Обратите внимание, что этот метод предполагает, что у вас отключен своп (например, выполните sudo swapoff -a
). Код и использование следующим образом:
// gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
int limit = 123456789;
if (argc >= 2) {
limit = atoi(argv[1]);
}
setbuf(stdout, NULL);
for (int i = 1; i <= limit; i++) {
memset(malloc(1 << 20), 1, 1 << 20);
printf("\rAllocated %5d MiB.", i);
}
sleep(10000);
return 0;
}
А вот как вы это используете:
$ gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
$ ./eatmem
Allocated 31118 MiB.Killed
$ ./eatmem 31110
Allocated 31110 MiB.Killed
Первый вызов обнаружил, что у нас есть 31 118 МБ свободной оперативной памяти. Поэтому я приказал приложению выделить 31 110 МБ ОЗУ, чтобы ядро не убивало его, а съело почти всю мою память. Моя система зависла: даже указатель мыши не сдвинулся с места. Я нажал Alt+SysRQ, Alt+f, и это убило мой процесс приема пищи, и система была восстановлена.
Несмотря на то, что мы рассмотрели наши варианты действий в ситуации с нехваткой памяти, наилучший подход (как и любая другая опасная ситуация) - это в первую очередь избегать этого. Есть много способов сделать это. Один из распространенных способов, которые я видел, - помещать неправильно работающие приложения (например, браузеры) в другие контейнеры, чем остальная часть системы. В этом случае браузер не сможет повлиять на ваш рабочий стол. Но сама профилактика выходит за рамки вопроса, поэтому я не буду об этом писать.
TL; DR: хотя в настоящее время нет способа полностью избежать подкачки страниц, вы можете уменьшить полную остановку системы, отключив overcommit. Но ваша система все еще будет неработоспособна в ситуации нехватки памяти, но другим способом.
Независимо от вышесказанного, в ситуации нехватки памяти нажмите Alt+SysRQ, Alt+f, чтобы убить большой процесс выбора ядра. Ваша система должна восстановить свою отзывчивость через несколько секунд. Это предполагает, что у вас включен магический ключ sysrq (по умолчанию это не так).