단일 Linux 프로세스의 메모리 사용량 제한


151

나는 실행 해요 pdftoppm300DPI 이미지로 사용자가 제공하는 PDF로 변환 할 수 있습니다. 사용자가 페이지 크기가 매우 큰 PDF를 제공하는 경우를 제외하고는 효과적입니다. 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 제약하여 프로세스를 제한 할 수 있습니다.

이 도구가 작동하는 방식은 생성 된 프로세스가 설정된 경계를 초과하지 않은 경우 초당 여러 번 확인하는 것입니다. 이는 실제로 시간 초과 알림이 표시되고 프로세스가 종료되기 전에 프로세스 초과 구독 할 수 있는 작은 창이 있다는 것을 의미 합니다.

보다 정확한 접근 방식은 cgroup을 포함 할 가능성이 있지만, Docker 또는 runC를 사용하더라도 cgroup에 대해보다 사용자 친화적 인 추상화를 제공하는 경우에도 설정에 훨씬 더 관여합니다.


지금 나를 위해 일하는 것 같습니다 (다시?) 여기 구글 캐시 버전이 있습니다 : webcache.googleusercontent.com/…
kvz

작업 시간과 함께 타임 아웃을 사용할 수 있습니까 (메모리와 코어를 모두 제한해야 함)?
ransh

7
이 답변은 coreutils동일한 이름 의 Linux 표준 유틸리티를 나타내는 것이 아닙니다 ! 따라서 시스템의 어느 곳에서나 timeout리눅스 표준 coreutils패키지 가 될 것으로 예상되는 스크립트가있는 경우 대답은 위험 할 수 있습니다 ! 이 도구가 데비안과 같은 배포 용으로 패키지되어 있다는 것을 알지 못합니다.
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

라는 컨트롤 그룹을 생성하고 myGroupmyGroup에서 실행되는 프로세스 세트를 최대 500MB의 실제 메모리와 최대 5000MB의 스왑으로 제한합니다. 제어 그룹에서 프로세스를 실행하려면 다음을 수행하십시오.

cgexec -g memory:myGroup pdftoppm

현대 우분투 배포판 에서이 예제는 cgroup-bin패키지를 설치하고 다음 /etc/default/grub으로 변경 GRUB_CMDLINE_LINUX_DEFAULT하기 위해 편집 해야 합니다.

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

sudo update-grub새 커널 부팅 매개 변수로 부팅하기 위해 실행 하고 재부팅합니다.


3
firejail프로그램은 또한 메모리 제한으로 프로세스를 시작할 수 있도록합니다 (cgroup과 네임 스페이스를 사용하여 메모리 이상의 것을 제한합니다). 내 시스템에서는 이것이 작동하기 위해 커널 명령 줄을 변경할 필요가 없었습니다!
Ned64

1
GRUB_CMDLINE_LINUX_DEFAULT설정을 영구적으로 유지 하려면 수정 이 필요 합니까? 나는 그것을 여기에서 지속시키는 또 다른 방법을 찾았다 .
stason


이 답변에서 일부 배포판 (예 : Ubuntu)에서는 sudo가 cgcreate에 필요하며 현재 사용자에게 권한이 부여되지 않은 경우 이후의 명령도 필요합니다. 이를 통해 독자는이 정보를 다른 곳에서 찾을 필요가 없습니다 (예 : askubuntu.com/questions/345055 ). 이 효과에 대한 편집을 제안했지만 거부되었습니다.
스튜 기본

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
내가 질문을 올바르게 이해하면 하위 프로세스 (자식) 당 한도는 얼마입니까?
akira

어쨌든 @MarkR, 가상 주소 공간은 사용 된 메모리에 대한 근사치입니다. 특히 가상 시스템 (예 : Java)에 의해 제어되지 않는 프로그램을 실행하는 경우. 적어도 나는 더 나은 측정법을 모른다.

2
그냥 감사를 말하고 싶었다 -이 ulimit방법은 나에게 도움 firefox의의 ' 버그 622816 - 파이어 폭스를 "정지", 또는 시스템 충돌 할 수있는 큰 이미지를로드 ; RAM에서 USB 부팅시 OS를 정지시키는 경향이 있으며 하드 재시작이 필요합니다. 이제 적어도 firefoxOS 자체가 충돌하여 OS가 활성화 된 상태로 유지됩니다.
sdaau

