Ogranicz użycie pamięci dla pojedynczego procesu Linux

151

Pracuję, pdftoppmaby przekonwertować plik PDF dostarczony przez użytkownika na obraz 300DPI. Działa to świetnie, z wyjątkiem sytuacji, gdy użytkownik dostarczy plik PDF o bardzo dużym rozmiarze strony. pdftoppmprzydzieli wystarczającą ilość pamięci, aby pomieścić obraz 300DPI o takim rozmiarze w pamięci, który dla strony o przekątnej 100 cali wynosi 100 * 300 * 100 * 300 * 4 bajtów na piksel = 3,5 GB. Złośliwy użytkownik może po prostu dać mi głupio duży plik PDF i powodować różnego rodzaju problemy.

Chciałbym więc ustalić jakiś twardy limit wykorzystania pamięci dla procesu potomnego, który zamierzam uruchomić - po prostu umieść proces, jeśli spróbuje przydzielić więcej niż, powiedzmy, 500 MB pamięci. Czy to jest możliwe?

Nie sądzę, że można do tego użyć ulimit, ale czy istnieje odpowiednik jednego procesu?

Ben Dilts
źródło
Może docker?
Sridhar Sarnobat

Odpowiedzi:

58

Istnieją pewne problemy z ulimit. Oto przydatna lektura na ten temat: Ograniczanie czasu i zużycia pamięci przez program w systemie Linux , co prowadzi do narzędzia do limitowania czasu , które pozwala na umieszczenie procesu (i jego rozwidleń) w klatkach według zużycia czasu lub pamięci.

Narzędzie do przekroczenia limitu czasu wymaga Perla 5+ i podłączonego /procsystemu plików. Następnie skopiuj narzędzie, aby np. /usr/local/binTak:

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

Następnie możesz „zablokować” proces według zużycia pamięci, tak jak w pytaniu:

timeout -m 500 pdftoppm Sample.pdf

Alternatywnie możesz użyć -t <seconds>i -x <hertz>odpowiednio ograniczyć proces przez ograniczenia czasowe lub procesorowe.

To narzędzie działa, sprawdzając wiele razy na sekundę, czy odrodzony proces nie przekroczył swoich ustawionych granic. Oznacza to, że w rzeczywistości jest małe okno, w którym proces może potencjalnie nadmiernie subskrybować, zanim przekroczą limit czasu i zakończy proces.

Bardziej poprawne podejście prawdopodobnie dotyczyłoby grup, ale jest to o wiele bardziej zaangażowane w konfigurację, nawet jeśli użyjesz Dockera lub RunC, które między innymi oferują bardziej przyjazną dla użytkownika abstrakcję wokół grup.

kvz
źródło
Wygląda na to, że działa dla mnie teraz (ponownie?), Ale oto wersja Google cache: webcache.googleusercontent.com/…
kvz
Czy możemy wykorzystać limit czasu razem z zestawem zadań (musimy ograniczyć zarówno pamięć, jak i rdzenie)?
ransh
7
Należy zauważyć, że ta odpowiedź nie odnosi się do standardowego coreutilsnarzędzia linux o tej samej nazwie! Tak więc odpowiedź jest potencjalnie niebezpieczna, jeśli gdziekolwiek w twoim systemie, jakiś pakiet ma skrypt, który spodziewa timeoutsię być standardowym coreutilspakietem linux ! Nie wiem, czy to narzędzie jest pakowane do dystrybucji takich jak debian.
użytkownik1404316,
Czy -t <seconds>ograniczenie zabija proces po tak wielu sekundach?
xxx374562,
116

Innym sposobem ograniczenia tego jest użycie grup kontrolnych Linuksa. Jest to szczególnie przydatne, jeśli chcesz ograniczyć alokację pamięci fizycznej procesu (lub grupy procesów) w odróżnieniu od pamięci wirtualnej. Na przykład:

cgcreate -g memory:myGroup
echo 500M > /sys/fs/cgroup/memory/myGroup/memory.limit_in_bytes
echo 5G > /sys/fs/cgroup/memory/myGroup/memory.memsw.limit_in_bytes

utworzy grupę kontrolną o nazwie myGroup, ograniczy zestaw procesów uruchomionych w ramach mojej grupy do 500 MB pamięci fizycznej i do 5000 MB wymiany. Aby uruchomić proces w grupie kontrolnej:

