Tek bir Linux işlemi için bellek kullanımını sınırlayın


152

pdftoppmBir kullanıcı tarafından sağlanan PDF dosyasını 300DPI görüntüye dönüştürmek için çalışıyorum . Bu, kullanıcının çok büyük bir sayfa boyutuna sahip bir PDF sağlaması dışında harika bir işlemdir. pdftoppm100 inç karelik bir sayfa için 100 x 300 x 100 x 300 x 4 bayt olan piksel boyutunda 300 dpi boyutundaki bir resmi bellekte tutacak kadar bellek ayıracaktır. Kötü niyetli bir kullanıcı bana aptalca bir PDF verebilir ve her türlü soruna neden olabilir.

Bu yüzden yapmak istediğim, çalıştırmak üzere olduğum bir çocuk sürecinin bellek kullanımı konusunda bir tür zor sınırlama koymak, yani 500 MB bellekten daha fazlasını ayırmaya çalışırsa, işlemin ölmesi. Mümkün mü?

Ulimit'in bunun için kullanılabileceğini sanmıyorum, ancak bir işlem eşdeğeri var mı?


Belki docker?
Sridhar Sarnobat 12:16

Yanıtlar:


58

Ulimit ile ilgili bazı problemler var. İşte konuyla ilgili yararlı bir okuma: Linux'ta bir programın zamanını ve bellek tüketimini sınırlandırmak ; bu da zaman aşımı aracına yol açar , bu da bir işlemi (ve çatallarını) zamana veya bellek tüketimine göre kafeslemenizi sağlar.

Zaman aşımı aracı, Perl 5+ ve /procdosya sistemi monte edilmesini gerektirir . Bundan sonra aracı örneğin /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

Bundan sonra, işleminizi aşağıdaki gibi sorunuzda olduğu gibi bellek tüketimiyle 'kafesleyebilirsiniz'

timeout -m 500 pdftoppm Sample.pdf

Alternatif olarak , işlemi zamana veya CPU kısıtlamalarına göre sınırlayabilir -t <seconds>ve kullanabilirsiniz -x <hertz>.

Oluşturulan işlem belirlenen sınırları aştı değilse, bu aracın çalışması saniyede birden çok kez kontrol etmektir. Bu aslında, zaman aşımı fark etmeden ve süreci öldürmeden önce bir sürecin potansiyel olarak abonelikten çıkabileceği küçük bir pencere olduğu anlamına gelir .

Bu nedenle daha doğru bir yaklaşım, muhtemelen grup grupları içerecektir, ancak bu, gruplar arasında daha kullanıcı dostu bir soyutlama sunan Docker veya runC kullanıyor olsanız bile, kurulum için çok daha önemlidir.


Şu anda benim için çalışıyor gibi görünüyor (yine?) Ancak işte google önbellek sürümü: webcache.googleusercontent.com/…
kvz

Zaman aşımı görev seti ile birlikte kullanabilir miyiz (hem belleği hem de çekirdeği sınırlamamız gerekir)?
fidye

7
Bu cevabın coreutilsaynı isimdeki linux standart programına atıfta bulunmadığına dikkat edilmelidir ! Bu nedenle, sisteminizde herhangi bir yerde, bazı paketlerde timeoutlinux standart coreutilspaketi olmasını bekleyen bir komut dosyası varsa, cevap potansiyel olarak tehlikelidir ! Debian gibi dağıtımlar için paketlenmiş bu aracın farkında değilim.
user1404316

Does -t <seconds>kısıtlama birçok saniye sonra süreci öldürmek?
xxx374562

116

Bunu sınırlamanın başka bir yolu Linux'un kontrol gruplarını kullanmak. Bu özellikle, bir işlemin (veya işlem grubunun) fiziksel belleğinin sanal bellekten ayrı bir şekilde ayrılmasını sınırlamak istiyorsanız kullanışlıdır. Örneğin:

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

adında bir kontrol grubu oluşturacak myGroup, myGroup altında yürütülen işlem kümesini 500 MB'ye kadar fiziksel bellek ve 5000 MB'a kadar takasla kapatacak. Kontrol grubu altında bir işlem çalıştırmak için:

cgexec -g memory:myGroup pdftoppm

Modern bir Ubuntu dağıtımında bu örneğin cgroup-binpaketin kurulmasını ve /etc/default/grubdeğiştirilmesini GRUB_CMDLINE_LINUX_DEFAULTgerektirdiğini unutmayın:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

