ユーザー提供のPDFを300DPI画像に変換するためにpdftoppm
を実行しています。これは、ユーザーがPDF =非常に大きなページサイズ。pdftoppm
は、そのサイズの300DPIイメージをメモリに保持するのに十分なメモリを割り当てます。100インチの正方形のページの場合、100 * 300 * 100 * 300 *ピクセルあたり4バイトです= 3.5GB。悪意のあるユーザーが私に愚かな大きなPDFを与えるだけで、あらゆる種類の問題を引き起こす可能性があります。
だから私がしたいのは、実行しようとしている子プロセスのメモリ使用量に何らかのハード制限を課すことです。たとえば、500MBを超えるメモリを割り当てようとする場合は、プロセスを停止させてください。それは可能ですか?
これにはulimitは使用できないと思いますが、1プロセスの同等機能はありますか?
Ulimitにはいくつかの問題があります。以下は、このトピックに関する有用な資料です。 Linuxでのプログラムの時間とメモリの消費を制限する 。これにより、プロセス(およびそのフォーク)をケージできる timeout ツールが表示されます。 )時間またはメモリ消費量。
タイムアウトツールには、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の制約によってプロセスをそれぞれ制限することもできます。
このツールが機能する方法は、生成されたプロセスがその設定された境界をオーバーサブスクライブしていないかどうかを1秒あたり複数回チェックすることです。つまり、実際には小さなウィンドウがあり、タイムアウトが通知されてプロセスが強制終了する前に、プロセスが潜在的にオーバーサブスクライブする可能性があります。
したがって、より適切なアプローチにはcgroupが含まれる可能性がありますが、DockerまたはrunCを使用する場合でも、cgroupに関するよりユーザーフレンドリーな抽象化を提供する場合でも、セットアップははるかに複雑になります。
これを制限する別の方法は、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
を実行し、再起動して新しいカーネルブートパラメータでブートします。
プロセスがメモリを最も多く消費する子を生成しない場合は、 setrlimit
関数を使用できます。そのためのより一般的なユーザーインターフェイスは、シェルのulimit
コマンドを使用することです。
$ ulimit -Sv 500000 # Set ~500 mb limit
$ pdftoppm ...
これは、プロセスの「仮想」メモリのみを制限し、呼び出されるプロセスが他のプロセスと共有するメモリ、およびマップされているが予約されていないメモリ(Javaの大きなヒープなど)を考慮し、制限します。それでも、仮想メモリは、非常に大きくなるプロセスに最も近いため、前述のエラーは重要ではありません。
プログラムが子を生成し、メモリを割り当てるのが子である場合、それはより複雑になり、制御下でプロセスを実行するための補助スクリプトを記述する必要があります。私はブログに why と how と書いた。
Systemdベースのディストリビューションでは、systemd-runを介してcgroupを間接的に使用することもできます。例えば。 pdftoppm
を500MのRAMに制限する場合は、次を使用します。
systemd-run --scope -p MemoryLimit=500M pdftoppm
注:これはパスワードを要求しますが、アプリはユーザーとして起動されます。これは、コマンドがSudo
を必要とすると誤解させないでください。これは、コマンドをrootの下で実行することになるため、ほとんど意図していませんでした。
パスワードを入力したくない場合(結局のところ、あなたが自分のメモリを所有しているユーザーとして、それを制限するためにパスワードが必要なのはなぜですか)、あなた--user
オプションを使用できますが、これを機能させるには、cgroupsv2サポートを有効にする必要があります。これは、現在 systemd.unified_cgroup_hierarchy
カーネルパラメータでブートする必要があります です。
以下のスクリプトを使用しています。 更新:cgmanager
を通じてcgroupを使用します。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を使用することですが、そのa.t.m用のコマンドラインツールはありません。
#!/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
Mark Johnsonが提案したdaemontools
のツールに加えて、chpst
にあるrunit
も検討できます。 Runit自体はbusybox
にバンドルされているため、すでにインストールされている可能性があります。
chpst
のmanページ はオプションを示しています。
-mバイトはメモリを制限します。データセグメント、スタックセグメント、ロックされた物理ページ、およびプロセスごとのすべてのセグメントの合計をそれぞれバイトバイトに制限します。
私はUbuntu 18.04.2 LTSを実行しています。JanKanisスクリプトは、彼が示唆するように私にはまったく機能しません。 limitmem 100M script
を実行すると、100 MBのRAM with nlimited swap)が制限されます。
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`