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

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

pdftoppm사용자가 제공한 PDF를 300DPI 이미지로 변환하고 있습니다 . 사용자가 제공한 PDF 페이지 크기가 매우 크지 않는 한 이는 완벽하게 작동합니다. pdftoppm이 크기의 300DPI 이미지를 메모리에 저장하기에 충분한 메모리가 할당됩니다. 즉, 100인치 정사각형 페이지의 경우 100*300 * 100*300 * 픽셀당 4바이트 = 3.5GB입니다. 악의적인 사용자가 나에게 어리석게 큰 PDF를 보내면 모든 종류의 문제가 발생할 수 있습니다.

그래서 제가 하고 싶은 것은 제가 실행하려고 하는 자식 프로세스의 메모리 사용량에 대해 일종의 엄격한 제한을 설정하는 것입니다. 그리고 프로세스가 500MB 이상의 메모리를 할당하려고 하면 프로세스가 종료되도록 하는 것입니다. 그게 가능합니까?

이 목적으로 ulimit를 사용할 수는 없을 것 같지만 이에 상응하는 단일 프로세스가 있습니까?

답변1

이를 제한하는 또 다른 방법은 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최대 500MB의 물리적 메모리와 최대 5000MB의 물리적 메모리 및 스왑 메모리로 실행되도록 프로세스 집합을 제한하는 제어 그룹이 생성됩니다 myGroupmemory.limit_in_bytesmemory.memsw.limit_in_bytes. 이러한 옵션에 대한 자세한 내용은 여기에서 확인할 수 있습니다.https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-memory

통제 그룹에서 프로세스를 실행하려면 다음을 수행하십시오.

cgexec -g memory:myGroup pdftoppm

최신 Ubuntu 배포판에서는 이 예제를 설치해야 합니다.cgroup-tools패키지(이전cgroup-bin):

sudo apt install cgroup-tools

/etc/default/grub다음과 같이 변경 되도록 편집되었습니다 GRUB_CMDLINE_LINUX_DEFAULT.

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

그런 다음 실행 sudo update-grub하고 재부팅하여 새 커널 부팅 매개변수로 부팅합니다.

답변2

시스템 기반 배포판에서는 다음을 통해 간접적으로 cgroup을 사용할 수도 있습니다.시스템 실행 중. 예를 들어, 500M RAM 제한이 있는 경우 pdftoppmcgroupsv2로 시작하면 다음과 같이 간단히 수행할 수 있습니다.

systemd-run --scope -p MemoryMax=500M --user pdftoppm

이전에는 이systemd.unified_cgroup_hierarchy커널 매개변수 로 시작해야 함, 그러나 Ubuntu 22.04 cgroup-tools 2.0-2 테스트에서는 더 이상 그렇지 않은 것 같습니다. 명령은 커널 매개변수를 변경하지 않고도 작동하며 systemd.unified_cgroup_hierarchy설정되지 않습니다.

cgroupsv2 이전에는 를 사용할 수 없었습니다 --user. 대신 다음을 실행하십시오.

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

하지만 그렇지 않은 경우 --user사용자로 애플리케이션을 실행하더라도 매번 비밀번호를 묻는 메시지가 표시됩니다. 이 명령에 필요하다고 생각하는 바보짓을 하지 마십시오 sudo. 이렇게 하면 명령이 루트에서 실행될 것이므로 이는 거의 의도한 바가 아닙니다.

답변3

프로세스가 가장 많은 메모리를 소비하는 하위 프로세스를 더 생성하지 않는 경우 다음을 사용할 수 있습니다.setrlimit기능. 보다 일반적인 사용자 인터페이스는 ulimit쉘 명령을 사용하는 것입니다.

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

이는 호출된 프로세스가 다른 프로세스와 공유하는 메모리와 매핑되지만 예약되지 않은 메모리(예: Java의 대규모 힙)를 고려하고 제한하여 프로세스의 "가상" 메모리만 제한합니다. 그럼에도 불구하고 가상 메모리는 매우 커지는 프로세스에 대한 가장 가까운 근사치이므로 위의 오류는 사소합니다.

프로그램이 서브루틴을 생성하고 이에 의해 메모리가 할당되면 상황은 더욱 복잡해지며 제어 하에 프로세스를 실행하려면 도우미 스크립트를 작성해야 합니다. 블로그에 글을 썼는데,그리고어떻게.

답변4

다음 스크립트는 2022/Ubuntu 22.04부터 더 이상 사용되지 않습니다. Ubuntu 22.04는 더 이상 기본적으로 cgroups v1을 마운트하지 않으며 systemd-run은 이제 필요한 모든 것을 지원합니다. 하드 메모리 제한이 있는 프로그램을 실행하는 명령은 다음과 같습니다.

systemd-run --user --scope -p MemoryMax=<memorylimit> \
  -p MemorySwapMax=<swaplimit> <command>
  • memory.memsw.*사용된 메모리 + 스왑의 총량을 제어하는 ​​cgroups v1의 제어 파일 과 달리 메모리와 스왑에 대한 별도의 제한이 있다는 점에 유의하십시오 . 지금까지 메모리+스왑 조합에 대한 제한을 설정하는 방법을 찾지 못했습니다.

  • MemoryHigh덜 엄격한 또 다른 매개변수가 있습니다 MemoryMax. 프로세스를 종료하지는 않지만 프로세스를 조절하고 적극적으로 메모리를 교환하기 시작합니다.

아래 스크립트를 수정하여 cgroups v2에서 실행할 수 있습니다.Ciro Santilli의 답변, 그러나 systemd-run이제 필요한 모든 작업이 완료되었으므로 더 이상 필요하지 않습니다. 나는 원래 답변과 비슷한 스크립트를 가지고 있습니다 systemd-run.새로운 답변.


원래 답변:

아래 스크립트를 사용하고 있는데 훌륭하게 작동합니다. 통과합니다 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에 대한 명령줄 도구는 없습니다.

편집(2021): 지금까지 이 스크립트는 여전히 작동하지만 단일 프로그램을 사용하여 cgroup을 관리하라는 Linux 권장 사항에 어긋납니다. 이제 이 프로그램은 일반적으로 시스템화됩니다. 불행하게도 systemd에는 이 스크립트를 systemd 호출로 대체하기 어렵게 만드는 많은 제한 사항이 있습니다. 이 systemd-run --user명령을 사용하면 사용자는 리소스 제한이 있는 프로그램을 실행할 수 있지만 cgroups v1은 이 기능을 지원하지 않습니다. (docker는 최신 버전을 제외하고는 cgroupsv2에서 실행되지 않기 때문에 모든 사람이 cgroups v1을 사용합니다.) 루트 액세스(이 스크립트에도 필요함)를 사용하면 systemd-run올바른 시스템 지원 cgroup을 생성한 다음 수동으로 메모리 및 cgroup의 속성을 교환하지만 이는 아직 구현 중입니다. 당신은 또한 볼 수 있습니다이 잘못된 댓글맥락을 위해 그리고여기그리고여기관련 문서를 받으세요.

@Mikko의 의견에 따르면 systemd 내에서 이러한 스크립트를 사용하면 systemd가 세션 내 프로세스 추적을 잃을 위험이 있습니다. 나는 그러한 문제를 발견하지 못했지만 주로 단일 사용자 컴퓨터에서 이 스크립트를 사용합니다.

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

관련 정보