首页 Linux ulimit
文章
取消

Linux ulimit

ulimit 用于限制 shell 启动的进程所占用的资源,如进程最多能打开多少文件。

ulimit 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8192
-c: core file size (blocks)         0
-m: resident set size (kbytes)      unlimited
-u: processes                       7854
-n: file descriptors                1024
-l: locked-in-memory size (kbytes)  64
-v: address space (kbytes)          unlimited
-x: file locks                      unlimited
-i: pending signals                 7854
-q: bytes in POSIX msg queues       819200
-e: max nice                        0
-r: max rt priority                 0
-N 15:                              unlimited

ulimit 有软限制和硬限制之分:

  • 软限制:任何进程都可以修改软限制,但是软限制不能超过硬限制;
  • 硬限制:普通进程可以降低硬限制,只有 root 进程可以提高硬限制;

  • 软限制是内核对相应资源强制执行的值(实际生效的值),硬限制则是软限制的上限。
  • 非特权进程可以将软限制设置为 0 到硬限制范围内的值,也可降低硬限制,但不可提升。
  • 特权进程(具有 CAP_SYS_RESOURCE 能力的进程)可以对任一限制值进行任意更改。
  • 通过 fork 创建的子进程继承其父进程的资源限制;execve 系统调用会保留资源限制。

getrlimit()setrlimit() 系统调用是用来获取、设置当前进程的 rlimit(资源限制)的。

shell 的 ulimit 内置命令可以看做是 getrlimit()、setrlimit() 两个系统调用的简单封装。

当调用 ulimit 内置命令修改 rlimit 时,其实修改的是当前 shell 进程的 rlimit 值。

因为子进程的 rlimit 是从父进程那里继承过来的,所以修改当前 shell 进程的 rlimit,会影响当前 shell 进程产生的子进程的 rlimit。当然,子进程自己也可以调用 setrlimit() 来修改其资源限制。

需要强调的是,每个进程的 rlimit 都是互不影响的,你设置你的,我设置我的。


ulimit 常用参数:

  • ulimit -a:查看当前 shell 的所有资源限制,默认显示软限制
  • ulimit -Sa:查看当前 shell 的所有资源限制,-S 表示显示软限制
  • ulimit -Ha:查看当前 shell 的所有资源限制,-H 表示显示硬限制
  • ulimit -n:查看当前可打开的文件描述符数量,软限制
  • ulimit -Hn:查看当前可打开的文件描述符数量,硬限制
  • ulimit -Sn 65536:修改可打开的文件描述符数为 65536,软限制
  • ulimit -Hn 65536:修改可打开的文件描述符数为 65536,硬限制
  • ulimit -HSn 65536:修改可打开的文件描述符数为 65536,软限制 + 硬限制
  • ulimit -n 65536:修改可打开的文件描述符数为 65536,软限制 + 硬限制(sh/bash)

nofile

Linux“一切皆文件”这个概念大家应该都听过,在 Linux 中,我们使用一个 file descriptor(文件描述符,简称 fd)来引用一个打开的“文件”(磁盘文件、套接字文件、管道文件等都统称为“文件”)。每个进程都有一个 file descriptor table(文件描述符表),fd 是一个非负整数,实际上,fd 是 fd-table 中的一个索引值;每个非守护进程默认都会打开 3 个 fd:0(标准输入)、1(标准输出)、2(标准错误)。

显然,fd-table 的大小是有限制的(nofile 资源限制值),毕竟不能无限大(因为需要占用内存资源以及“文件”资源),默认情况下,nofile 的软限制为 1024,硬限制为 4096,因为软限制是实际生效的值,所以默认情况下,每个进程同一时间最多可以打开 1024 个 fd(文件、套接字等)。

对于 apache、nginx 等 web 服务器,1024 个 fd 显然是不够的(一个连接至少要 1 个 fd,再算上其它 fd 消耗,1024 个 fd 根本支撑不了 1024 个用户的并发访问),所以通常需要去修改这些服务的 nofile 限制值,比较合理的值为 65536、102400。如果一个进程已经持有了 1024 个 fd,当它尝试获取第 1025 个 fd 时,会得到 Too many open files 错误(nofile 限制的是同一时间可以持有的 fd 数量,不是 fd 数值的大小)。

可以通过 /proc/<pid>/limits 文件来查看指定 pid 进程的 rlimit 限制值。

nofile 调整

注意,进程必须具有 root 权限或 CAP_SYS_RESOURCE 能力,才能提高其 rlimit 硬限制。

对于 nginx,可以在 nginx.conf 中配置 worker_rlimit_nofile 来指定每个 worker 工作进程的 nofile 限制值,如 worker_rlimit_nofile 102400

