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

Еще одна вещь не принять в скрипте, запущенном кроном: значение $HOME. Используйте $HOME явно, не предполагайте, что он имеет некоторое конкретное значение. Команды вызова и ссылочные файлы полными путями.

165
29.08.2014, 18:17
7 ответов

Существуют некоторые проблемы с ulimit. Вот полезное чтение по теме: Ограничение времени и потребления памяти программы в Linux, которые приводят к инструменту тайм-аута, который позволяет Вам держать процесс в клетке (и его ветвления) ко времени или потреблению памяти.

Инструмент тайм-аута требует Perl 5 + и /proc файловая система смонтирована. После этого Вы копируете инструмент в, например. /usr/local/bin как так:

curl https://raw.githubusercontent.com/pshved/timeout/master/timeout | \
  sudo tee /usr/local/bin/timeout && sudo chmod 755 /usr/local/bin/timeout

После этого можно 'держать процесс в клетке' потреблением памяти как в вопросе как так:

timeout -m 500 pdftoppm Sample.pdf

Кроме того, Вы могли использовать -t <seconds> и -x <hertz> соответственно ограничить процесс к ограничения ЦП или времени.

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

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

66
27.01.2020, 19:28
  • 1
    Кажется, работает на меня теперь (снова?), но вот версия кэша Google: webcache.googleusercontent.com / … –  kvz 27.04.2017, 15:32
  • 2
    Мы можем использовать тайм-аут вместе с taskset (мы должны ограничить и память и ядра)? –  ransh 24.10.2017, 15:47
  • 3
    Нужно отметить, что этот ответ не обращается к стандарту Linux coreutils утилита того же имени! Таким образом ответ потенциально опасен если вообще где-нибудь в Вашей системе, некоторый пакет имеет ожидание сценария timeout быть стандартом Linux coreutils пакет! Я не знаю, что этот инструмент упаковывается для дистрибутивов, таких как debian. попытка –  user1404316 08.04.2018, 10:03

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

$ ulimit -Sv 500000     # Set ~500 mb limit
$ pdftoppm ...

Это только ограничит "виртуальную" память Вашего процесса, принимая во внимание — и ограничивая — память процесс, вызываемый доли с другими процессами, и с отображенной памятью, но не зарезервированное (например, большая "куча" Java). Однако, виртуальная память является самым близким приближением для процессов, которые становятся действительно большими, совершая упомянутые незначительные ошибки.

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

80
27.01.2020, 19:28
  • 1
    , почему setrlimit более сложный для большего количества детей? man setrlimit говорит мне, что "Дочерний процесс, созданный через ветвление (2), наследовал свои родительские пределы ресурса. Пределы ресурса сохраняются через execve (2)" –  akira 13.02.2011, 10:13
  • 2
    Поскольку ядро не суммирует vm размер для всех дочерних процессов; если бы это сделало то это поняло бы ответ превратно так или иначе. Предел для каждого процесса, и является виртуальным адресным пространством, не использованием памяти. Использование памяти более трудно измерить. –  MarkR 13.02.2011, 10:17
  • 3
    , если я понимаю вопрос правильно затем OP, каков предел на подпроцесс (ребенок).. не всего. –  akira 13.02.2011, 10:21
  • 4
    @MarkR, так или иначе, виртуальное адресное пространство является хорошим приближением для используемой памяти, особенно если Вы запускаете программу, этим не управляет виртуальная машина (скажите, Java). По крайней мере, я не знаю лучшей метрики. –   13.02.2011, 10:23
  • 5
    Просто требуемый для выражения благодарности - это ulimit подход помог мне с firefoxошибка 622816 – Загрузка большого изображения может "заморозить" Firefox или разрушить систему; который на начальной загрузке USB (от RAM) имеет тенденцию замораживать ОС, требуя "жесткого" перезапуска; теперь, по крайней мере, firefox разрушает себя, оставляя ОС живой... выполнения Удачи! –  sdaau 04.04.2013, 18:51
