Linux ограничения оперативной памяти

Обновлено: 06.07.2024

Подсистема виртуальной памяти распределяет память между задачами (процессами). Каждая задача (процесс) считает, что ей выделен непрерывный участок памяти максимального размера, поддерживаемого на соответствующей архитектуре (для архитектуры x86 это 4GB). Из них один гигабайт (только для x86) резервируется для ядра.

Процесс

Основным средством организации работы операционной системы UNIX® (Linux) и единицей многозадачности является процесс - уникальным образом идентифицируемая программа, которая нуждается в получении доступа к ресурсам компьютера. Процесс представляет собой программный код которым манипулирует Операционная система. Операционная система координирует работу разделов данных процесса и определяет среду выполнения.

Память

Данные, связанные с процессом, также являются частью образа процесса. Некоторые из них хранятся в регистрах, обычно представленных регистрами процессора. И существуют динамические области хранения данных (куча), выделяемые процессу по ходу работы при необходимости.

Еще у процесса есть стек, содержащийся в памяти и используемый для хранения локальных переменных программы и передачи параметров. Когда процесс выполняет обращение к функции или подпрограмме, в стек отправляется новый фрейм. Одной из частей каждого фрейма является указатель на базу предыдущего фрейма, который позволяет легко вернуться из вызова функции.

Количество доступной памяти в Linux определяется совокупным значением RAM + swap (пространство подкачки на диске).

На самом же деле программа занимает только тот объем памяти, с которым она реально работает. Большинство памяти существует виртуально, но будет предоставлено программе в тот момент, когда она обратится в эту область.

Ядро распределяет память страницами фиксированного размера. Процедура, когда страница оперативной памяти объявляется частью адресного пространства процесса, называется отображением этой страницы в адресное пространство процесса.

Когда процесс обращается к некоторой странице своего адресного пространства, ядро проверяет, имеет ли он право на доступа к этой странице, и если проверка пройдена и доступ получен, то ядро переадресовывает обращение на реальный адрес этой страницы. Размер страницы фиксирован архитектурой процессора, и для x86 размер составляет 4096 байт.

Если свободных страниц больше нет, но существует файл подкачки, куда ядро может убрать одну из наиболее долго не использовавшихся страниц, и освободившуюся физическую страницу отдать запросившему память процессу.

Если нет ни незанятого пространства в файле подкачки, ни свободных страниц RAM, то развитие событий может быть следующим: либо запросивший память процесс прерван и “убит” системой, либо какой-то другой из процессов (это определяется специфическими алгоритмами) будет “убит” ядром, и освободившаяся память будет передана запросившему память процессу.

Ограничение адресного пространства в 4GB не означает, что система не сможет адресовать более этого объема памяти. На платформе x86 ядро Linux может использовать до 64GB, а ограничение в 4GB накладывается лишь на размер адресного пространства процесса.

Количество памяти в Linux определяется совокупным значением RAM + swap. Память процессам выделяется из этого пула посредством функций языка *alloc() 1)

Память используется экономно, но ядро старается использовать всю память, если память не занята процессами, то ядро использует ее под буферизацию (кэширование) данных. Ценный ресурс не должен болтаться без дела.

Однако ядро позволяет выделять памяти больше чем RAM + swap. Такое поведение ядра называется перевыделение (overcommitting) памяти. Как это возможно без ущерба для устойчивости? Например выполняется некоторое количество процессов вебсервера apache. Примерно 20-30% от пространства памяти выделенного каждому процессу apache зарезервирована, но не используется и потому может быть предоставлена другим процессам. Алгоритм использования этого пространства может регулироваться параметрами ядра.

Контроль использования центрального процессора и памяти в Linux

Скрипт timeout — это полезная программа мониторинга использования ресурсов. Она ограничивает потребление времени и памяти процессом Linux. Это позволяет вам запускать программу под контролем, указав ограничения по времени и памяти, если программа нарушит эти пределы, то она будет остановлена.

Особенностью этого скрипта является то, что он не только контролирует родительский процесс, но также учитывает его дочерние форки. Вы можете выбрать, ограничен ли объем наблюдаемых процессов группой процессов или деревом процессов.

Программа timeout работает по принципу «чёрного ящика», то есть не требует какой-либо настройки самих процессов.

Обратите внимание, что данный скрипт не является одноимённой программой timeout из coreutils — это две разные программы.

Ещё нужно обязательно упомянуть Cgroups (Control Groups), которая контролирует потребление ресурсов системы на уровне ядра. По сравнению с рассматриваемым скриптом timeout, Cgroups сложнее в использовании, но и возможностей там больше.

Как установить timeout

