动态修改运行中进程的 rlimit

有时候发现线上运行的程序没有修改 ulimit -n ,导致文件描述符不够用,但是又不能随便重启服务,或者碰到运行中的程序可能不定时会 segment fault ,但是由于没 ulimit -c ,无法得到 core 文件调试。但是 linux 的 ulimit 命令只对当前会话有效,已经启动的进程是没法用这个命令修改限制的。这时候如果能动态修改进程的 rlimit 就会方便很多。linux 内核版本 2.6.36 以后,多了一个 prlimit 调用,可以实现动态修改某个进程的 rlimit。

例如,这个 C 写的小程序就可以放开指定进程的 core dump 限制。

#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>

int main(int argc, char** argv) {
    pid_t pid;
    struct rlimit new_limit;
    int result;
    if (argc < 2) {
        return 1;
    }
    pid = atoi(argv[1]);
    new_limit.rlim_cur = RLIM_INFINITY;
    new_limit.rlim_max = RLIM_SAVED_MAX;
    result = prlimit(pid, RLIMIT_CORE, &new_limit, NULL);
    return result;
}

rlimit 相关常量在 /usr/include/bits/resource.h 中有定义。这里 prlimit 的具体定义也可以通过 man 2 prlimit 查看文档。

如果有幸可以用上 util-linux 2.21 以上版本(http://www.kernel.org/pub/linux/utils/util-linux/),那么直接就会有一个 prlimit 命令可用。那就非常方便了。

但是如果内核版本较低,没有这功能又怎么办呢?其实还可以通过万能的 gdb 钩进进程调用 setrlimit 来实现。于是,可以包装一个脚本出来:

#!/bin/bash

f="0"
while getopts ":c:d:e:f:i:l:m:n:p:q:r:s:t:u:v:x" opt; do
  if [ "$f" == "1" ]; then
    echo "too many arguments" >&2;
    exit 1
  fi
  f="1"
  v="$OPTARG"
  case $opt in
  c) r=4  ;; # RLIMIT_CORE       core file size
  d) r=2  ;; # RLIMIT_DATA       data seg size
  e) r=13 ;; # RLIMIT_NICE       scheduling priority
  f) r=1  ;; # RLIMIT_FSIZE      file size
  i) r=11 ;; # RLIMIT_SIGPENDING pending signals
  l) r=8  ;; # RLIMIT_MEMLOCK    max locked memory
  m) r=5  ;; # RLIMIT_RSS        max memory size
  n) r=7  ;; # RLIMIT_NOFILE     open files
  q) r=12 ;; # RLIMIT_MSGQUEUE   POSIX message queues
  r) r=14 ;; # RLIMIT_RTPRIO     real-time priority
  s) r=3  ;; # RLIMIT_STACK      stack size
  t) r=0  ;; # RLIMIT_CPU        cpu time
  u) r=6  ;; # RLIMIT_NPROC      max user processes
  v) r=9  ;; # RLIMIT_AS         virtual memory
  x) r=10 ;; # RLIMIT_LOCKS      file locks

  ?) echo "bad argument $opt" >&2; exit 1 ;;
  esac
done

shift $(($OPTIND - 1))

if echo "$v" | grep -q -E "^\-?[0-9]+$"; then
  true
else
  echo "bad rlimti value $v" >&2
  exit 2
fi

if [ `echo "$v==-1 || ($v>=0 && $v<1048576)"|bc` == '0' ];then
  echo "bad rlimti value $v" >&2
  exit 2
fi

pid=$1
bin=`readlink /proc/$pid/exe`
if [ -z "$bin" ]; then
  echo "process $pid not found" >&2
  exit 3
fi

cmd='
set $rlim=&{-1ll,-1ll}
print getrlimit('$r',$rlim)
set *$rlim[0]='$v'
print setrlimit('$r',$rlim)
quit
'

result=`echo "$cmd" | gdb $bin $pid 2>/dev/null | grep '(gdb) \$2 ='`
result="${result##*= }"
exit $result

sudo ./rlimit.sh -c -1 12345

就可以 12345 放开进程的 core dump

参数同 ulimit ,-1 表示 unlimited。不过用这种方法,只能调整 soft rlimit ,而且没法超过原有的 hard rlimit。

修改以后,可以通过 cat /proc/<pid>/limits 查看效果

补充(感谢zolker):
对于 redhat 系,如 centos 6.2 之后可以通过

echo -n 'Max processes=10240:10240' >/proc/<pid>/limits

来修改。这里的 limit 的名称就是

cat /proc/<pid>/limits

里看到的