cgexec -g memory:myGroup pdftoppm

Pamiętaj, że w nowoczesnej dystrybucji Ubuntu ten przykład wymaga zainstalowania cgroup-binpakietu i edycji, /etc/default/grubaby zmienić GRUB_CMDLINE_LINUX_DEFAULTna:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

a następnie uruchomienie sudo update-grubi ponowne uruchomienie w celu uruchomienia z nowymi parametrami rozruchowymi jądra.

użytkownik65369
źródło
3
firejailProgram pozwala także rozpocząć proces limitów pamięci (używając cgroups i nazw ograniczyć więcej niż tylko pamięć). W moich systemach nie musiałem zmieniać wiersza poleceń jądra, aby to działało!
Ned64
1
Czy potrzebujesz GRUB_CMDLINE_LINUX_DEFAULTmodyfikacji, aby ustawienie było trwałe? Znalazłem tutaj inny sposób, aby upierać się przy tym .
Stason
Przydatne byłoby zwrócenie uwagi w tej odpowiedzi, że w niektórych dystrybucjach (np. Ubuntu) sudo jest wymagane do cgcreate, a także późniejszych poleceń, chyba że bieżący użytkownik uzyska pozwolenie. Dzięki temu czytelnik nie musiałby szukać tych informacji gdzie indziej (np. Askubuntu.com/questions/345055 ). Zasugerowałem edycję tego efektu, ale został on odrzucony.
stewbasic
77

Jeśli twój proces nie odrodzi więcej dzieci, które zużywają najwięcej pamięci, możesz użyć setrlimitfunkcji. Bardziej powszechnym interfejsem użytkownika jest użycie ulimitpolecenia powłoki:

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

Ograniczy to tylko „wirtualną” pamięć twojego procesu, biorąc pod uwagę - i ograniczając - pamięć wywoływanego procesu współużytkuje z innymi procesami oraz pamięć mapowaną, ale niezastrzeżoną (na przykład dużą stertę Javy). Mimo to pamięć wirtualna jest najbliższym przybliżeniem procesów, które stają się naprawdę duże, przez co wspomniane błędy są nieistotne.

Jeśli twój program odradza dzieci i to one przydzielają pamięć, staje się bardziej złożony i powinieneś pisać skrypty pomocnicze, aby uruchamiać procesy pod kontrolą. Napisałem na moim blogu, dlaczego i jak .

P Shved
źródło
2
dlaczego jest setrlimitbardziej złożony dla większej liczby dzieci? man setrlimitmówi mi, że „Proces potomny utworzony za pomocą fork (2) dziedziczy limity zasobów nadrzędnych. Limity zasobów są zachowane w execve (2)”
akira,
6
Ponieważ jądro nie sumuje wielkości vm dla wszystkich procesów potomnych; gdyby tak się stało, odpowiedź i tak byłaby błędna. Limit dotyczy pojedynczego procesu i dotyczy wirtualnej przestrzeni adresowej, a nie wykorzystania pamięci. Zużycie pamięci jest trudniejsze do zmierzenia.
MarkR
1
jeśli dobrze rozumiem pytanie, to OP ile wynosi limit na podproces (dziecko) .. nie w całości.
akira
@MarkR, w każdym razie, wirtualna przestrzeń adresowa jest dobrym przybliżeniem używanej pamięci, szczególnie jeśli uruchamiasz program, który nie jest kontrolowany przez maszynę wirtualną (powiedzmy Java). Przynajmniej nie znam lepszych danych.
2
Chciałem tylko powiedzieć, dzięki - to ulimitpodejście pomogło mi firefox„s bug 622816 - Loading dużego obrazu można«zamrozić»Firefox, lub awarii systemu ; który przy rozruchu USB (z pamięci RAM) ma tendencję do zawieszania systemu operacyjnego, wymagając twardego restartu; teraz przynajmniej firefoxulega awarii, pozostawiając system operacyjny przy życiu ... Pozdrawiam!
sdaau
8

Korzystam z poniższego skryptu, który działa świetnie. Używa cgroups przez cgmanager. Aktualizacja: teraz używa poleceń z cgroup-tools. Nazwij ten skrypt limitmemi umieść go w $ PATH, a będziesz mógł go używać tak jak limitmem 100M bash. Ograniczy to wykorzystanie pamięci i wymiany. Aby ograniczyć tylko pamięć, usuń linię za pomocą memory.memsw.limit_in_bytes.

