Linux

実行中プロセスの ulimit/rlimit を変更する

More than 3 years have passed since last update.

(bash のコマンドだと ulimit だけどシステムコールだと get/setrlimit だし、ulimit? rlimit? どっち?)

はじめに

ここ↓にある通りですが、カーネル 2.6.36 以降なら prlimit(1) で簡単にできます(prlimit が使える環境が手元になかったので試していませんが)。
それ以前のカーネル(あるいは Linux 以外)なら gdb でアタッチして対象プロセスで setrlimit を無理やり呼び出します。

手元に prlimit が使える環境がなかったので gdb を使います。

バッチを作成

gdb でアタッチした瞬間にプロセスが停止してしまうため gdb のコマンドは手打ちせず、停止時間を短くするために gdb のコマンドをバッチにしておく方が良いでしょう。

rlimit.gdb
set pagination off
set width 0
set height 0

set $rlim = &{0ll, 0ll}
print getrlimit(4, $rlim)
print *$rlim

set $rlim = &{-1ll, -1ll}
print setrlimit(4, $rlim)

detach
quit

先頭 3 行は gcore からコピりました、意味は知りません。

6 行目や 10 行目の 4RLIMIT_CORE です。具体的な値は /usr/include/bits/resource.h で定義されています。

9 行目の -1ll は long long の -1 で RLIM_INFINITY です。1つ目がソフトで2つ目がハードです。どちらも無制限に設定します。

gdb でプロセスにアタッチ

バッチを作成した後、対象プロセスの PID を調べて gdb を次のように実行します。

$ gdb -x rlimit.gdb -batch -p 12345
[Thread debugging using libthread_db enabled]
 :
$1 = 0
$2 = {0, -1}
$3 = 0
  • $1 は getrlimit の戻り値です(0 なら成功)
  • $2 は getrlimit で得られた現在のリミットです(ソフト、ハード)
  • $3 は setrlimit の戻り値です(0 なら成功)

コアダンプさせてみる

この後、このプロセスをセグらせるとコアダンプします。

セグらせる
$ kill -SIGSEGV 12345
対象プロセス
Segmentation fault (core dumped)

注意点

prlimit は試せる環境が無かったので判りませんが、gdb で setrlimit を呼ぶ方法はあくまでも対象プロセスのプロセス空間内で setrlimit を呼んでいるだけなので、ハードリミットを上げるためには対象プロセスが root で実行されているか CAP_SYS_RESOURCE ケーパビリティを持っている必要があります。

gdb を root で実行しようがなにしようが対象プロセスに特権が無いならハードリミットは上げられません。

ソフトリミットをハードリミットの範囲内で設定したり、ハードリミットを下げる分には問題ありません。

追記

id:hiboma から次のブコメをもらいました。

getrlimit, setrlimit 実行後にコケてしまうと errno を書きかえるので副作用がでます。errno を一旦退避しておいて元に戻しておくとより安全

実際に確認してみます。

まずは次のように errno が非 0 になるまでループさせます。
もちろんループの中で errno が非 0 になる可能性が無いので無限ループするはずです。

a.c
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <errno.h>

pid_t gettid()
{
    return syscall(SYS_gettid);
}

int main()
{
    pid_t pid = getpid();
    pid_t tid = gettid();

    printf("%ld %ld\n", pid, tid);
    while(!errno);
    printf("errno: %d\n", errno);

    return 0;
}

ハードリミットを 0 に設定して実行します。

$ gcc a.c
$ ulimit -H -c 0
$ ./a.out
12345 12345

gdb でアタッチします。

$ gdb -p 12345

setrlimit で -1 を設定してみます。が、非特権ユーザーなので失敗します。

(gdb)
set $rlim = &{-1ll, -1ll}
print setrlimit(4, $rlim)

デタッチすると・・・

(gdb)
detach

実行していたプロセスが終了します。

$ ./a.out
12345 12345
errno: 1

確かに setrlimit の失敗によって errno が書き換えられてしまってますね・・・
なので次のように errno を退避しておくほうが良いようです。

rlimit.gdb
set pagination off
set width 0
set height 0
set $errno = errno

set $rlim = &{0ll, 0ll}
print getrlimit(4, $rlim)
print *$rlim

set $rlim = &{-1ll, -1ll}
print setrlimit(4, $rlim)

set errno = $errno
detach
quit