จำกัด การใช้หน่วยความจำสำหรับกระบวนการ Linux เดียว


152

ฉันกำลังpdftoppmแปลงไฟล์ PDF ที่ผู้ใช้ให้เป็นภาพ 300DPI วิธีนี้ใช้งานได้ดียกเว้นในกรณีที่ผู้ใช้มีขนาดหน้ากระดาษที่ใหญ่มาก pdftoppmจะจัดสรรหน่วยความจำให้เพียงพอที่จะเก็บภาพขนาด 300DPI ในหน่วยความจำซึ่งสำหรับหน้าสี่เหลี่ยม 100 นิ้วคือ 100 * 300 * 100 * 300 * 4 ไบต์ต่อพิกเซล = 3.5GB ผู้ใช้ที่เป็นอันตรายสามารถให้ PDF ที่มีขนาดใหญ่และทำให้เกิดปัญหาได้ทุกประเภท

ดังนั้นสิ่งที่ฉันต้องทำคือการ จำกัด การใช้หน่วยความจำอย่างหนักสำหรับกระบวนการลูกที่ฉันจะเรียกใช้ - เพียงแค่มีกระบวนการตายหากพยายามจัดสรรหน่วยความจำมากกว่า 500MB เป็นไปได้ไหม

ฉันไม่คิดว่า ulimit สามารถใช้สำหรับสิ่งนี้ แต่มีกระบวนการที่เทียบเท่าหรือไม่


อาจจะdocker?
Sridhar Sarnobat

คำตอบ:


58

มีปัญหาบางอย่างกับ ulimit ต่อไปนี้เป็นประโยชน์ในการอ่านหัวข้อ: การจำกัด เวลาและการใช้หน่วยความจำของโปรแกรมใน Linuxซึ่งนำไปสู่เครื่องมือการหมดเวลาซึ่งช่วยให้คุณสามารถจัดกระบวนการ (และส้อม) ตามเวลาหรือการใช้หน่วยความจำ

เครื่องมือหมดเวลาต้องใช้ Perl 5+ และ/procติดตั้งระบบไฟล์ หลังจากนั้นคุณคัดลอกเครื่องมือเพื่อเช่น/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

หลังจากนั้นคุณสามารถ 'จัดการ' กระบวนการของคุณโดยการใช้หน่วยความจำเช่นเดียวกับในคำถามของคุณ:

timeout -m 500 pdftoppm Sample.pdf

หรือคุณสามารถใช้-t <seconds>และ-x <hertz>เพื่อ จำกัด กระบวนการตามเวลาหรือข้อ จำกัด ของ CPU

วิธีการทำงานของเครื่องมือนี้คือการตรวจสอบหลายครั้งต่อวินาทีหากกระบวนการที่เกิดมาไม่ได้มีการสมัครสมาชิกเกินขอบเขตที่ตั้งไว้ ซึ่งหมายความว่ามีหน้าต่างเล็ก ๆ ที่กระบวนการอาจมีการบอกรับสมาชิกเกินกำหนดก่อนที่จะหมดเวลาประกาศและฆ่ากระบวนการ

ดังนั้นวิธีการที่ถูกต้องน่าจะเกี่ยวข้องกับกลุ่ม cg มากขึ้น แต่นั่นก็เกี่ยวข้องกับการตั้งค่ามากขึ้นแม้ว่าคุณจะใช้ Docker หรือ runC ซึ่งเป็นสิ่งที่ให้ผู้ใช้ที่เป็นมิตรรอบ cgroups


ดูเหมือนว่าจะใช้งานได้สำหรับฉันตอนนี้ (อีกแล้วใช่ไหม) แต่นี่เป็นรุ่นแคชของ Google: webcache.googleusercontent.com/…
kvz

เราสามารถใช้การหมดเวลาร่วมกับชุดงาน (เราจำเป็นต้อง จำกัด ทั้งหน่วยความจำและแกน)?
ransh

7
ควรสังเกตว่าคำตอบนี้ไม่ได้อ้างถึงcoreutilsอรรถประโยชน์มาตรฐานของlinux ที่มีชื่อเดียวกัน! ดังนั้นคำตอบอาจเป็นอันตรายได้หากทุกที่ในระบบของคุณแพคเกจบางตัวมีสคริปต์ที่คาดว่าtimeoutจะเป็นcoreutilsแพ็คเกจมาตรฐานของ linux ! ฉันไม่ทราบว่าเครื่องมือนี้ถูกจัดทำแพ็กเกจสำหรับการแจกแจงเช่นเดเบียน
user1404316