对于 sysvinit 方式启动的服务,如 service mysrv start,可以修改其服务文件(本质是 shell 脚本),在脚本开头添加 ulimit -n 102400 即可,子进程默认会从父进程中继承 rlimit 限制值,改好之后执行 service mysrv restart 生效。

对于 systemd 方式启动的服务,如 systemctl start mysrv,可以修改其 mysrv.service 文件,在 [Service] 配置段中添加 LimitNOFILE=102400,执行 systemctl daemon-reloadsystemctl restart mysrv 生效。

对于自己开发的程序,最简单的方式就是提供一个选项/配置,类似 nginx 的 worker_rlimit_nofile,然后在程序中调用 setrlimit() 接口来修改其 nofile 的限制值。

nofile 上限

nofile 的值不能是无限大的(unlimited),即使你是 root 权限执行,也不行,会提示这个错误:

1
2
3
4
5
6
7
8
9
10
$ ulimit -n unlimited
bash: ulimit: open files: cannot modify limit: Operation not permitted

$ cat /proc/sys/fs/nr_open 
1073741816

$ ulimit -n 1073741816

$ ulimit -n 1073741817
bash: ulimit: open files: cannot modify limit: Operation not permitted

可以发现,nofile 的限制值最大只能为 fs.nr_open 的值,这个内核参数的意思其实就是单个进程的 nofile 可设置的最大值,即使你是 root 用户也不能超过这个值。

而 nr_open 默认大小为 1048576(2 的 20 次方),基本上我们也不需要去调整 nr_open 值,因为默认的 100w 已经够大了,当然有需求的话,也是可以改的,比如改为 1000w。

除了 fs.nr_open 内核参数外,还有一个内核参数与 nofile 有关,那就是 fs.file-max,这个参数的意思是 linux 内核可同时打开的最大文件数量,如果这个参数设置的太小,即使你 nr_open 设置的再大,也无济于事,因为当前内核总共就只能打开这么多个文件。

所以通常我们会在 sysctl.conf 中调整这两个内核参数,默认情况下,这两个内核参数都是比较保守的,为了发挥系统的最佳性能,建议调大一些(我不是内核调优专家,所以配置值仅供参考):

1
2
fs.nr_open = 10000000
fs.file-max = 500000000

查看 fd 使用情况:cat /proc/sys/fs/file-nr,三个值分别表示:

  • 已分配的句柄数
  • 未使用的句柄数
  • file-max 值

/etc/security/limits.conf

网上很多教程都说修改 nofile 限制值要去改 /etc/security/limits.conf 配置文件,这里我想说一下我个人的见解(如果理解有误还请您指出):

根据 man 文档,limits.conf 只会被 pam_limits.so 模块使用,pam_limits.so 是 PAM(可插拔认证模块)的一个子模块,用于设置 rlimit 限制;

在配置 sshd_config 的时候,估计大家都见过这样一个配置:UsePam yes,这其实就是告诉 sshd,通过 ssh 登录的用户会使用 PAM 认证模块;

而 PAM 模块会读取 limits.conf,所以就会为登录的用户设置对应的 rlimit 限制值(shell 进程)。

因此,对于服务进程、守护进程(不是通过”登录”终端启动的进程),limits.conf 实际上不会生效。

limits.conf 语法:<domain> <type> <item> <limit>

  • domainusername@groupname*所有用户;
  • typesoft软限制、hard硬限制、-软限制和硬限制;
  • item:常用的有:nofile文件描述符、nproc进程数量;
  • limit:整型数值,如果为无限制,可以使用unlimited表示;

注意:domain 中的 * 表面上说是代表所有用户,但实际上它不包括 root 用户。

limits.conf 配置例子(改完需要重新登录来生效):

1
2
3
4
5
6
7
8
9
10
11
# nofile 文件描述符数
*    soft nofile 102400
*    hard nofile 102400
root soft nofile 102400
root hard nofile 102400

# nproc 最大进程/线程数
*    soft nproc unlimited
*    hard nproc unlimited
root soft nproc unlimited
root hard nproc unlimited

/etc/systemd/system.conf

如果你的系统用的是 systemd 而不是传统的 sysvinit,建议在修改了 /etc/security/limits.conf 的基础上再修改 systemd 提供的配置文件:

  • /etc/systemd/system.conf(系统实例)
  • /etc/systemd/user.conf(用户实例)

一般修改 system.conf 就行了。

修改 system.conf 后,由 service 文件启动的服务进程的 rlimit 会继承 system.conf 中配置的值,这样就不需要每个 service 文件都去配置 LimitNOFILE=102400 了。

编辑 /etc/systemd/system.conf 添加这两行,重启系统生效(或 systemctl daemon-reexec):

  • 设置nofile:DefaultLimitNOFILE=102400:102400
  • 设置nproc:DefaultLimitNPROC=infinity:infinity
本文由作者按照 CC BY 4.0 进行授权

Shell 脚本选项

字符集、字符编码