Sunday, 3 May 2009
Про память: OOM Killer
Начинаю новый цикл «про память». Первой темой будет магический OOM Killer.
OOM Killer — это способ ядра решить проблему, когда памяти недостаточно. Известно, что виртуальной памяти может быть бесконечно много (в пределах адресации), а вот физической — вполне конечное число. Иногда процессы системы съедают ее всю, и системе надо кого‑то убить, чтобы продолжить работу. Текущая реализация OOM Killer в Linux стремится выбрать наименее важный процесс. Он выбирает среди всех процессов, кроме init и kernel threads, самый негодный (badness).
Алгоритм расчета уровня негодности процесса (итоговое значение будет измеряться в очках негодности (badness ponts)):
Берется размер виртуальной памяти процесса (
total_vm
). Это базовые очки негодности (mm/oom_kill.c:69).К текущим очкам прибавляется
total_vm/2 + 1
для всех порожденных процессов (mm/oom_kill.c:85).Текущие очки делятся на
int_sqrt(cpu_time)
, где cpu_time — это user + system время процесса сдвинутое вправо наSHIFT_HZ + 3
, т.е. дляHZ=1000
приблизительно будет равен значениюint_sqrt((utime+stime)/10)
. причем если результат деления и последующего округления будет 0 — то очки не изменяются (mm/oom_kill.c:100).Текущие очки делятся на
int_sqrt(int_sqrt(run_time/1024))
, где run_time — время прошедшое с момента запуска процесса. Если результат 0 — то очки не изменяются (mm/oom_kill.c:100).Очки умножаются на 2, если
nice
процесса больше 0 (mm/oom_kill.c:118).Если процесс имеет привилегию CAP_SYS_ADMIN или CAP_SYS_RESOURCE или (e)uid в нуле, то текущие очки делятся на 4 (mm/oom_kill.c:125).
Если процесс имеет привилегию CAP_SYS_RAWIO, то текущие очки делятся на 4 (mm/oom_kill.c:133).
Если память процесса, для которого мы считаем очки негодности, пересекается с памятью процесса, для которого в момент выделения новой памяти произошла ошибка
out of memory
, тогда очки делятся на 8 (для ядер старше 2.6.28, mm/oom_kill.c:142).Набранные очки умножаются на
2oom_adj
, где oom_adj — берется из/proc/$PID/oom_adj
, он может принимать значения от -17 до 15. В случае значения -17 процесс не будет тронут OOM Killer (mm/oom_kill.c:150).
И самый негодный (с самыми большими очками) процесс будет убит.
Небольшие пояснения.
- При расчете дочерних
total_vm
учитываются только процессы с самостоятельной виртуальной памятью. Т.е. не потоки. - Предполагается, что если приоритет больше нуля, то выполнение этого процесса менее критично, чем выполнение процессов с отрицательным приоритетом.
- Предполагается, что root-процессы важнее, чем процессы непривилегированных пользователей.
- Убийство процессов, которые осуществляют прямую работу с устройствами, может повлечь за собой нежелательные последствия.
- OOM Killer стремится убивать более молодые процессы. Это надо, чтобы OOM Killer убил только что запущенный процесс с утечкой памяти и не тронул старые, добротные процессы, которые просто кушают много памяти ☺
- OOM Killer стремится сохранить жизнь процесса, при выделении памяти для которого произошла ошибка
out of memory
, и процессам, у которых с ним есть общая память (для ядер старше 2.6.28).
Доступные пользователю настройки:
- самый простой способ повлиять на OOM Killer — использовать vm.overcommit_memory;
vm.oom_dump_tasks
— делать dump всех процессов за исключением kernel threads, в dump попадает pid, uid, tgid, vm size, rss, cpu и oom_adj. Имеет смысл включать только для отладки OOM Killer;vm.oom_kill_allocating_task
— убивать процесс, процесс которому не хватило памяти, без выбора самого плохого;vm.panic_on_oom
— считать запуск OOM критической ошибкой.
Comments
Всё чётко и понятно.. кроме одной вещи: в пунктах 3, 4 и 5 в случае получения нуля после округления очки е меняются. правильно ли я понял, что это значит следующее: допустим, количество очков было равно 49, вот мы делим это на 100, получаем 0.49,округляем, итого — ноль, значит количество очков остаётся 40? Но при этом же если было 51 очко -> 51/100 =0.51, то в результате будет единица, да? как-то это плохо.. изначальная разница в 2 очка в результате приводит к разнице в 38 очков.. я что-то не так понял?)
Что не понятно в пункте 5?
Про пункт 3 и 4 записал в пояснение, спасибо.
Боюсь это была опечатка)) имелись ввиду конечно же только 3 и 4 пункты. Пояснение осознал, спасибо.
для чего, вообще этот страшный оом киллер? Можно, вообще, обойтись без него? Я, честно говоря, обрадовался, не найдя в параметрах ядра overcommit_memory, я думал, что эту систему убрали из ядра насовсем…
Как зачем? А что системе делать если кончилась физическая память? Падать в панику?
как положено, сказать “памяти больше нет”, извините.
Представим себе ситуацию, если бы винда сама решала, что удалить на винте, когда у нее заканчивается место. Просто тупо находила бы самые ненужные с ее точки зрения файлы и на их место писала бы чтото очень важное, например, кэш эксплоррера… Вместо того, что бы просто сказать “прости, брат, места больше нет”
сравнивать файлы на диски и запущенные программы некорректно. если проводить аналогию то винда выдает тебе окошко “не хватает места на диске, не хотите почистить кое какие временные файлы?”
и вот однажды твой сервер перестает работать.. ты пытаешся подключится к нему по ssh и тебе отвечают “прости, брат, но памяти больше нет”. а все изза того что какая то прога текла слишком сильно. и все, больше ты ничего сделать не сможешь потому что на все будет выдаватся эта фраза. только ребут.
Позолю себе не согласиться. 1) для рута резервируестя 5% памяти и диска и 2) Так прибейте ту прогу, которая в данный момент пытаестя писать в память. Ну нету у нас больше памяти. Если прога течет, она самоубъется с очень большой вероятностью и освободит всю свою память.
Так нет же. Пишущий процесс неприкосновенен.
Сорри за флейм…
Не верно одно утверждение. Не все fs резервируют место под root. И этот резерв устанавливается при создание fs. В то время как для памяти он kernel-specific.
Вообще смерть процесса который пишет, и еще в raw-mode в устройства, может быть куда опасней для данных, что хранятся, чем просто процесса который спит, согласен?
Очень сильно эта штука пригождается на embedded-устройствах, где нет свапа. В панику никак нельзя, железка должна работать!
не понимаю, как такое возможно. Как можно работать, если чтото, о чем мы думали, что оно работает, уже прибито? Мне кажется, что скорее именно на эмбедед устройствах правильнее уйти перезагрузиться по кернел паник.
Зачем мне аллоцировать память, которую я не буду использовать? Как тот счетовод из “маленького принца”, что бы знать, что она у меня есть?
Если я, как программист, аллоцирую память, стало быть, я рассчитываю ее использовать. Если памяти нет, надо мне об этом сообщить. Иначе мне прийдется уже после malloc делать проверку, что в системе еще есть память перед каждой попыткой записи. Маразм.
Получается, что ядро пытается делать оптимизацию за программиста. Причем делает это по-мастдайски. И проблема эта идет с самых далеких времен. Спасибо, хоть, что дали возможность эту оптимизацию вырубить. Почему по умолчанию такие гадкие параметры, не понятно
Спасибо за параметр overcommit_ratio. Было не понятно, надо его ставить в 0 или в 1. Теперь все будет хорошо. :)
обычно прибивается какой-нибудь сервис, у которого течет память. а еще есть демоны, которые должны эти другие демоны проверять и в случае чего перезапускать.
а если это просто корявый веб-итерфейс со взбесившимяс скриптом? ядро прибило скрипт и все! пользователь просто перезагрузит страничку. не перезагружать же ради этого всю железяку?!!
и еще масса примеров, где это нужно… не нужно — вырубайте! благо есть такая возможность! система-то гибкая!
Я дописал про настройки и написал отдельный пост про overcommit. Думаю этого будет достаточно что бы скорректировать поведение OOM Killer как вам хочется :)
А что касается примера с сриптом, то не факт что прибьется скрипт, может запросто подохнуть какой-нибудь торрент клиент ;)
точно точно. Скорее всего подохнет чтонить другое. Как нам было сказано, киллер пытается сначала прибить новеньких, но не того процесса, который отжирает память в текущий момент. Стало быть, если скрипт сбесился, сначала будет убито все подряд, включая демонов, которые должны перезапускать сервисы, а потом уже, может быть, прибъется и сам скрипт. Се ля ви.
vm.oom_kill_allocating_task ставишь в 1 и подохнет тот процесс, на выделение памяти которого произошел out of memory
Век живи, век учись. Спасибо.
Я где-то давал ссылку на то, с какой версии это появилось :)
Да, без него обойтись можно и другие ОС так и делают. Да и в Linux его вполне можно отключить.
OOM killer это всего лишь одна из вариантов решения проблемы виртуального пространства. И именно она принята в Linux по умолчанию.
Дело в том, что часто приложения аллоцируют намного больше памяти, чем им реально нужно. Если кратко, и не вдаваясьв детали, то работает это так : Когда прилоение просит выделить память (например используя malloc ) по получает ссылку на виртуальную страницу. Реальное же пространство выделяется только тогда, когда это пространство начинает использоваться.
Когда же приложение не использует выделенную ей память и памяти совсем мало то эти страницы перемещаются из оперативной памяти в swap.
Теперь давайте рассмотрим ситуацию, когда в swap уже нет места. В оперативной памяти его тоже нет, а приложение, ранее выделевшее себе кучу памяти наконец решило в него что-то записать. Аллоцировать эту память ядру не от куда. При этом сообщить приложению что нет памяти так невозможно, так как malloc на эту память запускался ранее и отработал без ошибок. Что же делать в такой ситуации? В linux это решается запуском OOM killer’а который убивает процессы, высвобождая память, необходимую нашему приложению для работы.
В других OS это реализовано иначе. Да и в linux можно сделать echo 2 > /proc/sys/vm/overcommit_memory
Не забываем ставить vm.overcommit_ratio в 0.
Наверное следующий пост будет про эти магические опции.
Почитал вдумчиво документацию, вот понять не могу одного а как overcommit_memory=2 влияет на COW при fork()? По идеи он должен выключать вообще все оптимизации такого рода?
Можешь ссылки в ядро кинуть?
overcommit_memory никак не влияет на COW при fork. Он включает в ядре проверку на суммарный допустимый размер адресуемого виртаульного пространства (vm).
Когда запускается fork(), или же например делается mmap c MAP_PRIVATE то размер адресного пространства, необходимого для адресации известен. Но пока данные не изменены страницы просто ссылаются на неизмененные данные.
В случае overcommit_memory=2 размер виртуального адресного пространства (vm) будет равен “размер swap”+ “размер RAM”*(x/100) где x собственно и есть overcommit_ratio.
Таким образом при использовании overcommit_ratio=0 в реальной жизни большая часть памяти не исользуется, так как резервируется место подо все виртуальноа адресное пространство, тогда как далеко не все оно адресует уникальные данные(скопированные COW алгоритмом и принадлежащие именно этому процессу страницы). По этому я думаю логичным будет overcommit_ratio=50 и по моему оно и является значением по умолчанию.
Насчет ссылко на код то если я не ошибаюсь, функция называется что-то вроде __vm_enough_memory где очень показательна проверка при overcommit_memory=0 ;-) Хотя с тех пор когда я туда заглядывал могло и измениться.
Коллеги, а что такое COW (http://progopedia.ru/dialect/cow/)?
“размер swap”+ “размер RAM”(x/100), при х=0 будет равно размеру swap. Скорее всего, проверка будет такой: “размер swap”+ “размер RAM”(1+ x/100).
Только я хотел возрадоваться, что дела пошли на лад и можно сделать так, что бы линукс правильно считал количество памяти, так все равно ерунда получаестя. (((
А скажите тогда вот что. Всякие там top’ы показывают какое количество памяти?
COW — Copy On Write
В каких колонках? В RES — сколько физической памяти, в VIRT — сколько у процесса всего виртуальной. Ну а SWAP это VIRT — RES :)
Спасибо. Сообразил.
Как всегда, фраза «с большим приоритетом» воспринимается неправильно. Под приоритетом подразумевается nice конечно же?
А вообще, черная магия. Есть какая-то мат-основа под этим? Или чисто эмпирические наблюдения.
nice-nice. Спасибо, записался :)
А мат. основания — нет, нету. Весь этот алгоритм именно что русская рулетка. Для процессов.
В заметке приведены ссылки на исходный код с указанием номеров строк. Возможно я ошибаюсь, но в гиперссылках отсутствует явное указание на <<ту самую>> версию файла oom_kill.c, которую имел в виду автор, когда писал заметку. В будущем, по мере внесения изменений в этот файл, номера строк перестанут соответствовать описанной в тексте функциональности (например, функциональность может остаться той же, но будет реализована в строчках с другими номерами). Подобное несоответствие может ввести читателя в заблуждение.
думаете стоит так сделать? Если заботиться о читателе настолько сильно, то стоит поднять зеркало git репозитория и давать ссылки внутри, т.е. в зону моей ответствености.
я ж советовал сделать ссылку на релиз, а не на HEAD Ж:-)
ну на head я и писал :)
зато там есть хитрый хеш ;)
hash коммита на который писал статью добавил, спасибо.
Товарищи, а почему oom killer не может отследить fork-бомбу?
например на питоне:
import os while(1): os.fork()
не подскажете, параметр vm.oom_kill_allocating_task с какой версии ядра появился? нашёл упоминание в 2.6.24 в 2.6.18 вроде еще нету
и ещё вопрос может ли oom-killer начинать своё кровавое дело, если и свопа и физ. памяти ещё предостаточно?
а то у меня тут ситуация забавная, ядро 2.6.18, overcommit_memory=2, overcommit_ratio=50, кто-то захотел памяти, сперва пореклеймили весь кеш фс, полезли в своп (при этом освобождённую память никто не занял) на ~3.3Gb свободной памяти и ~5.3 свопа проснулся oom-killer и устроил тотал десматч вендетту
Появился он начиная с этого коммита а если идти по версиям, то начиная с 2.6.24.
Про ваш случай — программа могла просто захотеть много памяти. Скорее всего это и было.
потому, что процесс, который сейчас запрашивает память, он старается не убивать. Плюс у форк бомбы маленький размер, что делает ее еще менее желаемой мишенью для киллера.
Вообще если ты создаешь fork() большого и тяжелого приложения, то благодаря COW форкнется оно мгновенно и память будет, фактически, общей, до тех пор пока дочка или родитель не начнет модифицировать память. Тогда создаться копия страницы в которой живет память, которую решили начать править.
Т.е. если делать форк бомбу так:
malloc(много_памяти) заполняем ее единицами fork() заполняем ее нулями for() заполняем ее единицами
и т.п., то да, oom-killer придет крайне быстро ;)
от форк бомбы как раз поможет ограничение по количеству процессов/памяти для одного пользователя.
понятно. у меня курсач походу выливается в решение как раз етой проблемы с форк бомбой. понятно, что можно ограничить кол-во процессов на юзера, но саму бомбу ето не убьёт. быть может есть какой нибудь простой способ убить форк бомбу?
Comment form for «Про память: OOM Killer»
думай, чем форк-бомба отличается от хороших процессов в системе, отследи эту характеристику и убей. сходу так, если подумать, можно отстреливать пользователей, которые уперлись в свой лимит по количеству процессов. Можно анализировать вывод ps, строить двоичное дерево, если все ветви дерева имеют одно название, стало быть это форк.
Потом, как убивать – если прибить родителя, не факт, что погибнут все головы гидры. А как только останется одна веточка, гидра возродится вновь.
сори за офтоп.
PS: Думай. Для того тебе курсач и выдали, что бы ты думал :)
Процессов да. Памяти нет.
потому что создание fork() не выделяет, фактически много новой памяти. Спасибо COW. Т.е. oom-killer может начать грохать форки, но, блин, система уйдет в себя раньше :(
А где другие части “про память”?
Пишутся. Просто нужно поймать вдохновение и дописать. Часто это бывает сложно :(
У нас на работе тут такая ситуация…
36 GiB памяти на 32bit ядре. Оказалось что LowMem быстро заполняется и из-за LowMem как раз вызывается OOM killer, который убивает машину напрочь. При этом в HighMem памяти выше крыши и swap вообще не используется.
Попробовали пока на одной машине отключить OOM killer. Посмотрим что произойдет.
А почему бы не взять 64bit ядро?
Доброздравствуйте. Набрел на ваши преполезные статьи о памяти, спасибо за подробные разъяснения. У вас упоминается — новый цикл о памяти, не подкинете ссылку на старые статьи? Не смог самостоятельно найти в блоге.
И не подскажете — можно ли в ветке 2.6.3х регулировать количество памяти отдаваемой под cached?