Batasi penggunaan memori untuk satu proses Linux

152

Saya sedang menjalankan pdftoppmuntuk mengubah PDF yang disediakan pengguna menjadi gambar 300DPI. Ini berfungsi dengan baik, kecuali jika pengguna menyediakan PDF dengan ukuran halaman yang sangat besar. pdftoppmakan mengalokasikan cukup memori untuk menampung gambar 300DPI dari ukuran itu dalam memori, yang untuk halaman persegi 100 inci adalah 100 * 300 * 100 * 300 * 4 byte per piksel = 3,5GB. Seorang pengguna jahat hanya bisa memberi saya PDF besar-konyol dan menyebabkan semua jenis masalah.

Jadi yang ingin saya lakukan adalah meletakkan beberapa batasan keras pada penggunaan memori untuk proses anak yang akan saya jalankan - proses saja mati jika mencoba mengalokasikan lebih dari, katakanlah, 500MB memori. Apakah itu mungkin?

Saya tidak berpikir ulimit dapat digunakan untuk ini, tetapi apakah ada satu proses yang setara?

Ben Dilts
sumber

Jawaban:

58

Ada beberapa masalah dengan ulimit. Berikut ini adalah bacaan yang bermanfaat tentang topik ini: Membatasi konsumsi waktu dan memori dari suatu program di Linux , yang mengarah ke alat batas waktu , yang memungkinkan Anda membatasi proses (dan forknya) berdasarkan konsumsi waktu atau memori.

Alat timeout membutuhkan Perl 5+ dan sistem /procfile terpasang. Setelah itu Anda menyalin alat ke eg /usr/local/binseperti:

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

Setelah itu, Anda dapat 'membatasi' proses Anda dengan konsumsi memori seperti pada pertanyaan Anda seperti:

timeout -m 500 pdftoppm Sample.pdf

Atau Anda dapat menggunakan -t <seconds>dan -x <hertz>membatasi masing-masing proses berdasarkan waktu atau kendala CPU.

Cara alat ini bekerja adalah dengan memeriksa beberapa kali per detik jika proses spawned belum kelebihan batas yang ditetapkan. Ini berarti sebenarnya ada jendela kecil di mana suatu proses berpotensi menjadi kelebihan langganan sebelum pemberitahuan batas waktu dan membunuh proses.

Maka, pendekatan yang lebih tepat kemungkinan akan melibatkan cgroup, tetapi itu jauh lebih terlibat untuk diatur, bahkan jika Anda akan menggunakan Docker atau runC, yang di antaranya, menawarkan abstraksi yang lebih ramah pengguna di sekitar cgroup.

kvz
sumber
Tampaknya berfungsi untuk saya sekarang (lagi?) Tapi inilah versi cache google: webcache.googleusercontent.com/…
kvz
Bisakah kita menggunakan batas waktu bersama dengan tasket (kita perlu membatasi memori dan inti)?
ransh
7
Perlu dicatat bahwa jawaban ini tidak mengacu pada coreutilsutilitas standar linux dengan nama yang sama! Dengan demikian, jawabannya berpotensi berbahaya jika di mana saja pada sistem Anda, beberapa paket memiliki skrip yang diharapkan timeoutmenjadi coreutilspaket standar linux ! Saya tidak mengetahui alat ini sedang dikemas untuk distribusi seperti debian.
user1404316
Apakah -t <seconds>kendala menghentikan proses setelah beberapa detik?
xxx374562
116