[118933]Другой способ ограничить это - использовать контрольные группы Linux. Это особенно полезно, если Вы хотите ограничить выделение процесса (или группы процессов) физической памяти, отличной от виртуальной. Например: [12177] создаст управляющую группу под названием [119324]myGroup[119325], ограничит набор процессов, запущенных под myGroup до 500 Мб физической памяти и до 5000 Мб подкачки. Для запуска процесса под контрольной группой:[12178]Обратите внимание, что в современном дистрибутиве Ubuntu этот пример требует установки пакета [119326]cgroup-bin[119327] и редактирования [119328]/etc/default/grub[119329] для изменения [119330]GRUB_CMDLINE_LINUX_DEFAULT[119331] на [119330]GRUB_CMDLINE_LINUX_DEFAULT[119331]: [12179]и затем запустить [119332]sudo update-grub[119333] и перезагрузиться для загрузки с новыми параметрами загрузки ядра. [118940]
123
27.01.2020, 19:28

В дополнение к инструментам из daemontools, предложенным Марком Джонсоном, можно также рассмотреть chpst, который находится в runit. Сама прогонка вложена в busybox, так что, возможно, вы уже установили её.

На странице man-страницы chpst показана опция:

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

7
27.01.2020, 19:28

Я использую приведенный ниже скрипт, который отлично работает. Он использует cgroups через cgmanager. Обновление: теперь он использует команды из cgroup-tools. Назовите этот скрипт limitmem и поместите его в $PATH, и вы сможете использовать его как limitmem 100M bash. Это ограничит использование как памяти, так и свопа. Чтобы ограничить только память, удалите строку с memory.memsw.limit_in_bytes.

edit: При установке Linux по умолчанию это ограничивает только использование памяти, но не свопа. Чтобы включить ограничение использования свопа, вам нужно включить учет свопа в вашей системе Linux. Сделайте это, установив/добавив swapaccount=1 в /etc/default/grub, чтобы это выглядело примерно так

GRUB_CMDLINE_LINUX="swapaccount=1"

Затем запустите sudo update-grub и перезагрузитесь.

Оговорка: я не удивлюсь, если cgroup-tools также сломается в будущем. Правильным решением было бы использовать systemd api's для управления cgroup, но для этого нет инструментов командной строки.

#!/bin/sh

# This script uses commands from the cgroup-tools package. The cgroup-tools commands access the cgroup filesystem directly which is against the (new-ish) kernel's requirement that cgroups are managed by a single entity (which usually will be systemd). Additionally there is a v2 cgroup api in development which will probably replace the existing api at some point. So expect this script to break in the future. The correct way forward would be to use systemd's apis to create the cgroups, but afaik systemd currently (feb 2018) only exposes dbus apis for which there are no command line tools yet, and I didn't feel like writing those.

# strict mode: error if commands fail or if unset variables are used
set -eu

if [ "$#" -lt 2 ]
then
    echo Usage: `basename $0` "<limit> <command>..."
    echo or: `basename $0` "<memlimit> -s <swaplimit> <command>..."
    exit 1
fi

cgname="limitmem_$$"

# parse command line args and find limits

limit="$1"
swaplimit="$limit"
shift

if [ "$1" = "-s" ]
then
    shift
    swaplimit="$1"
    shift
fi

if [ "$1" = -- ]
then
    shift
fi

if [ "$limit" = "$swaplimit" ]
then
    memsw=0
    echo "limiting memory to $limit (cgroup $cgname) for command $@" >&2
else
    memsw=1
    echo "limiting memory to $limit and total virtual memory to $swaplimit (cgroup $cgname) for command $@" >&2
fi

# create cgroup
sudo cgcreate -g "memory:$cgname"
sudo cgset -r memory.limit_in_bytes="$limit" "$cgname"
bytes_limit=`cgget -g "memory:$cgname" | grep memory.limit_in_bytes | cut -d\  -f2`

