Linux ulimit详解

ulimit 用于限制 shell 启动的进程所占用的资源,支持以下各种类型的限制:所创建的内核文件的大小、进程数据块的大小、Shell 进程创建文件的大小、内存锁住的大小、常驻内存集的大小、打开文件描述符的数量、分配堆栈的最大大小、CPU 时间、单个用户的最大线程数、Shell 进程所能使用的最大虚拟内存。

ulimit 命令

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

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

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

getrlimit()setrlimit() 两个系统调用是用来获取、设置当前进程的 rlimit(资源限制)的,而 shell 的 ulimit 内置命令其实就是 getrlimit()、setrlimit() 两个系统调用的包装命令,所以当我们调用 ulimit 内置命令修改 rlimit 时,其实修改的是当前 shell 进程的 rlimit 值。但又因为子进程的 rlimit 值默认是从父进程那里继承过来的,所以使用 ulimit 修改当前 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 文件来查看指定进程的 rlimit 限制值,如 cat /proc/7865/limits 查看 pid 为 7865 的进程的 rlimit 限制值。

如何提高进程的 nofile 限制值

对于 nginx,可以通过 worker_rlimit_nofile 配置项来指定每个 worker 工作进程的 nofile 限制值,如 worker_rlimit_nofile 102400

那如果是一个普通进程呢?该如何修改它的 nofile 限制值?也简单,如果是 sysvinit 方式启动的服务(service mysrv start),则修改对应的 mysrv 服务文件(本质就是 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 的限制值不能是无限大的(unlimited),即使你是 root 权限执行,也不行,会提示这个错误:

可以发现,nofile 的限制值最大只能为 fs.nr_open 的值,这个内核参数的意思其实就是单个进程的 nofile 可设置的最大值,即使你是 root 用户也不能超过这个 nr_open 值。而 nr_open 默认大小为 1048576(2 的 20 次方),基本上我们也不需要去调整 nr_open 值,因为默认的 100w 已经够大了,当然有需求的话,也是可以改的,比如改为 1000w。

除了 fs.nr_open 内核参数外,还有一个内核参数与 nofile 有关,那就是 fs.file-max,这个参数的意思是 linux 内核允许同时打开的最大 fd 数量,如果这个参数设置的太小,即使你 nr_open 设置的再大,也无济于事,因为当前内核总共就只能打开这么多个文件描述符。所以通常我们会在 sysctl.conf 中调整这两个内核参数,默认情况下,这两个内核参数都是比较保守的,为了发挥系统的最佳性能,建议调大一些:

查看 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 配置例子(改完需要重新登录来生效):

/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 添加这两行,然后保存退出,重启系统生效:

  • nofile:DefaultLimitNOFILE=102400:102400
  • nproc:DefaultLimitNPROC=infinity:infinity