8

아래 스크립트를 사용하고 있습니다. 를 통해 cgroup을 사용합니다 cgmanager. 업데이트 : 이제의 명령을 사용합니다 cgroup-tools. 이 스크립트의 이름을 지정 limitmem하고 $ PATH에 넣으면 다음과 같이 사용할 수 있습니다 limitmem 100M bash. 메모리와 스왑 사용량이 모두 제한됩니다. 메모리 만 제한하려면로 줄을 제거하십시오 memory.memsw.limit_in_bytes.

편집 : 기본 Linux 설치에서는 스왑 사용량이 아닌 메모리 사용량 만 제한합니다. 스왑 사용량 제한을 활성화하려면 Linux 시스템에서 스왑 계정을 활성화해야합니다. 설정 / 추가 swapaccount=1하여 /etc/default/grub그렇게 보이게하십시오.

GRUB_CMDLINE_LINUX="swapaccount=1"

그런 다음 실행 sudo update-grub하고 재부팅하십시오.

면책 조항 : cgroup-tools앞으로도 깨질 경우 놀라지 않을 것입니다. 올바른 해결책은 cgroup 관리에 systemd api를 사용하는 것이지만 해당 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 request모든 프로세스에 대해 실행하려고합니다 limitmem 100M processname. Xubuntu 16.04 LTS를 사용 중이며 해당 패키지가 설치되었습니다.
Aaron Franke

Ups, 나는이 오류 메시지가 나타납니다 : $ 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에서도 사용할 수 없습니다. 사용하는 시스템화 된 API는 어느 시점에서 변경되었으므로 아마도 그 이유 일 것입니다. cgroup-tools 명령을 사용하도록 스크립트를 업데이트했습니다.
JanKanis

계산 percent결과가 0이면 expr상태 코드는 1이며이 스크립트는 조기에 종료됩니다. 에 라인을 변경하는 것이 좋습니다 percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))(참고 : unix.stackexchange.com/questions/63166/... )
윌리 Ballenthin에게

한도를 초과하면 프로세스를 종료하도록 cgroup을 구성하려면 어떻게해야합니까?
d9ngle

7

에서 도구 외에도 daemontools마크 존슨에 의해 제안, 당신은 또한 고려할 수 chpst에서 발견된다 runit. Runit 자체는에 번들로 제공 busybox되므로 이미 설치했을 수 있습니다.

man 페이지chpst 는 옵션 보여줍니다 :

-m 바이트는 메모리를 제한합니다. 데이터 세그먼트, 스택 세그먼트, 잠긴 실제 페이지 및 프로세스 당 모든 세그먼트의 총계를 각각 바이트 바이트로 제한하십시오.


3

Ubuntu 18.04.2 LTS를 실행 중이며 JanKanis 스크립트가 제안한대로 작동하지 않습니다. 무제한 스왑으로 실행시 limitmem 100M script100MB의 RAM이 제한 됩니다.

라는 매개 변수가 없어 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
맞아, 나는 대답을 편집했다. 스왑 제한을 활성화하려면 시스템에서 스왑 계정을 활성화해야합니다. 이에 대한 런타임 오버 헤드가 적으므로 우분투에서는 기본적으로 활성화되어 있지 않습니다. 내 편집을 참조하십시오.
JanKanis

2

모든 시스템 기반 배포판에서 systemd-run을 통해 간접적으로 cgroup을 사용할 수도 있습니다. 예를 들어 pdftoppm500M의 RAM 으로 제한 하는 경우 다음을 사용하십시오.

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

참고 : 암호를 묻지 만 앱이 사용자로 시작됩니다. 이 명령이 명령을 필요로한다고 생각하지 않도록 sudo하십시오. 명령이 루트에서 실행되도록하기 때문입니다.

암호를 입력하지 않으려면 (결국 메모리를 소유 한 사용자로서 암호를 제한해야하는 이유는 무엇입니까)--user 옵션을 사용할 수는 있지만 cgroupsv2 지원이 활성화되어 있어야합니다. 이제 커널 매개 변수 로 부팅해야합니다systemd.unified_cgroup_hierarchy .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.