Cara lain untuk membatasi ini adalah dengan menggunakan grup kontrol Linux. Ini sangat berguna jika Anda ingin membatasi alokasi (atau sekelompok proses 'proses) memori fisik berbeda dari memori virtual. Sebagai contoh:

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

akan membuat grup kontrol bernama myGroup, membatasi rangkaian proses yang berjalan di bawah myGroup hingga 500 MB memori fisik dan hingga 5.000 MB swap. Untuk menjalankan proses di bawah grup kontrol:

cgexec -g memory:myGroup pdftoppm

Perhatikan bahwa pada distribusi Ubuntu modern contoh ini mengharuskan Anda menginstal cgroup-binpaket dan mengedit /etc/default/grubuntuk mengubah GRUB_CMDLINE_LINUX_DEFAULTke:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

dan kemudian jalankan sudo update-grubdan reboot untuk boot dengan parameter boot kernel baru.

pengguna65369
sumber
3
The firejailProgram juga akan membiarkan Anda mulai proses dengan batas memori (menggunakan cgroups dan ruang nama untuk membatasi lebih dari sekedar memori). Pada sistem saya, saya tidak perlu mengubah baris perintah kernel agar ini berfungsi!
Ned64
1
Apakah Anda memerlukan GRUB_CMDLINE_LINUX_DEFAULTmodifikasi untuk membuat pengaturan tetap ada? Saya menemukan cara lain untuk membuatnya bertahan di sini .
stason
Akan bermanfaat untuk mencatat dalam jawaban ini bahwa pada beberapa distribusi (misalnya Ubuntu) sudo diperlukan untuk cgcreate, dan juga perintah-perintah selanjutnya kecuali jika izin diberikan kepada pengguna saat ini. Ini akan menyelamatkan pembaca dari keharusan menemukan informasi ini di tempat lain (mis. Askubuntu.com/questions/345055 ). Saya menyarankan edit untuk efek ini tetapi ditolak.
stewbasic
77

Jika proses Anda tidak menghasilkan lebih banyak anak yang menghabiskan sebagian besar memori, Anda dapat menggunakan setrlimitfungsi. Antarmuka pengguna yang lebih umum untuk itu adalah menggunakan ulimitperintah shell:

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

Ini hanya akan membatasi memori "virtual" dari proses Anda, dengan mempertimbangkan — dan membatasi — memori yang digunakan oleh proses yang dibagikan dengan proses lain, dan memori dipetakan tetapi tidak dicadangkan (misalnya, tumpukan besar Java). Namun, memori virtual adalah perkiraan terdekat untuk proses yang tumbuh sangat besar, membuat kesalahan tersebut tidak signifikan.

Jika program Anda menghasilkan anak-anak, dan mereka yang mengalokasikan memori, itu menjadi lebih kompleks, dan Anda harus menulis skrip bantu untuk menjalankan proses di bawah kendali Anda. Saya menulis di blog saya, mengapa dan bagaimana .

P Shved
sumber
2
mengapa setrlimitlebih kompleks untuk lebih banyak anak? man setrlimitmemberi tahu saya bahwa "Proses anak yang dibuat melalui fork (2) mewarisi batasan sumber daya orang tuanya. Batas sumber daya dipertahankan di seluruh execve (2)"
akira
6
Karena kernel tidak menjumlahkan ukuran vm untuk semua proses anak; jika itu dilakukan, itu akan mendapatkan jawaban yang salah pula. Batasnya adalah per proses, dan ruang alamat virtual, bukan penggunaan memori. Penggunaan memori lebih sulit untuk diukur.
MarkR
1
jika saya mengerti pertanyaan dengan benar maka OP whats batas per subprocess (child) .. tidak secara total.
akira
@MarkR, bagaimanapun, ruang alamat virtual adalah perkiraan yang baik untuk memori yang digunakan, terutama jika Anda menjalankan program yang tidak dikendalikan oleh mesin virtual (katakanlah, Java). Setidaknya saya tidak tahu metrik yang lebih baik.
2
Hanya ingin mengucapkan terima kasih - ini ulimitpendekatan membantu saya dengan firefox's bug 622.816 - Memuat gambar besar dapat 'membekukan' firefox, atau sistem crash ; yang pada boot USB (dari RAM) cenderung membeku OS, membutuhkan restart keras; sekarang setidaknya firefoxcrash sendiri, meninggalkan OS hidup ... Cheers!
sdaau
8

Saya menggunakan skrip di bawah ini, yang sangat bagus. Ia menggunakan cgroups melalui cgmanager. Pembaruan: sekarang menggunakan perintah dari cgroup-tools. Beri nama skrip ini limitmemdan letakkan di $ PATH Anda dan Anda dapat menggunakannya seperti limitmem 100M bash. Ini akan membatasi penggunaan memori dan swap. Untuk membatasi hanya memori hapus baris dengan memory.memsw.limit_in_bytes.

sunting: Pada instalasi Linux standar ini hanya membatasi penggunaan memori, bukan menukar penggunaan. Untuk mengaktifkan pembatasan penggunaan swap, Anda harus mengaktifkan akuntansi swap pada sistem Linux Anda. Lakukan itu dengan menetapkan / menambahkan swapaccount=1di /etc/default/grubsehingga terlihat seperti

GRUB_CMDLINE_LINUX="swapaccount=1"

Kemudian jalankan sudo update-grubdan reboot.

Penafian: Saya tidak akan terkejut jika cgroup-toolsjuga rusak di masa depan. Solusi yang benar adalah dengan menggunakan api systemd untuk manajemen cgroup tetapi tidak ada alat baris perintah untuk atm itu

#!/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
sumber
1
call to cgmanager_create_sync failed: invalid requestuntuk setiap proses yang saya coba jalankan limitmem 100M processname. Saya menggunakan Xubuntu 16.04 LTS dan paket itu diinstal.
Aaron Franke
Ups, saya mendapatkan pesan kesalahan ini: $ limitmem 400M rstudio limiting memory to 400M (cgroup limitmem_24575) for command rstudio Error org.freedesktop.DBus.Error.InvalidArgs: invalid request ada ide?
R Kiselev
@RKiselev cgmanager sudah usang sekarang, dan bahkan tidak tersedia di Ubuntu 17.10. Systemd api yang digunakannya diubah di beberapa titik, jadi itu mungkin alasannya. Saya telah memperbarui skrip untuk menggunakan perintah cgroup-tools.
JanKanis
jika perhitungan untuk percenthasil dalam nol, exprkode status adalah 1, dan skrip ini keluar sebelum waktunya. merekomendasikan mengubah jalur ke: percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))(ref: unix.stackexchange.com/questions/63166/… )
Willi Ballenthin
bagaimana cara mengkonfigurasi cgroup untuk mematikan proses saya jika saya melampaui batas?
d9ngle
7

