暗色模式

Linux ulimit 资源限制:从打开文件数到系统级配额调优

技术教程
2026-08-27
9
0
本文要点
  • 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>/limitsprlimit,能直接看到每个运行中进程生效的软硬限制

为什么需要 ulimit:系统中的"资源配额"

很多时候我们会在运维中碰到这样的报错:Nginx 日志里刷 too many open files,或者某个程序启动时提示 Resource temporarily unavailablecannot allocate memory。它们背后往往都是同一类原因——进程触碰到了系统给的资源上限

Linux 为了防止单个进程(或单个用户)把机器资源耗尽,为每个进程都挂了一组资源限制(resource limit):最多能打开多少个文件、最多占多少 CPU 时间、最多用多少内存、最多派生多少进程……这些限制就像一个"配额",我们日常排查高并发、崩溃、OOM 问题时最先该翻的就是它。

而操作和查看这些限制的命令,就是 ulimit。它是 shell 内建命令,直接在当前 shell 进程上生效,你启动的每个子进程(包括后台服务脚本文本)都会继承这些值。

Ubuntu 24.04 上无需安装,任何 shell 都自带。先看看系统当前默认限制:

ulimit -a

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 时默认改 soft

root 也不能为所欲为: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

文件描述符耗尽:errno=24 EMFILE

可以看到限制为 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 才生效;
  • typesofthard-(soft 和 hard 同时);
  • itemnofile(打开文件数)、nproc(进程数)、fsizecpustack 等,与 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

limits.conf 演示配置

另外 /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] 段里,用 LimitNOFILELimitNPROC 等指令直接声明:

# /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

systemd 服务的 LimitNOFILE 生效

两行 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典型默认常见调整
打开文件数-nnofile1024高并发服务常调 65535+
进程数-unproc机器配置决定Docker/多进程常调高
单个文件大小-ffsizeunlimited限制日志/回传文件
CPU 时间(秒)-tcpuunlimited防死循环
栈大小-sstack8192深递归易崩时调大
虚拟内存-vasunlimited限制单进程内存
core 文件-ccore0调试崩溃开 unlimited

排查思路小结

一次典型的"too many open files"处理流程:先用 ulimit -n 看当前值,再用 cat /proc/<PID>/limits 确认目标进程的实际限制;若是登录型应用(命令行启动、脚本启动)改 /etc/security/limits.conf;若是 systemd 服务(nginx/redis/mysql 等)改单位文件的 LimitNOFILEsystemctl daemon-reload + 重启服务。改完记得重新验证,别急着把值调成天文数字——软硬限制、内核 fs.nr_open、系统的 file-max 三层叠加,最终生效的是它们共同约束下的那个值。

发表评论

暂无评论,快来抢沙发吧!