Beschränken Sie die Speichernutzung für einen einzelnen Linux-Prozess

152

Ich pdftoppmmöchte ein vom Benutzer bereitgestelltes PDF in ein 300DPI-Bild konvertieren. Dies funktioniert hervorragend, es sei denn, der Benutzer stellt eine PDF-Datei mit einer sehr großen Seitengröße bereit. pdftoppmreserviert genügend Speicher, um ein 300DPI-Bild dieser Größe im Speicher zu speichern, was für eine 100-Zoll-Quadratseite 100 * 300 * 100 * 300 * 4 Bytes pro Pixel = 3,5 GB entspricht. Ein böswilliger Benutzer könnte mir einfach ein albern großes PDF geben und alle möglichen Probleme verursachen.

Was ich also tun möchte, ist, die Speichernutzung für einen untergeordneten Prozess, den ich ausführen möchte, auf eine harte Grenze zu beschränken. Lassen Sie den Prozess einfach abstürzen, wenn er versucht, mehr als beispielsweise 500 MB Speicher zuzuweisen. Ist das möglich?

Ich glaube nicht, dass ulimit dafür verwendet werden kann, aber gibt es ein Ein-Prozess-Äquivalent?

Ben Dilts
quelle

Antworten:

58

Es gibt einige Probleme mit ulimit. Hier finden Sie eine nützliche Lektüre zum Thema: Begrenzen der Zeit und des Speicherverbrauchs eines Programms unter Linux. Dies führt zum Timeout- Tool, mit dem Sie einen Prozess (und seine Forks) nach Zeit und Speicherverbrauch einschränken können.

Das Timeout-Tool erfordert Perl 5+ und das /procbereitgestellte Dateisystem. Danach kopieren Sie das Tool zB /usr/local/binso:

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

Danach können Sie Ihren Prozess anhand des Speicherverbrauchs wie in Ihrer Frage wie folgt "einschränken":

timeout -m 500 pdftoppm Sample.pdf

Alternativ könnten Sie -t <seconds>und , -x <hertz>um jeweils den Prozess durch die Zeit oder CPU Einschränkungen zu begrenzen.

Die Funktionsweise dieses Tools besteht darin, mehrmals pro Sekunde zu überprüfen, ob der erzeugte Prozess seine festgelegten Grenzen nicht überzeichnet hat. Dies bedeutet, dass es tatsächlich ein kleines Fenster gibt, in dem ein Prozess möglicherweise überzeichnet sein kann, bevor Timeout den Prozess bemerkt und abbricht.

Ein korrekterer Ansatz würde daher wahrscheinlich cgroups beinhalten, aber das Einrichten ist viel aufwendiger, selbst wenn Sie Docker oder runC verwenden, die unter anderem eine benutzerfreundlichere Abstraktion um cgroups bieten.

kvz
quelle
Scheint jetzt (wieder?) Für mich zu arbeiten, aber hier ist die Google Cache-Version: webcache.googleusercontent.com/…
kvz
Können wir Timeout zusammen mit Taskset verwenden (wir müssen sowohl Speicher als auch Kerne begrenzen)?
Ransh
7
Es ist zu beachten, dass sich diese Antwort nicht auf das gleichnamige Linux-Standarddienstprogramm coreutilsbezieht! Daher ist die Antwort möglicherweise gefährlich, wenn in einem Paket irgendwo auf Ihrem System ein Skript vorhanden ist, timeoutdas als Linux-Standardpaket erwartet wird coreutils! Mir ist nicht bekannt, dass dieses Tool für Distributionen wie Debian gepackt ist.
user1404316
Bricht die -t <seconds>Einschränkung den Prozess nach so vielen Sekunden ab?
xxx374562
116

Eine andere Möglichkeit, dies einzuschränken, ist die Verwendung von Linux-Kontrollgruppen. Dies ist besonders nützlich, wenn Sie die Zuordnung des physischen Speichers eines Prozesses (oder einer Gruppe von Prozessen) vom virtuellen Speicher unterscheiden möchten. Zum Beispiel:

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

erstellt eine Kontrollgruppe mit dem Namen myGroup, begrenzt die unter myGroup ausgeführten Prozesse auf bis zu 500 MB physischen Speicher und bis zu 5000 MB Swap. So führen Sie einen Prozess unter der Kontrollgruppe aus:

cgexec -g memory:myGroup pdftoppm

Beachten Sie, dass in einer modernen Ubuntu-Distribution in diesem Beispiel das cgroup-binPaket installiert und bearbeitet werden muss /etc/default/grub, um GRUB_CMDLINE_LINUX_DEFAULTzu Folgendem zu wechseln :

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