Selain alat dari daemontools, disarankan oleh Mark Johnson, Anda juga dapat mempertimbangkan chpstyang ditemukan di runit. Runit sendiri dibundel busybox, jadi Anda mungkin sudah menginstalnya.

The halaman manual darichpst menunjukkan pilihan:

-m byte batas memori. Batasi segmen data, segmen stack, halaman fisik terkunci, dan total semua segmen per proses untuk masing-masing byte byte.

Oz123
sumber
3

Saya menjalankan Ubuntu 18.04.2 LTS dan skrip JanKanis tidak berfungsi dengan baik seperti yang disarankannya. Menjalankan limitmem 100M scriptmembatasi 100MB RAM dengan swap tanpa batas .

Menjalankan limitmem 100M -s 100M scriptgagal secara diam-diam karena cgget -g "memory:$cgname"tidak ada parameter bernama memory.memsw.limit_in_bytes.

Jadi saya menonaktifkan 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`
d9ngle
sumber
@sourcejedi menambahkannya :)
d9ngle
2
Benar, saya mengedit jawaban saya. Untuk mengaktifkan batas swap, Anda perlu mengaktifkan akuntansi swap di sistem Anda. Ada overhead runtime kecil untuk itu sehingga tidak diaktifkan secara default di Ubuntu. Lihat hasil edit saya.
JanKanis
3

Pada setiap distro berbasis systemd Anda juga dapat menggunakan cgroups secara tidak langsung melalui systemd-run. Misalnya untuk kasus Anda membatasi pdftoppm500M RAM, gunakan:

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

Catatan: ini akan meminta Anda untuk kata sandi tetapi aplikasi akan diluncurkan sebagai pengguna Anda. Jangan biarkan ini menipu Anda agar berpikir bahwa perintah itu perlu sudo, karena itu akan menyebabkan perintah berjalan di bawah root, yang hampir tidak merupakan niat Anda.

Jika Anda tidak ingin memasukkan kata sandi (setelah semua, sebagai pengguna Anda memiliki ingatan Anda, mengapa Anda memerlukan kata sandi untuk membatasinya) , Anda dapat menggunakan --useropsi, namun untuk ini untuk bekerja Anda akan memerlukan dukungan cgroupsv2 diaktifkan, yang benar sekarang perlu boot dengan systemd.unified_cgroup_hierarchyparameter kernel .

Hai Malaikat
sumber
Terima kasih, buat hariku
Geradlus_RU