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-reload
、systemctl 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>
:
domain
:username
、@groupname
、*
所有用户;type
:soft
软限制、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