Для работы скрипта на вашей системе Linux нужен установленный Perl 5 и смонтированная файловая система /proc.

Для проверки версии Perl на вашей системе Linux запустите следующую команду:

Поскольку у меня установлена программа timeout из пакета coreutils, то чтобы не вызывать путаницу, я не стал копировать этот скрипт в системную директорию исполнимых файлов. Поэтому я буду вызывать программу по относительному пути так (когда нахожусь в директории со скриптом):

Также программу можно вызывать по абсолютному пути (независимо от вашей текущей рабочей папки) — но у вас путь до файла будет другим:

Опции timeout:

Базовыми опциями являются:

Как ограничить время выполнения программы (нагрузку на процессор)

Для ограничения времени выполнения используется опция -t после которой указывается количество секунд, которое должен проработать скрипт. Пример ограничения работы четыремя секундами:

В выводе показана статистика использования процессора и памяти. Обратите внимание, что имеется ввиду процессорное время, то есть нагрузка на ЦПУ. Чтобы разница была более понятной, посмотрите на следующий пример:

Он проработает не 4 секунды, а 20 секунд, поскольку нагрузка на процессор отсутствует. В выводе будет показано:

Как ограничить потребление памяти

В следующем примере программа будет работать любое количество времени, но если она её потребление ресурсов превысит 10 гигабайт памяти, то она будет остановлена:

Посмотрите на скриншот, на нём видно, как программа постепенно увеличивала количество занятой памяти, а потом была завершена из-за превышения лимита:


Как ограничить использование процессорного времени и памяти

В следующем примере количество используемой памяти ограничено 1 гигабайтом и процессорное время ограничено 9 секундами:

Как можно увидеть, было потреблено менее трёх секунд процессорного времени, а программа была завершена из-за превышения лимита памяти.

Ограничение нагрузки на процессор с множеством коротких дочерних процессов

То есть программа timeout успешно подсчитывает не только ресурсы основного процесса, но и дочерних процессов.

Продвинутые опции

  • -p .*regexp1.*,ИМЯ1;.*regexp2.*,ИМЯ2 — собирает статистику для дочерних процессов с указанными командами. ИМЕНА определяют сегменты, а regexps (регулярные выражения в формате Perl) определяют совпадение дочерних процессов, которые попадают в эти сегменты.
    Если шаблон начинается с CHILD:, тогда в эту категорию собирается информация о работе дочерних совпадающих процессах (поиск совпадений выполняется по остальному шаблону). Обратите внимание, что это единственный способ собрать статистику потреблённого времени дочерними процессами, которые длятся только доли секунды.
  • -o файл_вывода — файл для сброса статистики сегментов, собранной опцией -p
  • --detect-hangups — выключить выявление зависаний. Если вы указали фрагмент опцией -p, тогда если CPU время в любом из фрагментов не увеличивается во время некоторого времени, то скрипт timeout приходит к выводу, что контролируемый процесс завис и выключает его.
  • --no-info-on-success — отключает вывод статистики использования если контролируемый процесс успешно завершил работу.
  • --confess, -c — при выключении контролируемого процесс возвращает его код выхода или сигнал+128. Это также приводит к тому, что timeout ожидает пока контролируемый процесс не завершиться. Без этой опции скрипт возвращает толь.
  • --memlimit-rss, -s — монитор лимита памяти RSS (resident set size)

Сбор информации о «тяжёлых» процессах:

Сбор статистики о «лёгких» дочерних процессах

В этом шаблоне легковесные дочерние процессы должны отслеживаться с помощью специального префикса CHILD:, сравните предыдущую команду и эту:

Почему остальные не показаны в статистике? Все процессы запускаются из Perl, но короткоживущие не отслеживаются в полной мере, поскольку timeout не просыпается достаточно часто.

Выявление зависаний в программе:

Более простой пример для остановки процесса, если он завис:

Итак, скрипт timeout — это простая программа для мониторинга потребления ресурсов, которая ограничивает потребление процессорного времени и памяти любой программы в Linux, она умеет выявлять зависания и подсчитывать статистику, в том числе с учётом дочерних процессов, а также отдельно для дочерних процессов.

Прежде всего, как выяснить процессы каких пользователей потребляют больше всего ресурсов CPU оперативной памяти.

Для текущего момента посмотреть процессы можно при помощи утилиты ps

Команды выведут сортированные списки процессов в одной из колонок каждого списка будет указано имя пользователя. Процессы, запущенные от имени root показываться не будут и выведутся только 35 самых активных процессов.

