Ograniczenie wykorzystania pamięci dla pojedynczego procesu Linuksa

Uruchamiam pdftoppm, Aby przekonwertować dostarczony przez użytkownika plik PDF na obraz 300DPI. Działa to świetnie, chyba że Użytkownik dostarczy plik PDF o bardzo dużym rozmiarze strony. pdftoppm przeznaczy wystarczającą ilość pamięci, aby pomieścić w pamięci obraz 300dpi o takim rozmiarze, który dla 100-calowej strony kwadratowej jest 100*300 * 100*300 * 4 bajtów na piksel = 3,5 GB. Złośliwy użytkownik może po prostu dać mi głupi duży PDF i spowodować różnego rodzaju problemy.

Więc to, co chciałbym zrobić, to umieścić jakiś twardy limit na użycie pamięci dla procesu potomnego, który mam zamiar uruchomić -- wystarczy, że proces umrze, jeśli spróbuje przeznaczyć więcej niż, powiedzmy, 500MB pamięci. Czy to możliwe?

Nie sądzę, aby ulimit mógł być użyty do tego, ale czy istnieje ekwiwalent jednego procesu?

Author: derobert, 2011-02-13

8 answers

Są pewne problemy z ulimitem. Oto przydatna lektura na ten temat: ograniczanie czasu i zużycia pamięci programu w Linuksie , które prowadzą do narzędzia timeout , które pozwala klatkować proces (i jego forki) według czasu lub zużycia pamięci.

Narzędzie timeout wymaga Perla 5+ i zamontowanego systemu plików /proc. Następnie skopiujesz narzędzie do np. /usr/local/bin w następujący sposób:

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 "uwięzić" swój proces poprzez zużycie pamięci, jak w twoim pytaniu tak:

timeout -m 500 pdftoppm Sample.pdf

Alternatywnie możesz użyć -t <seconds> i -x <hertz>, aby odpowiednio ograniczyć proces o czas lub ograniczenia procesora.

Sposób działania tego narzędzia polega na sprawdzaniu wiele razy na sekundę, czy proces wywołany nie przekroczył swoich ustalonych granic. Oznacza to, że w rzeczywistości istnieje małe okno, w którym proces może potencjalnie być oversubscribing przed powiadomieniami timeout i zabija proces.

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

 72
Author: kvz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-23 03:32:48

Innym sposobem ograniczenia tego jest użycie grup kontrolnych Linuksa. Jest to szczególnie przydatne, jeśli chcesz ograniczyć przydzielanie pamięci fizycznej procesowi (lub grupie procesów) wyraźnie 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, obejmującą zestaw procesów uruchomionych pod myGroup do 500 MB pamięci fizycznej z memory.limit_in_bytes oraz do 5000 MB pamięci fizycznej i wymiany razem z memory.memsw.limit_in_bytes. Więcej informacji na temat tych opcji można znaleźć tutaj: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-memory

Aby uruchomić proces pod grupą kontrolną:

cgexec -g memory:myGroup pdftoppm

Zauważ, że na nowoczesnej dystrybucji Ubuntu ten przykład wymaga zainstalowania pakietu cgroup-bin i edycji /etc/default/grub, aby zmienić GRUB_CMDLINE_LINUX_DEFAULT na:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

A następnie uruchom sudo update-grub i ponownie uruchom z nowymi parametrami rozruchowymi jądra.

 149
Author: user65369,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-01 02:46:46

Jeśli twój proces nie rodzi więcej dzieci, które zużywają najwięcej pamięci, możesz użyć setrlimit Funkcja. Bardziej powszechnym interfejsem użytkownika jest użycie polecenia ulimit 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ęć, którą wywoływany proces dzieli z innymi procesami oraz pamięć mapowaną, ale nie zarezerwowaną (na przykład duża sterta Javy). Jednak pamięć wirtualna jest najbliższym przybliżeniem dla procesy, które rosną naprawdę duże, czyniąc wspomniane błędy nieistotne.

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

 90
Author: P Shved,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-06 14:05:56

Na każdej dystrybucji opartej na systemd możesz również używać cgroups pośrednio poprzez systemd-run. Np. w przypadku ograniczenia pdftoppm do 500m pamięci RAM, użyj:

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