und dann sudo update-grubausführen und neu starten, um mit den neuen Kernel-Boot-Parametern zu booten.

user65369
quelle
3
Mit dem firejailProgramm können Sie auch einen Prozess mit Speicherbeschränkungen starten (indem Sie cgroups und Namespaces verwenden, um mehr als nur Speicher zu begrenzen). Auf meinen Systemen musste ich die Kernel-Befehlszeile nicht ändern, damit dies funktioniert!
Ned64
1
Benötigen Sie die GRUB_CMDLINE_LINUX_DEFAULTÄnderung, um die Einstellung dauerhaft zu machen? Ich habe einen anderen Weg gefunden, um es hier hartnäckig zu machen .
Stason
In dieser Antwort sollte beachtet werden, dass bei einigen Distributionen (z. B. Ubuntu) sudo für cgcreate und auch für die späteren Befehle erforderlich ist, sofern dem aktuellen Benutzer keine Berechtigung erteilt wird. Dies erspart dem Leser, diese Informationen an anderer Stelle zu suchen (z . B. askubuntu.com/questions/345055 ). Ich schlug eine entsprechende Änderung vor, diese wurde jedoch abgelehnt.
Stewbasic
77

Wenn Ihr Prozess nicht mehr Kinder hervorbringt, die den meisten Speicher verbrauchen, können Sie die setrlimitFunktion verwenden. Eine häufigere Benutzeroberfläche dafür ist der ulimitBefehl der Shell:

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

Dies begrenzt nur den "virtuellen" Speicher Ihres Prozesses, wobei berücksichtigt wird - und begrenzt wird -, dass der Speicher, den der aufgerufene Prozess verwendet, mit anderen Prozessen geteilt wird und der Speicher zugeordnet, aber nicht reserviert ist (zum Beispiel der große Heap von Java). Dennoch ist virtueller Speicher die beste Annäherung für Prozesse, die sehr groß werden, wodurch die genannten Fehler unerheblich werden.

Wenn Ihr Programm untergeordnete Elemente erzeugt und sie den Speicher zuweisen, wird es komplexer, und Sie sollten Hilfsskripts schreiben, um Prozesse unter Ihrer Kontrolle auszuführen. Ich habe in meinem Blog geschrieben, warum und wie .

P Shved
quelle
2
Warum ist setrlimites für mehr Kinder komplexer? man setrlimit"Ein über fork (2) erstellter
untergeordneter
6
Weil der Kernel die VM-Größe nicht für alle untergeordneten Prozesse summiert; wenn ja, würde die Antwort trotzdem falsch sein. Das Limit gilt pro Prozess und bezieht sich auf den virtuellen Adressraum, nicht auf die Speichernutzung. Die Speichernutzung ist schwerer zu messen.
MarkR
1
Wenn ich die Frage richtig verstehe, dann ist OP das Limit pro Unterprozess (Kind) .. nicht insgesamt.
Akira
@MarkR Der virtuelle Adressraum ist ohnehin eine gute Annäherung an den verwendeten Speicher, insbesondere wenn Sie ein Programm ausführen, das nicht von einer virtuellen Maschine gesteuert wird (z. B. Java). Zumindest kenne ich keine bessere Metrik.
2
ulimitIch wollte nur danke sagen - dieser Ansatz hat mir bei firefoxdem Fehler 622816 geholfen - Das Laden eines großen Bildes kann Firefox "einfrieren" oder das System zum Absturz bringen . was bei einem USB-Boot (vom RAM) dazu neigt, das Betriebssystem einzufrieren, was einen harten Neustart erfordert; jetzt firefoxstürzt zumindest selbst ab und lässt das Betriebssystem am Leben ... Prost!
Sdaau
8

Ich verwende das folgende Skript, das funktioniert großartig. Es werden cgroups through verwendet cgmanager. Update: Es werden nun die Befehle von verwendet cgroup-tools. Benennen Sie dieses Skript limitmemund fügen Sie es in Ihr $ PATH ein, und Sie können es wie folgt verwenden limitmem 100M bash. Dies schränkt sowohl die Speicher- als auch die Auslagerungsnutzung ein. Um nur den Speicher zu begrenzen, entfernen Sie die Zeile mit memory.memsw.limit_in_bytes.