ve ardından sudo update-grubyeni çekirdek önyükleme parametreleriyle önyükleme yapmak için çalıştırma ve yeniden başlatma.


3
firejailProgram ayrıca (sadece bellek daha sınırlamak için cgroups ve ad alanlarını kullanarak) bellek limitlerine bir süreci başlatmak izin verir. Sistemlerimde bunun çalışması için çekirdek komut satırını değiştirmek zorunda değildim!
Ned64,

1
İhtiyacınız mı GRUB_CMDLINE_LINUX_DEFAULTayarı kalıcı kılmak için modifikasyon? Burada ısrar etmenin başka bir yolunu buldum .
stason


Bu cevabın bazı dağıtımlarda (örneğin Ubuntu) sudo'nun cgcreate için ve ayrıca mevcut kullanıcıya izin verilmedikçe daha sonraki komutlar için gerekli olduğunu not etmek yararlı olacaktır. Bu, okuyucunun bu bilgiyi başka bir yerde bulmasını önler (örneğin, askubuntu.com/questions/345055 ). Bu efekt için bir düzenleme önerdim, ancak reddedildi.
stewbasic

77

İşleminiz en fazla belleği tüketen daha fazla çocuğu doğurmazsa, setrlimitişlevi kullanabilirsiniz . Bunun için daha yaygın bir kullanıcı arayüzü ulimitkabuğun komutunu kullanıyor :

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