ไม่-t <seconds>จำกัด ฆ่ากระบวนการหลังจากนั้นหลายวินาทีหรือไม่
xxx374562

116

อีกวิธีในการ จำกัด สิ่งนี้คือการใช้กลุ่มควบคุมของ Linux สิ่งนี้มีประโยชน์อย่างยิ่งหากคุณต้องการ จำกัด การจัดสรร (หรือกลุ่มกระบวนการ) ของหน่วยความจำฟิสิคัลอย่างชัดเจนจากหน่วยความจำเสมือน ตัวอย่างเช่น:

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

จะสร้างกลุ่มควบคุมที่ชื่อmyGroupตั้งค่าชุดของกระบวนการที่เรียกใช้ภายใต้ myGroup สูงสุด 500 MB ของหน่วยความจำกายภาพและสูงสุด 5000 MB ของการแลกเปลี่ยน ในการรันโปรเซสภายใต้กลุ่มควบคุม:

cgexec -g memory:myGroup pdftoppm

โปรดทราบว่าในการแจกจ่าย Ubuntu ที่ทันสมัยตัวอย่างนี้ต้องติดตั้งcgroup-binแพคเกจและการแก้ไข/etc/default/grubเพื่อเปลี่ยนGRUB_CMDLINE_LINUX_DEFAULTเป็น:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

จากนั้นเรียกใช้sudo update-grubและรีบูตเครื่องเพื่อบูตด้วยพารามิเตอร์การบูตเคอร์เนลใหม่


3
firejailโปรแกรมนี้ยังจะช่วยให้คุณเริ่มต้นกระบวนการที่มีขีด จำกัด ของหน่วยความจำ (ใช้ cgroups และ namespaces ที่จะ จำกัด มากขึ้นกว่าหน่วยความจำเพียง) ในระบบของฉันฉันไม่จำเป็นต้องเปลี่ยนบรรทัดคำสั่งเคอร์เนลเพื่อให้ทำงานได้!
Ned64

1
คุณต้องการการGRUB_CMDLINE_LINUX_DEFAULTแก้ไขเพื่อให้การตั้งค่าคงอยู่หรือไม่? ผมพบว่าวิธีที่จะทำให้มันถาวรอีกที่นี่
stason


มันจะมีประโยชน์ที่จะต้องทราบในคำตอบนี้ว่าในบางดิสทริบิวชั่น (เช่น Ubuntu) จำเป็นต้องใช้ cudo สำหรับ cgcreate และคำสั่งในภายหลังยกเว้นว่าจะได้รับอนุญาตจากผู้ใช้ปัจจุบัน การทำเช่นนี้จะช่วยให้ผู้อ่านไม่ต้องค้นหาข้อมูลนี้จากที่อื่น (เช่นaskubuntu.com/questions/345055 ) ฉันแนะนำให้แก้ไขผลกระทบนี้ แต่ถูกปฏิเสธ
stewbasic

77

หากกระบวนการของคุณไม่ได้วางไข่เด็ก ๆ ที่ใช้หน่วยความจำมากที่สุดคุณอาจใช้setrlimitฟังก์ชั่น ส่วนต่อประสานผู้ใช้ทั่วไปที่ใช้ulimitคำสั่งของเชลล์:

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

สิ่งนี้จะ จำกัด หน่วยความจำ "เสมือน" ของกระบวนการของคุณโดยคำนึงถึง - และ จำกัด - หน่วยความจำที่กระบวนการถูกเรียกใช้ร่วมกับกระบวนการอื่นและหน่วยความจำที่แมป แต่ไม่สงวนไว้ (เช่นฮีปขนาดใหญ่ของ Java) ถึงกระนั้นหน่วยความจำเสมือนก็เป็นกระบวนการที่ใกล้เคียงกับกระบวนการที่มีขนาดใหญ่มากโดยประมาณซึ่งทำให้ข้อผิดพลาดดังกล่าวไม่สำคัญ