# try also limiting swap usage, but this fails if the system has no swap
if sudo cgset -r memory.memsw.limit_in_bytes="$swaplimit" "$cgname"
then
    bytes_swap_limit=`cgget -g "memory:$cgname" | grep memory.memsw.limit_in_bytes | cut -d\  -f2`
else
    echo "failed to limit swap"
    memsw=0
fi

# create a waiting sudo'd process that will delete the cgroup once we're done. This prevents the user needing to enter their password to sudo again after the main command exists, which may take longer than sudo's timeout.
tmpdir=${XDG_RUNTIME_DIR:-$TMPDIR}
tmpdir=${tmpdir:-/tmp}
fifo="$tmpdir/limitmem_$$_cgroup_closer"
mkfifo --mode=u=rw,go= "$fifo"
sudo -b sh -c "head -c1 '$fifo' >/dev/null ; cgdelete -g 'memory:$cgname'"

# spawn subshell to run in the cgroup. If the command fails we still want to remove the cgroup so unset '-e'.
set +e
(
set -e
# move subshell into cgroup
sudo cgclassify -g "memory:$cgname" --sticky `sh -c 'echo $PPID'`  # $$ returns the main shell's pid, not this subshell's.
exec "$@"
)

# grab exit code 
exitcode=$?

set -e

# show memory usage summary

peak_mem=`cgget -g "memory:$cgname" | grep memory.max_usage_in_bytes | cut -d\  -f2`
failcount=`cgget -g "memory:$cgname" | grep memory.failcnt | cut -d\  -f2`
percent=`expr "$peak_mem" / \( "$bytes_limit" / 100 \)`

echo "peak memory used: $peak_mem ($percent%); exceeded limit $failcount times" >&2

if [ "$memsw" = 1 ]
then
    peak_swap=`cgget -g "memory:$cgname" | grep memory.memsw.max_usage_in_bytes | cut -d\  -f2`
    swap_failcount=`cgget -g "memory:$cgname" |grep memory.memsw.failcnt | cut -d\  -f2`
    swap_percent=`expr "$peak_swap" / \( "$bytes_swap_limit" / 100 \)`

    echo "peak virtual memory used: $peak_swap ($swap_percent%); exceeded limit $swap_failcount times" >&2
fi

# remove cgroup by sending a byte through the pipe
echo 1 > "$fifo"
rm "$fifo"

exit $exitcode
12
20.08.2021, 13:37

Я использую Ubuntu 18.04.2 LTS, и сценарий JanKanis не работает, как он предлагает. Запуск limitmem 100M scriptограничивает 100 МБ ОЗУ с неограниченным свопом.

Запуск limitmem 100M -s 100M scriptзавершается неудачно, так как cgget -g "memory:$cgname"не имеет параметра с именем memory.memsw.limit_in_bytes.

Поэтому я отключил своп:

# create cgroup
sudo cgcreate -g "memory:$cgname"
sudo cgset -r memory.limit_in_bytes="$limit" "$cgname"
sudo cgset -r memory.swappiness=0 "$cgname"
bytes_limit=`cgget -g "memory:$cgname" | grep memory.limit_in_bytes | cut -d\  -f2`
4
20.08.2021, 13:37

В любом дистрибутиве на основе systemd -вы также можете косвенно использовать cgroups через запуск systemd -. Например. для вашего случая ограничения pdftoppmдо 500 МБ ОЗУ используйте:

systemd-run --scope -p MemoryMax=500M pdftoppm

Обратите внимание, :у вас будет запрошен пароль, но приложение будет запущено от имени вашего пользователя. Не позволяйте этому ввести вас в заблуждение, думая, что команда нуждается в sudo, потому что это заставит команду работать под root, что вряд ли было вашим намерением.

Если вы действительно не хотите вводить пароль (, зачем вам пароль для ограничения уже имеющейся памяти ), вы можете использовать опцию --user, однако для этого вам потребуется Включена поддержка cgroupsv2, которая прямо сейчас требует загрузки с systemd.unified_cgroup_hierarchyпараметром ядра .

71
20.08.2021, 13:37

Теги

Похожие вопросы