Подобным образом статистику посмотреть не удасться, например, для веб-сервера с mod_php, однаако в большинстве случаев это как раз та информация, которая нужна для того чтобы выбрать пользователей для которых нужно предусмотреть ограничения.

Ограничения нужны на нагруженных серверах, они существует у каждого хостинг провайдера.

Изменять /etc/security/limits.conf может только пользователь root или другой пользователь работающий из под sudo.

Файл хорошо задокументирован, вся необходимая информация находится в нем в комментариях

В общем виде любое правило выглядит так:

ulimit в Linux и ограничение ресурсов для пользователя

Soft лимиты пользователь может переопределить используя ulimit

Выполнение команды с аргументом -a выведет актуальные ограничения

ulimit -as 1500000
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14685
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 14685
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

Ключи из вывода можно брать и использовать в командах, которые будут задавать ограничения для текущей терминальной сессии.

Дополнительно следует указывать тип ограничения:

Если не указывать ничего лимиты будут заданы жестко. hard здесь можно задать для текущего пользователя даже без прав root, но изменения не сохранятся после перезагрузки и чтобы ограничения были установлены постоянно нужно редактировать файл, который был рассмотрен ранее.

Создадим ограничение по оперативной памяти в 1500 Мб для пользователя

Выполнив ulimit -Hm сейчас можно увидеть, что ограничение установлено. Получить ту же информацию можно просмотрев лимиты для процесса

Можно указывать идентификатор любого процесса, запущенного от имени пользователя, для которого задали ограничение по памяти

Иногда необходимо ограничить процесс или группу процессов по тому или иному ресурсу системы - память, ввод/вывод, процессорное время.
Обычно такое необходимо, если некий процесс может неожиданно "разрастись" и вытеснить остальные процессы в своп или занять всё процессорное время. Т.е. для остальных (или конкретных) процессов надо гарантировать минимальный объём ресурсов системы.
Раньше для таких вещей использовали ulimit. Но такие ограничения хоть и распространяются на порожденные (fork) процессы, но между собой не суммируются. Т.е. если процесс порождает дочерние, контролировать их суммарное воздействие на систему через ulimit не получится - у каждого дочернего процесса ограничения будут рассчитываться независимо от других процессов, в т.ч. и родительского. Кроме того, что касается памяти, ulimit ограничивает "виртуальное адресное пространство", а не реальный объём оперативной памяти. Ввод/вывод ulimit не ограничивает вовсе.
Как вариант, можно контролировать "разрастающийся" процесс и убивать его с помощью timeout, но далеко не всегда "убийство" приемлемо.
В этом случае на помощь приходят control groups (cgroups). Что они могут:
- Привязывать процессы на конкретные процессоры
- Ограничивать процессорное время, указав "вес" процесса, а так же выставлять абсолютные ограничения для realtime процессов
- Ограничивать использование оперативной памяти, в т.ч. с учетом свопа
- Ограничивать ввод/вывод, как в абсолютных величинах, так и по "весу" процесса в системе
- Вести аудит использования ресурсов
Управление cgroups выполняется аналогично работе с procfs, т.е. через echo/cat соответствующих управляющих файлов. Так же есть удобные утилиты, выполняющие аналогичные действия - libcgroup-tools (в RHEL). В новых системах cgroup монтируются автоматически, поэтому не будем на этом останавливаться. Если нет - смотрим man по cgconfig.conf :)
А теперь сразу к примеру.
Ограничим по памяти некий процесс с pid 1234 и его детишек до 1Гб:

Престо! :) Для ограничения по памяти+своп memory.limit_in_bytes меняем на memory.memsw.limit_in_bytes.

Ограничение по CPU делается аналогичным образом - заместо memory используется директория (контроллер) cpu.
С помощью параметра cpu.shares регулируется "вес" процесса - 1024 значение по умолчанию:

Ограничение процессов реального времени (realtime) по процессорному времени: параметры cpu.rt_runtime_us и cpu.rt_period_us. Первый указывает максимальный непрерывный период, когда процесс может выполняться, а второй - промежуток времени, когда этот возможно. Т.е. для cpu.rt_runtime_us=1000 и cpu.rt_period_us=3000, процесс может непрерывно работать целую секунду, но не чаще, чем каждые 3 секунды.
Привязка к конкретным процессорам:

Ограничение ввода/вывода немного отличается. Мы так же создаём свою cgroup в директории blkio (контроллере), но вот ограничения вводятся следующим образом:

Первое и второе число, указанные через двоеточие - это мажорный и минорный номер устройства (смотрим его через ls -l /dev/<имя_устройства>), третье - само ограничение. В данном случае "вес" - по умолчанию, 500.
Также интересны следующие параметры:

Читайте также: