
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の物理メモリとスワップメモリで実行されるようにプロセスセットを制限する制御グループが作成されますmyGroup
memory.limit_in_bytes
memory.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の制限がある場合は、pdftoppm
cgroupsv2で起動すると、次のように簡単に実行できます。
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