単一の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最大500 MBの物理メモリと最大5000 MBの物理メモリとスワップメモリ​​で実行されるようにプロセスセットを制限する制御グループが作成されます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)属性を交換しますが、これはまだ実装中です。また、見ることができますこの間違ったコメント文脈のためにここそしてここ関連文書を入手してください。

@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

関連情報