Uwaga: spowoduje to poproszenie o hasło, ale aplikacja zostanie uruchomiona jako użytkownik. Nie pozwól, aby to cię zwodziło do myślenia, że komenda potrzebuje sudo, ponieważ spowodowałoby to uruchomienie komendy pod rootem, co nie było twoim zamiarem.

Jeśli nie chcesz wpisywać hasła (rzeczywiście, po co Ci hasło do ograniczenia pamięci, którą już posiadasz), możesz użyć opcji --user, jednak aby to zadziałało, musisz włączyć obsługę cgroupsv2, która obecnie wymaga uruchomienia z systemd.unified_cgroup_hierarchy parametrem jądra .

 52
Author: Hi-Angel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-27 07:51:51

Używam poniższego skryptu, który działa świetnie. używa cgroups poprzez cgmanager. Aktualizacja: teraz używa poleceń z cgroup-tools. Nazwij ten skrypt limitmem i umieść go w swojej $PATH i możesz go używać jak limitmem 100M bash. Ograniczy to zarówno użycie pamięci, jak i wymiany. Aby ograniczyć tylko pamięć Usuń wiersz za pomocą memory.memsw.limit_in_bytes.

edit: w domyślnych instalacjach Linuksa ogranicza to tylko zużycie pamięci, a nie użycie wymiany. Aby włączyć ograniczenie wykorzystania swap, musisz włączyć swap księgowość w systemie Linux. Zrób to ustawiając / dodając swapaccount=1 w /etc/default/grub, aby wyglądało to mniej więcej tak:

GRUB_CMDLINE_LINUX="swapaccount=1"

Następnie uruchom sudo update-grub i uruchom ponownie.

Disclaimer: nie zdziwiłbym się, gdyby cgroup-tools również zepsuł się w przyszłości. Poprawnym rozwiązaniem byłoby użycie API systemd do zarządzania cgroup, ale nie ma narzędzi wiersza poleceń dla tego a.t. M.

#!/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
 11
Author: JanKanis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-02 13:16:21

Oprócz narzędzi z daemontools, sugerowanych przez Marka Johnsona, można również rozważyć chpst, który znajduje się w runit. Sam Runit jest dołączony do busybox, więc możesz go już zainstalować.

Strona man of chpst pokazuje opcję:

- m bajty ogranicz pamięć. Ogranicz segment danych, segment stosu, zablokowane strony fizyczne i sumę wszystkich segmentów na proces do bajtów bajtów każdy.

 7
Author: Oz123,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-08 14:25:42

Używam Ubuntu 18.04.2 LTS i skrypt JanKanis nie działa dla mnie tak jak sugeruje. Uruchamianie limitmem 100M scriptogranicza 100MB pamięci RAM z nieograniczonym swapem.

Uruchomienie limitmem 100M -s 100M script nie powiedzie się po cichu, ponieważ cgget -g "memory:$cgname" nie ma parametru o nazwie memory.memsw.limit_in_bytes.

Więc wyłączyłem swap:

# 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
Author: d9ngle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-19 17:51:25

Nie do końca odpowiedź na postawione pytanie, ale:

Czy możesz sprawdzić rozmiar pliku, aby zapobiec problemom przed próbą przetworzenia pliku pdf? To by usunęło "śmiesznie duży" problem.

Istnieją również programy, które będą przetwarzać plik pdf (istnieją programy Pythona, na przykład: http://theautomatic.net/2020/01/21/how-to-read-pdf-files-with-python / ), dzięki czemu można podzielić plik pdf na bardziej zarządzalne kawałki. Lub zrobić jedno i drugie: jeśli Rozmiar pliku jest rozsądny, przetworzyć; w przeciwnym razie (inaczej) podzielić go na tyle części, ile jest to wymagane, i przetworzyć je. Następnie można ponownie połączyć wyjścia. Może być konieczne nakładanie się części między sekcjami, aby zapobiec problemom "granicznym".

Ograniczenie dostępnej pamięci może wymusić awarię przetwarzania większych plików lub prowadzić do poważnych problemów z wymianą pamięci.

 0
Author: John Beck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-04 06:36:03