หากโปรแกรมของคุณวางไข่เด็ก ๆ และเป็นโปรแกรมที่จัดสรรหน่วยความจำมันจะซับซ้อนมากขึ้นและคุณควรเขียนสคริปต์เสริมเพื่อเรียกใช้กระบวนการภายใต้การควบคุมของคุณ ผมเขียนในบล็อกของฉันทำไมและวิธีการ


2
ทำไมจึงsetrlimitซับซ้อนกว่าสำหรับเด็กมากขึ้น man setrlimitบอกฉันว่า "กระบวนการเด็กที่สร้างขึ้นผ่านทาง fork (2) สืบทอดข้อ จำกัด ทรัพยากรของผู้ปกครองข้อ จำกัด ของทรัพยากรจะถูกเก็บไว้ใน execve (2)"
akira

6
เนื่องจากเคอร์เนลไม่ได้รวมขนาด vm สำหรับกระบวนการลูกทั้งหมด ถ้ามันทำมันจะได้รับคำตอบที่ผิดอยู่ดี ขีด จำกัด ต่อกระบวนการและเป็นพื้นที่ที่อยู่เสมือนไม่ใช่การใช้หน่วยความจำ การใช้หน่วยความจำนั้นยากที่จะวัด
MarkR

1
ถ้าฉันเข้าใจคำถามอย่างถูกต้องแล้ว OP คืออะไรขีด จำกัด สำหรับแต่ละกระบวนการย่อย (ลูก) .. ไม่รวม
akira

@MarkR อย่างไรก็ตามพื้นที่ที่อยู่เสมือนเป็นการประมาณที่ดีสำหรับหน่วยความจำที่ใช้โดยเฉพาะถ้าคุณเรียกใช้โปรแกรมที่ไม่ได้ควบคุมโดยเครื่องเสมือน (เช่น Java) อย่างน้อยฉันก็ไม่รู้ว่ามีอะไรดีกว่า

2
เพียงแค่อยากจะบอกว่าขอบคุณ - นี้ulimitวิธีการช่วยฉันด้วยfirefox's ข้อผิดพลาด 622,816 - โหลดภาพขนาดใหญ่สามารถ 'หยุด' Firefox หรือผิดพลาดของระบบ ; สิ่งที่อยู่ในการบูท USB (จาก RAM) มีแนวโน้มที่จะหยุดระบบปฏิบัติการ ตอนนี้อย่างน้อยก็firefoxล่มเองปล่อยให้ OS มีชีวิตอยู่ ...
sdaau

8

ฉันใช้สคริปต์ด้านล่างซึ่งใช้งานได้ดี มันใช้ cgroups cgmanagerผ่าน UPDATE: cgroup-toolsตอนนี้ใช้คำสั่งจาก ตั้งชื่อสคริปต์นี้limitmemและใส่ใน $ PATH ของคุณและคุณสามารถใช้มันlimitmem 100M bashได้ สิ่งนี้จะ จำกัด ทั้งการใช้หน่วยความจำและการสลับ เพื่อ จำกัด memory.memsw.limit_in_bytesหน่วยความจำเพียงแค่ลบบรรทัดที่มี

แก้ไข:ในการติดตั้ง Linux เริ่มต้นจะ จำกัด การใช้หน่วยความจำเท่านั้นไม่ใช้การสลับ ในการเปิดใช้งานการ จำกัด การใช้งาน swap คุณต้องเปิดใช้งานการสลับบัญชีบนระบบ Linux ของคุณ ทำได้โดยการตั้งค่า / เพิ่มswapaccount=1ใน/etc/default/grubจึงมีลักษณะคล้าย

GRUB_CMDLINE_LINUX="swapaccount=1"

จากนั้นเรียกใช้sudo update-grubและรีบูต

ข้อจำกัดความรับผิดชอบ: ฉันจะไม่แปลกใจหากcgroup-toolsยังมีการแตกหักในอนาคต ทางออกที่ถูกต้องคือใช้ systemd api สำหรับการจัดการกลุ่ม cgroup แต่ไม่มีเครื่องมือบรรทัดคำสั่งสำหรับ 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

1
call to cgmanager_create_sync failed: invalid requestlimitmem 100M processnameสำหรับทุกขั้นตอนฉันพยายามที่จะทำงานด้วย ฉันใช้งาน Xubuntu 16.04 LTS และมีการติดตั้งแพ็คเกจ
Aaron Franke