edycja: W domyślnych instalacjach Linuksa ogranicza to tylko użycie pamięci, a nie zamianę. Aby włączyć ograniczenie użycia wymiany, należy włączyć rozliczanie wymiany w systemie Linux. To zrobić poprzez ustawienie / dodanie swapaccount=1w /etc/default/grubtak to wygląda mniej więcej tak

GRUB_CMDLINE_LINUX="swapaccount=1"

Następnie uruchom sudo update-grubi uruchom ponownie.

Oświadczenie: Nie zdziwiłbym się, gdyby cgroup-toolsrównież przerwy w przyszłości. Prawidłowym rozwiązaniem byłoby użycie systemd API do zarządzania grupami, ale dla tego bankomatu nie ma narzędzi wiersza poleceń

#!/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
JanKanis
źródło
1
call to cgmanager_create_sync failed: invalid requestdla każdego procesu, z którym staram się biegać limitmem 100M processname. Jestem na Xubuntu 16.04 LTS i ten pakiet jest zainstalowany.
Aaron Franke,
Ups, pojawia się ten komunikat o błędzie: $ limitmem 400M rstudio limiting memory to 400M (cgroup limitmem_24575) for command rstudio Error org.freedesktop.DBus.Error.InvalidArgs: invalid request jakiś pomysł?
R Kiselev
@RKiselev cgmanager jest teraz przestarzały i nie jest nawet dostępny w Ubuntu 17.10. Systemowy interfejs API, którego używa, został w pewnym momencie zmieniony, więc prawdopodobnie jest to powód. Zaktualizowałem skrypt, aby używał poleceń cgroup-tools.
JanKanis
jeśli obliczenia dla percentwyniku są zerowe, exprkod stanu wynosi 1, a ten skrypt kończy się przedwcześnie. polecam zmianę linii na: percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))(ref: unix.stackexchange.com/questions/63166/… )
Willi Ballenthin
jak mogę skonfigurować cgroup, aby zabił mój proces, jeśli przekroczę limit?
d9ngle
7

Oprócz narzędzi z daemontools, sugerowanych przez Marka Johnsona, możesz również rozważyć, chpstktóre z nich można znaleźć runit. Samo Runit jest dołączone busybox, więc możesz już go zainstalować.

Strona człowiekchpst pokazuje opcję:

-m bajty ograniczają pamięć. Ogranicz segment danych, segment stosu, zablokowane strony fizyczne oraz sumę wszystkich segmentów na proces do bajtów.

Oz123
źródło
3

Korzystam z systemu Ubuntu 18.04.2 LTS, a skrypt JanKanis nie działa dla mnie tak, jak sugeruje. Bieganie limitmem 100M scriptogranicza 100 MB pamięci RAM przy nieograniczonej zamianie.

Uruchamianie limitmem 100M -s 100M scriptkończy się bezgłośnie, ponieważ cgget -g "memory:$cgname"nie ma określonego parametru memory.memsw.limit_in_bytes.

Więc wyłączyłem zamianę:

# 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`
d9ngle
źródło
@sourcejedi dodał to :)
d9ngle
2
Tak, zredagowałem swoją odpowiedź. Aby włączyć limity wymiany, musisz włączyć rozliczanie wymiany w swoim systemie. Jest to niewielki narzut związany ze środowiskiem uruchomieniowym, więc nie jest domyślnie włączony w Ubuntu. Zobacz moją edycję.
JanKanis
2

W każdej dystrybucji opartej na systemie możesz także używać grup pośrednio przez systemd-run. Na przykład w przypadku ograniczenia pdftoppmdo 500 MB pamięci RAM użyj:

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

Uwaga: poprosi Cię o podanie hasła, ale aplikacja zostanie uruchomiona jako użytkownik. Nie daj się zwieść myśleniu, że polecenie to potrzebuje sudo, ponieważ spowodowałoby to uruchomienie komendy pod rootem, co nie było twoim zamiarem.

Jeśli nie chcesz wpisywać hasła (w końcu, jako użytkownik posiadasz pamięć, dlaczego potrzebujesz hasła, aby ją ograniczyć) , możesz użyć --useropcji, jednak aby to zadziałało, musisz włączyć obsługę cgroupsv2, które to prawo teraz wymaga uruchomienia z systemd.unified_cgroup_hierarchyparametrem jądra .

Cześć aniele
źródło