本文要点
ulimit是一个 shell 内建命令,用来查看和修改当前 shell 及其启动进程的资源限制(打开文件数、CPU 时间、虚拟内存、进程数等)- 限制分 soft(软限制) 和 hard(硬限制) 两层:普通用户只能把 soft 调高到不超过 hard;只有 root 能提高 hard,而 hard 还受内核参数
fs.nr_open约束 - 最常用场景
ulimit -n对应"打开文件数上限",默认 1024。高并发服务(Nginx/Redis 等)经常遇到too many open files,就是这个值太小 - 临时修改只对当前 shell 生效,退出即失效;永久修改对登录用户走
/etc/security/limits.conf(经 PAM),对 systemd 服务走单位文件里的LimitNOFILE等指令 - 排查实际进程的限制:
cat /proc/<PID>/limits或prlimit,能直接看到每个运行中进程生效的软硬限制
为什么需要 ulimit:系统中的"资源配额"
很多时候我们会在运维中碰到这样的报错:Nginx 日志里刷 too many open files,或者某个程序启动时提示 Resource temporarily unavailable、cannot allocate memory。它们背后往往都是同一类原因——进程触碰到了系统给的资源上限。
Linux 为了防止单个进程(或单个用户)把机器资源耗尽,为每个进程都挂了一组资源限制(resource limit):最多能打开多少个文件、最多占多少 CPU 时间、最多用多少内存、最多派生多少进程……这些限制就像一个"配额",我们日常排查高并发、崩溃、OOM 问题时最先该翻的就是它。
而操作和查看这些限制的命令,就是 ulimit。它是 shell 内建命令,直接在当前 shell 进程上生效,你启动的每个子进程(包括后台服务脚本文本)都会继承这些值。
Ubuntu 24.04 上无需安装,任何 shell 都自带。先看看系统当前默认限制:
ulimit -a
每一项代表一种资源类型,括号里就是它的命令行参数缩写:
open files (-n) 1024:最大打开文件描述符数,最常见的坑;max user processes (-u) 7219:当前用户最多能同时运行的进程数;file size (-f) unlimited:单个文件最大能写到多大;cpu time (-t) unlimited:可消耗的 CPU 时间(秒),超了进程会被杀掉;stack size (-s) 8192:进程栈大小(KB);max memory size (-m)、virtual memory (-v):内存类限制;core file size (-c) 0:是否允许产生 core dump 文件(0 表示禁止,调试崩溃时经常先ulimit -c unlimited)。
软限制与硬限制:两级配额体系
ulimit 的限制分成两层,这非常重要:
- soft(软限制):进程当前实际生效的上限。普通进程运行时给自己的可提升空间。可以用
ulimit -S查看/修改。 - hard(硬限制):soft 的天花板。普通用户只能把 soft 调到不超过 hard,且无法调高 hard;只有 root 能同时提高 soft 和 hard。
之所以这么设计,是为了防止普通用户"自我解放"——比如管理员把文件描述符上限定为 8192,用户不能自己偷偷改成百万。查看 / 修改时用 -S / -H 前缀区分:
ulimit -Sn # 只看 soft
ulimit -Hn # 只看 hard
ulimit -n 2048 # 直接改 -n 时默认改 softroot 也不能为所欲为:hard 上限本身还受内核参数 fs.nr_open 约束。这就是为什么上面截图中 hard open files 是 1048576——fs.nr_open 的默认值就是它。要突破必须先在 sysctl 里提高内核参数:
sysctl fs.nr_open # 默认 1048576,这就是 hard 的天花板
sysctl -w fs.nr_open=3000000 # 提高内核上限
ulimit -Hn 3000000 # root 现在可以把 hard 调高临时修改:ulimit 的日常用法
打开文件数:高频排查场景
ulimit -n(打开文件数)是运维里最常被动的值。默认 1024 对高并发服务来说太少了,Nginx、Redis、Java 应用连接数一多就会撞上限。临时调整只需一条命令:
ulimit -n 65535
ulimit -n # 再次查看注意:这样只对当前终端会话生效。新开一个终端、或重开后,又回到 1024。要永久生效得靠后面的配置文件方案。
限制文件大小 / CPU 时间
反向地,ulimit 也常用来限制某个命令的行为,防止失控。比如限制 shell 写出的文件最大 4 块(每块 512 字节),超了立刻报错终止:
ulimit -f 4
dd if=/dev/zero of=/tmp/big.bin bs=1M count=5 # 写一半触发 "File size limit exceeded"再比如限制 CPU 时间 1 秒,防止死循环脚本烧死 CPU。
文件描述符耗尽演示:errno=24
用一个 C 小程序实测"文件描述符用尽"现象。程序不断打开 /dev/null,直到碰壁:
ulimit -n 100 # 先把 open files 调小
./fdtest # 打开直到失败
ulimit -n 10240 # 调大再跑一遍
./fdtest
可以看到限制为 100 时只成功打开了 97 个(系统本身占用 stdin/stdout/stderr 等几个描述符)就报 errno=24——即 EMFILE: Too many open files,这正是 Nginx/Redis 日志里那句经典报错的根源。把限制调到 10240 后,能打开的数目水涨船高。"too many open files" 并不可怕,改对限制即可。
永久生效方案一:limits.conf(登录用户)
临时修改的问题是重启/重登即失效。对登录型用户(ssh 登录的用户、su 切换的用户),永久配置在 /etc/security/limits.conf,由 PAM 模块 pam_limits.so 在会话建立时读入(Ubuntu 里 /etc/pam.d/common-session 已默认加载)。
格式为一行四列:域(domain) 类型(type) 项目(item) 值(value):
- domain:用户名、
@组名、或通配符*(匹配所有用户);root需要显式写 root 才生效; - type:
soft、hard或-(soft 和 hard 同时); - item:
nofile(打开文件数)、nproc(进程数)、fsize、cpu、stack等,与 ulimit 的项一一对应; - value:数值。
为演示给一个用户 testu 配置:打开文件数 soft=4096/hard=8192,进程数 soft=100/hard=200:
# /etc/security/limits.conf
testu soft nofile 4096
testu hard nofile 8192
testu soft nproc 100
testu hard nproc 200
另外 /etc/security/limits.d/*.conf 里的文件会按字母序覆盖 limits.conf 的同名条目,配置分发时留意别被覆盖。配置后重新登录(ssh 或 su)立即生效,无需重启。登录后验证:
ulimit -Sn # 4096
ulimit -Hn # 8192
ulimit -u # 100普通用户此时再想往高调就撞 hard 了:
ulimit -n 9000
# bash: ulimit: open files: cannot modify limit: Operation not permitted永久生效方案二:systemd 服务的 LimitNOFILE
limits.conf 只对登录会话生效,管不到 systemd 服务(systemd 管理的守护进程不走 PAM 登录)。这也是很多人"改了 limits.conf 但 nginx 没变化"的原因——服务配额要看 systemd 单位文件。
在服务的 .service 单位文件 [Service] 段里,用 LimitNOFILE、LimitNPROC 等指令直接声明:
# /etc/systemd/system/limitdemo.service
[Unit]
Description=ulimit demo service
[Service]
ExecStart=/bin/bash -c "sleep 300"
LimitNOFILE=2048
LimitNPROC=512
Type=simple
[Install]
WantedBy=multi-user.target写好后 daemon-reload 并启动,实际进程的限制会直接按单位文件生效:
systemctl daemon-reload
systemctl start limitdemo
systemctl show limitdemo -p LimitNOFILE -p LimitNPROC
cat /proc/$(systemctl show limitdemo -p MainPID --value)/limits
两行 limitdemo 的关键字段都变成了 2048 / 512,而不是默认的 1024。常见服务的实际情况:Nginx/MySQL 等对并发要求高的服务,LimitNOFILE 常配到 65535 甚至更高。
查看运行中进程的真实限制
改完配置后,如何确认一个正在运行的进程到底带着什么限制?直接读 /proc 或看进程的资源信息:
cat /proc/<PID>/limits
prlimit --pid <PID>/proc/<PID>/limits 会列出该进程全部资源的 soft/hard 实际值,是排查"为什么我的配置没生效"的第一手证据。比如 Nginx worker 进程的 Max open files 是多少,一眼便知。
常用速查表
| 资源 | 参数 | limits.conf item | 典型默认 | 常见调整 |
|---|---|---|---|---|
| 打开文件数 | -n | nofile | 1024 | 高并发服务常调 65535+ |
| 进程数 | -u | nproc | 机器配置决定 | Docker/多进程常调高 |
| 单个文件大小 | -f | fsize | unlimited | 限制日志/回传文件 |
| CPU 时间(秒) | -t | cpu | unlimited | 防死循环 |
| 栈大小 | -s | stack | 8192 | 深递归易崩时调大 |
| 虚拟内存 | -v | as | unlimited | 限制单进程内存 |
| core 文件 | -c | core | 0 | 调试崩溃开 unlimited |
排查思路小结
一次典型的"too many open files"处理流程:先用 ulimit -n 看当前值,再用 cat /proc/<PID>/limits 确认目标进程的实际限制;若是登录型应用(命令行启动、脚本启动)改 /etc/security/limits.conf;若是 systemd 服务(nginx/redis/mysql 等)改单位文件的 LimitNOFILE 并 systemctl daemon-reload + 重启服务。改完记得重新验证,别急着把值调成天文数字——软硬限制、内核 fs.nr_open、系统的 file-max 三层叠加,最终生效的是它们共同约束下的那个值。
评论 (0)
暂无评论,快来抢沙发吧!