prilmit についての調べ物
社内 slack で prlimit の話題があがったので、どういうもんだったかと調べた
prlimit(2) システムコール
任意の pid のリソースリミットを変更するシステムコールである。
ulimit
, setrlimit
, getrlimit
, /proc/$pid/limits
, /etc/security/limits.conf
, pam_imits.so
... と関連キーワードを並べると「ああ、リソースリミットって あれ のことですね」と分かるよね
#include <sys/time.h> #include <sys/resource.h> int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
一見、複雑そうなインタフェースに見えるが、 pid
が指定できる getrlimit(2) と setrlimit(2) の合体インタフェースだと見なせばいいかな?
prlimit(1) コマンド
prlimit(2) システムコールを呼び出して、任意のプロセスのリソースリミットを変更するのが prlimit(1) コマンド
使用例
nginx の リソースリミットを取得してみよう.
$ sudo LANG=C prlimit --pid $( pgrep nginx -n ) RESOURCE DESCRIPTION SOFT HARD UNITS AS address space limit unlimited unlimited bytes CORE max core file size 0 unlimited blocks CPU CPU time unlimited unlimited seconds DATA max data size unlimited unlimited bytes FSIZE max file size unlimited unlimited blocks LOCKS max number of file locks held unlimited unlimited MEMLOCK max locked-in-memory address space 65536 65536 bytes MSGQUEUE max bytes in POSIX mqueues 819200 819200 bytes NICE max nice prio allowed to raise 0 0 NOFILE max number of open files 1024 4096 👈 NPROC max number of processes 7084 7084 RSS max resident set size unlimited unlimited pages RTPRIO max real-time priority 0 0 RTTIME timeout for real-time tasks unlimited unlimited microsecs SIGPENDING max number of pending signals 7084 7084 STACK max stack size 8388608 unlimited bytes
次に、リソースリミットを変更してみよう.
<soft>:<hard>
で値を指定できる (ソフトリミット、ハードリミットの説明は省略)- さらに細かな指定方法は
man 3 prlimit
で確認してね
-bash-4.2$ sudo prlimit --pid $( pgrep nginx -n ) --nofile=9999:9999
実行後、NOFILE max number of open files
が 9999:999
になったのが確認できる 😊
-bash-4.2$ sudo prlimit --pid $( pgrep nginx -n ) RESOURCE DESCRIPTION SOFT HARD UNITS AS address space limit unlimited unlimited バイト CORE max core file size 0 unlimited blocks CPU CPU time unlimited unlimited seconds DATA max data size unlimited unlimited バイト FSIZE max file size unlimited unlimited blocks LOCKS max number of file locks held unlimited unlimited MEMLOCK max locked-in-memory address space 65536 65536 バイト MSGQUEUE max bytes in POSIX mqueues 819200 819200 バイト NICE max nice prio allowed to raise 0 0 NOFILE max number of open files 9999 9999 👈 NPROC max number of processes 7084 7084 RSS max resident set size unlimited unlimited pages RTPRIO max real-time priority 0 0 RTTIME timeout for real-time tasks unlimited unlimited microsecs SIGPENDING max number of pending signals 7084 7084 STACK max stack size 8388608 unlimited バイト
オプションを変えれば他のリソースリミットも操作できる
コマンドのユースケース
「とある巨大なデーモン を起動したけどファイルデスクリプタの上限を上げるのを忘れていた ... 上限を上げ直したいけど、再起動は手間がかかるしヘビーなんだよなぁ。うーんどうしよう 😭 」
という風景を何度も見たことがある。こういうケースで役に立つだろう
あるいは 動的 にプロセスのリソースリミットを 制限 したいケースでも使えるが、制限しないと困る !!! というケースが思い浮かばない. 何か有用な事例があったら教えていただきい
prlimit(2) カーネルのソースコード
prlimit(2) のカーネルのソースコードはコンパクトで、詳細に入り込まず斜め読みでも理解できる感じだ
/* バージョンは 4.17rc-2 */ SYSCALL_DEFINE4(prlimit64, pid_t, pid, unsigned int, resource, const struct rlimit64 __user *, new_rlim, struct rlimit64 __user *, old_rlim) { struct rlimit64 old64, new64; struct rlimit old, new; struct task_struct *tsk; unsigned int checkflags = 0; int ret; if (old_rlim) checkflags |= LSM_PRLIMIT_READ; if (new_rlim) { if (copy_from_user(&new64, new_rlim, sizeof(new64))) return -EFAULT; rlim64_to_rlim(&new64, &new); checkflags |= LSM_PRLIMIT_WRITE; } rcu_read_lock(); tsk = pid ? find_task_by_vpid(pid) : current; if (!tsk) { rcu_read_unlock(); return -ESRCH; } ret = check_prlimit_permission(tsk, checkflags); if (ret) { rcu_read_unlock(); return ret; } get_task_struct(tsk); rcu_read_unlock(); ret = do_prlimit(tsk, resource, new_rlim ? &new : NULL, old_rlim ? &old : NULL); 👈 if (!ret && old_rlim) { rlim_to_rlim64(&old, &old64); if (copy_to_user(old_rlim, &old64, sizeof(old64))) ret = -EFAULT; } put_task_struct(tsk); return ret; }
中で呼び出している do_prlimit() がシステムコールのコアで、ここも把握するとよい
/* make sure you are allowed to change @tsk limits before calling this */ int do_prlimit(struct task_struct *tsk, unsigned int resource, struct rlimit *new_rlim, struct rlimit *old_rlim) { struct rlimit *rlim; int retval = 0; if (resource >= RLIM_NLIMITS) return -EINVAL; if (new_rlim) { if (new_rlim->rlim_cur > new_rlim->rlim_max) return -EINVAL; if (resource == RLIMIT_NOFILE && new_rlim->rlim_max > sysctl_nr_open) return -EPERM; } /* protect tsk->signal and tsk->sighand from disappearing */ read_lock(&tasklist_lock); if (!tsk->sighand) { retval = -ESRCH; goto out; } rlim = tsk->signal->rlim + resource; task_lock(tsk->group_leader); if (new_rlim) { /* Keep the capable check against init_user_ns until cgroups can contain all limits */ if (new_rlim->rlim_max > rlim->rlim_max && !capable(CAP_SYS_RESOURCE)) retval = -EPERM; if (!retval) retval = security_task_setrlimit(tsk, resource, new_rlim); if (resource == RLIMIT_CPU && new_rlim->rlim_cur == 0) { /* * The caller is asking for an immediate RLIMIT_CPU * expiry. But we use the zero value to mean "it was * never set". So let's cheat and make it one second * instead */ new_rlim->rlim_cur = 1; } } if (!retval) { if (old_rlim) *old_rlim = *rlim; if (new_rlim) *rlim = *new_rlim; } task_unlock(tsk->group_leader); /* * RLIMIT_CPU handling. Note that the kernel fails to return an error * code if it rejected the user's attempt to set RLIMIT_CPU. This is a * very long-standing error, and fixing it now risks breakage of * applications, so we live with it */ if (!retval && new_rlim && resource == RLIMIT_CPU && new_rlim->rlim_cur != RLIM_INFINITY && IS_ENABLED(CONFIG_POSIX_TIMERS)) update_rlimit_cpu(tsk, new_rlim->rlim_cur); out: read_unlock(&tasklist_lock); return retval; }
関数の命名が明瞭なので、だいたい何をやっているか抽象的には理解できるだろう
こんな感じ
prlimit(2) が呼び出している do_prlimit() は、getrlimit(2), setrlimit(2) からも呼び出しされていて、デザインパターンで言う所の Facade パターン(ほんとかな?) のような構造になっている
getrlimit(2) と setrlimit(2) のソースコード
COMPAT_SYSCALL_DEFINE2(getrlimit, unsigned int, resource, struct compat_rlimit __user *, rlim) { struct rlimit r; int ret; ret = do_prlimit(current, resource, NULL, &r); 👈 if (!ret) { struct compat_rlimit r32; if (r.rlim_cur > COMPAT_RLIM_INFINITY) r32.rlim_cur = COMPAT_RLIM_INFINITY; else r32.rlim_cur = r.rlim_cur; if (r.rlim_max > COMPAT_RLIM_INFINITY) r32.rlim_max = COMPAT_RLIM_INFINITY; else r32.rlim_max = r.rlim_max; if (copy_to_user(rlim, &r32, sizeof(struct compat_rlimit))) return -EFAULT; } return ret; }
SYSCALL_DEFINE2(setrlimit, unsigned int, resource, struct rlimit __user *, rlim) { struct rlimit new_rlim; if (copy_from_user(&new_rlim, rlim, sizeof(*rlim))) return -EFAULT; return do_prlimit(current, resource, &new_rlim, NULL); 👈 }
つまり prlimit(2) を読むと一緒に getlimit(2), setrlimit(2) も把握したことになってお得だ !!!
その他
prlimit(2) に対応していない古いカーネルの場合は gdb で変更する技がある
感想
- ソースを見て見ると以外やシンプル
- リソースリミットって暗黙的な設定になりがちで、結構な確率で足を撃っているように思う