อัพฉันได้รับข้อความแสดงข้อผิดพลาดนี้: $ limitmem 400M rstudio limiting memory to 400M (cgroup limitmem_24575) for command rstudio Error org.freedesktop.DBus.Error.InvalidArgs: invalid request ความคิดใด ๆ
R Kiselev

@RKiselev cgmanager เลิกใช้แล้วในตอนนี้และยังไม่มีให้ใน Ubuntu 17.10 systemd api ที่ใช้มีการเปลี่ยนแปลงในบางจุดดังนั้นอาจเป็นเหตุผล ฉันได้อัปเดตสคริปต์เพื่อใช้คำสั่ง cgroup-tools
JanKanis

หากการคำนวณpercentผลลัพธ์เป็นศูนย์exprรหัสสถานะคือ 1 และสคริปต์นี้ออกก่อนกำหนด แนะนำให้เปลี่ยนบรรทัดเป็น: percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))(อ้างอิง: unix.stackexchange.com/questions/63166/… )
Willi Ballenthin

ฉันจะกำหนดค่า cgroup เพื่อฆ่ากระบวนการของฉันได้อย่างไรถ้าฉันเกินขีด จำกัด ?
d9ngle

7

นอกเหนือจากเครื่องมือที่daemontoolsแนะนำโดยมาร์คจอห์นสันแล้วคุณยังสามารถพิจารณาchpstได้ว่ามีrunitอะไรบ้าง Runit เองรวมอยู่busyboxด้วยดังนั้นคุณอาจติดตั้งไว้แล้ว

หน้าคนของchpstแสดงตัวเลือก:

-m ไบต์ จำกัด หน่วยความจำ จำกัด เซ็กเมนต์ข้อมูลส่วนสแต็กหน้าฟิสิคัลที่ล็อคและผลรวมของเซกเมนต์ทั้งหมดต่อกระบวนการเป็นไบต์ไบต์ละ


3

ฉันใช้ Ubuntu 18.04.2 LTS และสคริปต์ JanKanis ไม่เหมาะกับฉันอย่างที่เขาแนะนำ การรันlimitmem 100M scriptจำกัด RAM 100MB ด้วยการสลับไม่ จำกัด

การทำงานlimitmem 100M -s 100M scriptล้มเหลวอย่างเงียบ ๆ เป็นมีพารามิเตอร์ไม่มีชื่อcgget -g "memory:$cgname"memory.memsw.limit_in_bytes

ดังนั้นฉันจึงปิดการแลกเปลี่ยน:

# 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 เพิ่มแล้ว :)
d9ngle

2
ใช่ฉันแก้ไขคำตอบของฉัน ในการเปิดใช้งานข้อ จำกัด การแลกเปลี่ยนคุณต้องเปิดใช้งานการบัญชีการแลกเปลี่ยนในระบบของคุณ มีค่าใช้จ่ายในการดำเนินการเล็กน้อยดังนั้นจึงไม่ได้เปิดใช้งานตามค่าเริ่มต้นบน Ubuntu ดูการแก้ไขของฉัน
JanKanis

3

ในดิสทริบิวเตอร์ที่ยึดตาม systemd คุณยังสามารถใช้ cgroups ได้โดยอ้อมผ่าน systemd-run เช่นสำหรับกรณีของคุณ จำกัดpdftoppmRAM 500M ให้ใช้:

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

หมายเหตุ: นี่จะขอรหัสผ่านจากคุณ แต่แอปจะเปิดตัวในฐานะผู้ใช้ของคุณ อย่าอนุญาตให้สิ่งนี้หลอกคุณให้คิดว่าคำสั่งนั้นต้องการsudoเพราะจะทำให้คำสั่งทำงานภายใต้รูตซึ่งแทบจะเป็นความตั้งใจของคุณ

หากคุณไม่ต้องการป้อนรหัสผ่าน(หลังจากทั้งหมดในฐานะผู้ใช้ที่คุณเป็นเจ้าของหน่วยความจำทำไมคุณต้องใช้รหัสผ่านในการ จำกัด รหัสผ่าน)คุณสามารถใช้--userตัวเลือกได้ ตอนนี้ต้องบูตด้วยsystemd.unified_cgroup_hierarchyพารามิเตอร์เคอร์เนล


ขอบคุณทำให้วันของฉัน
Geradlus_RU
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.