Bu, yalnızca işleminizin "sanal" hafızasını hesaba katar - ve sınırlandırır - işlemin başlatıldığı hafızayı diğer işlemlerle paylaşır ve eşleştirilen ancak ayrılmaz (örneğin, Java'nın büyük öbekleri). Yine de, sanal bellek gerçekten büyük boyutta büyüyen süreçler için en yakın yaklaşımdır ve bu da söz konusu hataları önemsiz kılar.

Programınız çocukları doğuruyorsa ve bellek ayıranlar ise, daha karmaşık hale gelir ve kontrolünüz altında işlemleri çalıştırmak için yardımcı komut dosyaları yazmanız gerekir. Bloguma neden ve nasıl yazdım .


2
neden setrlimitdaha fazla çocuk için daha karmaşık? man setrlimit"Çatal (2) ile yaratılan bir çocuk süreci ebeveynlerinin kaynak sınırlarını devraldığını söylüyor. Kaynak sınırları yürütme (2) 'de korunuyor"
akira

6
Çünkü çekirdek, tüm alt işlemler için vm boyutunu toplamıyor; eğer öyleyse, cevabı yine de yanlış bulacaktır. Sınır işlem başınadır ve sanal adres alanıdır, bellek kullanımı değildir. Bellek kullanımı ölçmek daha zordur.
MarkR

1
Eğer soruyu doğru anlarsam, OP, alt süreç başına düşen çocuk sayısını sınırlar.
akira

@MarkR, yine de, sanal adres alanı, özellikle sanal bir makine tarafından kontrol edilmeyen bir program çalıştırıyorsanız (örneğin, Java), kullanılan bellek için iyi bir yaklaşımdır. En azından daha iyi bir ölçüm bilmiyorum.

2
Sadece teşekkür etmek istedim - bu ulimityaklaşım bana yardımcı oldu firefoxs' böcek 622816 - firefox 'dondurmak' ya da sistemi çökmesine çıkarabileceğiniz büyük bir görüntü yüklemek ; bir USB önyüklemesinde (RAM’den) işletim sistemini dondurmaya meyillidir; Şimdi en azından firefoxişletim sistemini canlı bırakarak kendisini çökertiyor ... Şerefe!
sdaau

8

Aşağıdaki betiği kullanıyorum, bu harika çalışıyor. Üzerinden cgroups kullanır cgmanager. Güncelleme: şimdi gelen komutları kullanır cgroup-tools. Bu betiği adlandırın limitmemve $ PATH'inize koyun; istediğiniz gibi kullanabilirsiniz limitmem 100M bash. Bu hem belleği hem de takas kullanımını sınırlayacaktır. Sadece hafızayı sınırlandırmak için satırı kaldırın memory.memsw.limit_in_bytes.

edit: Varsayılan Linux kurulumlarında bu kullanım kullanımını değiştirmez, yalnızca hafıza kullanımını sınırlar. Takas kullanımının sınırlandırılmasını sağlamak için, Linux sisteminizde takas muhasebesini etkinleştirmeniz gerekir. Ekleme / ayarlayarak bunu swapaccount=1içinde /etc/default/grubböylece gibi görünür

GRUB_CMDLINE_LINUX="swapaccount=1"

Sonra koş sudo update-grubve yeniden başlat.

Feragatname: cgroup-toolsGelecekte de kırılırsa şaşırmam . Doğru çözüm, systemd api'sinin grup yönetimi için kullanılması olabilir, ancak bu ATM için komut satırı araçları yok.

#!/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

1
call to cgmanager_create_sync failed: invalid requestÇalıştığım her işlem için limitmem 100M processname. Xubuntu 16.04 LTS'deyim ve bu paket kurulu.
Aaron Franke

Ups, bu hata mesajını alıyorum: $ limitmem 400M rstudio limiting memory to 400M (cgroup limitmem_24575) for command rstudio Error org.freedesktop.DBus.Error.InvalidArgs: invalid request herhangi bir fikir?
R Kiselev

@RKiselev cgmanager şimdi kullanımdan kaldırıldı ve Ubuntu 17.10'da bile mevcut değil. Kullandığı systemd api bir noktada değiştirildi, bu yüzden muhtemelen sebep bu. Cgroup-tools komutlarını kullanmak için betiği güncelledim.
JanKanis

eğer hesaplamaların percentsıfır olması exprdurumunda , durum kodu 1'dir ve bu komut dosyası erken çıkar. çizgiyi şu şekilde değiştirmenizi öneririz: percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))(ref: unix.stackexchange.com/questions/63166/… )
Willi Ballenthin

Sınırı aşarsam, grubumu işlemimi öldürecek şekilde nasıl yapılandırabilirim?
d9ngle

7

daemontoolsMark Johnson tarafından önerilen araçlara ek olarak, chpsthangisinde bulunduğunu da düşünebilirsiniz runit. Runit'in kendisi paketlenmiştir busybox, bu yüzden zaten yüklemiş olabilirsiniz.

Adamı sayfachpst seçeneği gösterir:

-m bayt belleği sınırlar. Veri segmentini, yığın segmentini, kilitli fiziksel sayfaları ve işlem başına tüm segmentlerin toplamını bayt olarak sınırlandırın.


3

Ubuntu 18.04.2 kullanıyorum LTS ve JanKanis senaryosu önerdiği gibi benim için çalışmıyor. Çalışma limitmem 100M scriptRAM 100MB sınırladığını sınırsız takas.

Adında hiçbir parametre olmadığından, çalışma limitmem 100M -s 100M scriptsessizce başarısız olur .cgget -g "memory:$cgname"memory.memsw.limit_in_bytes

Bu yüzden takas devre dışı bıraktım:

# 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`

@sourcejedi ekledi :)
d9ngle

2
Doğru, cevabımı düzenledim. Takas limitlerini etkinleştirmek için sisteminizde takas muhasebesini etkinleştirmeniz gerekir. Küçük bir çalışma zamanı ek yükü vardır, bu nedenle varsayılan olarak Ubuntu'da etkin değildir. Düzenlemeye bak.
JanKanis

3

Herhangi bir systemd tabanlı dağıtımda da cgroups'u systemd-run üzerinden dolaylı olarak kullanabilirsiniz. Örneğin pdftoppm, 500M RAM ile sınırlama durumunuz için, şunları kullanın:

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

Not: Bu, sizden bir şifre isteyecektir ancak uygulama, kullanıcı olarak başlatılacaktır. Bunun, komutun gerekli olduğunu düşünmenize izin vermesine izin vermeyin sudo, çünkü bu komutun kökün altında çalışmasına neden olur;

Şifreyi girmek istemiyorsanız (sonuçta, hafızanıza sahip olduğunuz bir kullanıcı olarak, neden şifrelemek için bir şifreye ihtiyaç duyuyorsunuz)--user seçeneğini kullanabilirsiniz , ancak bunun için cgroupsv2 desteğinin etkin olması gerekir; Şimdi çekirdek parametresi ile önyükleme yapmak gerekiyorsystemd.unified_cgroup_hierarchy .


Teşekkür ederim, günümü yaptım
Geradlus_RU
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.