Bearbeiten: Bei Standard-Linux-Installationen wird dadurch nur die Speichernutzung begrenzt, nicht die Auslagerungsnutzung. Um die Beschränkung der Swap-Nutzung zu aktivieren, müssen Sie die Swap-Abrechnung auf Ihrem Linux-System aktivieren. Tun Sie das , indem Sie / Hinzufügen swapaccount=1in /etc/default/grubso sieht es so etwas wie

GRUB_CMDLINE_LINUX="swapaccount=1"

Dann starten sudo update-grubund neu starten .

Haftungsausschluss: Es würde mich nicht wundern, wenn cgroup-toolsauch in Zukunft Pausen entstehen. Die richtige Lösung wäre, die systemd-APIs für die Gruppenverwaltung zu verwenden, aber es gibt keine Befehlszeilentools für diese atm

#!/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
quelle
1
call to cgmanager_create_sync failed: invalid requestfür jeden Prozess, mit dem ich versuche zu laufen limitmem 100M processname. Ich bin auf Xubuntu 16.04 LTS und das Paket ist installiert.
Aaron Franke
Ups, ich bekomme folgende Fehlermeldung: $ limitmem 400M rstudio limiting memory to 400M (cgroup limitmem_24575) for command rstudio Error org.freedesktop.DBus.Error.InvalidArgs: invalid request Irgendeine Idee?
R Kiselev
@RKiselev cgmanager ist jetzt veraltet und nicht mehr in Ubuntu 17.10 verfügbar. Die verwendete System-API wurde irgendwann geändert, das ist wahrscheinlich der Grund. Ich habe das Skript aktualisiert, um cgroup-tools-Befehle zu verwenden.
JanKanis
Wenn die Berechnung für percentNull ergibt, exprlautet der Statuscode 1, und dieses Skript wird vorzeitig beendet. empfehle die Zeile zu ändern: percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))(ref: unix.stackexchange.com/questions/63166/… )
Willi Ballenthin
Wie kann ich cgroup so konfigurieren, dass mein Prozess beendet wird, wenn ich das Limit überschreite?
1.
7

Zusätzlich zu den daemontoolsvon Mark Johnson vorgeschlagenen Tools von können Sie auch berücksichtigen, chpstwelche in enthalten sind runit. Runit selbst ist im Lieferumfang enthalten busybox, sodass es möglicherweise bereits installiert ist.

Die Manpage vonchpst zeigt die Option:

-m Bytes begrenzen den Speicher. Begrenzen Sie das Datensegment, das Stapelsegment, die gesperrten physischen Seiten und die Summe aller Segmente pro Prozess auf jeweils Byte Byte.

Oz123
quelle
3

Ich verwende Ubuntu 18.04.2 LTS und das JanKanis-Skript funktioniert nicht ganz so, wie er es vorschlägt. Laufen limitmem 100M scriptist die Begrenzung von 100 MB RAM mit unbegrenztem Swap.

Die Ausführung limitmem 100M -s 100M scriptschlägt unbemerkt fehl, da cgget -g "memory:$cgname"kein Parameter angegeben wurde memory.memsw.limit_in_bytes.

Also habe ich Swap deaktiviert:

# 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
quelle
@sourcejedi hat es hinzugefügt :)
d9ngle
2
Richtig, ich habe meine Antwort bearbeitet. Um Swap-Limits zu aktivieren, müssen Sie die Swap-Abrechnung auf Ihrem System aktivieren. Das hat einen geringen Zeitaufwand, so dass es unter Ubuntu nicht standardmäßig aktiviert ist. Siehe meine Bearbeitung.
JanKanis
3

Auf jeder systemd-basierten Distribution können Sie cgroups auch indirekt über systemd-run verwenden. pdftoppmVerwenden Sie beispielsweise für den Fall einer Beschränkung auf 500 MB RAM Folgendes:

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

Hinweis: Hier werden Sie nach einem Passwort gefragt, aber die App wird als Ihr Benutzer gestartet. Lassen Sie sich nicht täuschen, dass der Befehl benötigt wird sudo, da der Befehl dann unter root ausgeführt wird, was kaum Ihre Absicht war.

Wenn Sie das Passwort nicht eingeben möchten (schließlich benötigen Sie als Benutzer, dessen Speicher Sie besitzen, ein Passwort, um es--user einzuschränken ) , können Sie die Option verwenden. Damit dies funktioniert, muss jedoch die Unterstützung von cgroupsv2 aktiviert sein Jetzt muss mit dem systemd.unified_cgroup_hierarchyKernel-Parameter gebootet werden .

Hallo Engel
quelle
Danke, habe meinen Tag gemacht
Geradlus_RU