Too many open files 改了 ulimit 还是报:一次文件描述符上限的复盘

一个 systemd 管的 Java 服务在流量高峰刷 Too many open files,登服务器 ulimit -n 查到 1024 改成 65535,重启服务还是报,又改了 /etc/security/limits.conf 加 nofile 65535 重启还是报,能改的地方全改了服务死守 1024 不放。排查梳理:文件描述符上限不是一个全局开关它是每个进程各自的一份属性进程启动时从父进程继承,ulimit -n 65535 只改了我当前这个 shell 进程那一份它只对这个 shell 和我从这个 shell 直接启动的子进程生效,那个 java 服务不是我从 shell 启动的它的父进程是 systemd 继承的是 systemd 给它的限制跟我的 shell 毫无关系;ulimit 有软限制和硬限制软限制是实际生效的值硬限制是软限制能调到的天花板普通用户只能在硬限制内调软限制抬硬限制要 root;ulimit 命令两个致命局限改的是内存里 shell 进程的属性不写文件退出登录就失效,管不到不是这个 shell 启动的进程;/etc/security/limits.conf 不是内核读的也不是系统全局自动生效的它由 PAM 模块 pam_limits.so 在用户登录建立 PAM 会话那一刻读取应用,只对 SSH su 等经过 PAM 登录的会话生效对 systemd 启动的守护进程完全无效因为它们根本不走 PAM 登录;真凶 systemd 管的服务资源上限由 service 单元文件里的 LimitNOFILE 决定没写就用 /etc/systemd/system.conf 的 DefaultLimitNOFILE,正确做法是 systemctl edit 服务名加 LimitNOFILE 再 daemon-reload 加 restart 服务新限制才被继承;排查的唯一真相是 /proc/PID/limits 内核在这里如实记录该进程实际受的每项限制别看你以为的限制,应用层如 Nginx worker_rlimit_nofile 可能还有自己的限制两层都要对上。正确做法是一报 Too many open files 先 cat /proc/出问题进程PID/limits 看真实上限再判断该改哪,以及一套文件描述符上限排查纪律。

2023 年,一次"我把 ulimit 明明改成了 65535,服务还是死活报 Too many open files"的事故,把我对"设置一个限制"这件事的理解,从头到尾翻新了一遍。那台服务器上跑着一个 Java 服务,平时好好的,可一到流量高峰,日志里就开始刷 java.io.IOException: Too many open files,连接处理一片混乱。我一看就知道:文件描述符不够了,把上限调大就行。我登上服务器,敲 ulimit -n,显示 1024——果然太小。我立刻 ulimit -n 65535,再 ulimit -n 确认,显示 65535,改好了。我重启服务,满心以为这事结了。可流量一上来,日志里 Too many open files——【一个字没少】,照刷。我懵了,又去查,网上说要改 /etc/security/limits.conf 才持久。我照改,加了 * - nofile 65535,重启服务——还是报。我盯着屏幕,脑子里全是问号:我 ulimit -n 查出来明明白白是 65535,我配置文件也改了,我能改的地方【全都改了】,可那个服务,就像活在另一个世界里,死守着它那个 1024 不放。我改的那个"限制",和那个服务身上真正生效的"限制",到底是不是【同一个东西】?如果是,它为什么不听我的?如果不是——那我这半天,改的究竟是【谁】的限制?这件事逼着我把软硬限制、limits.conf 的作用边界、systemd 服务的资源限制,还有"我设的限制"和"进程实际受的限制"的天壤之别,彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,systemd 管理的 Java 服务,流量高峰报错
事故现象:
- 高峰期日志刷:java.io.IOException: Too many open files
- ulimit -n 查到是 1024,改成 65535
- ★ 重启服务,还是报 Too many open files
- 改了 /etc/security/limits.conf,重启服务,★ 依然报

现场排查:
# 1. ★ 我在 shell 里查、改 ulimit
$ ulimit -n
1024
$ ulimit -n 65535
$ ulimit -n
65535                                  # ★ shell 里确实改成了 65535

# 2. ★ 但服务还在报。看服务进程【实际】的限制
$ ps -ef | grep java                   # 拿到 java 进程 PID,假设 8888
$ cat /proc/8888/limits | grep 'open files'
Max open files   1024    1024    files  # ★★ 进程实际上限,还是 1024!

# 3. ★ 我改了 limits.conf
$ grep nofile /etc/security/limits.conf
* - nofile 65535                        # ★ 我加的这行

# 4. ★ 关键:这个 java 服务,是谁启动的
$ systemctl status myapp
   ● myapp.service                      # ★★ 是 systemd 管的服务!
$ cat /proc/8888/limits | grep 'open files'
Max open files   1024    1024    files  # ★ limits.conf 改了也没用

# 5. ★ 看 systemd 给服务设的限制
$ systemctl show myapp -p LimitNOFILE
LimitNOFILE=1024                        # ★★ 真凶在这!systemd 设的

根因(后来想清楚的):
1. ★ "文件描述符上限"不是一个全局开关。它是【每个
   进程各自】的一份属性,进程启动时从父进程【继承】。
2. ★ 我 ulimit -n 65535 —— 只改了【我这个 shell
   进程】的那一份。它只对"我这个 shell,以及我从
   这个 shell 里直接启动的子进程"生效。
3. ★ 那个 java 服务,不是我从 shell 启动的。它是
   systemd 启动的 —— 它的父进程是 systemd,继承的
   是【systemd 给它的限制】,跟我的 shell 毫无关系。
4. ★ /etc/security/limits.conf 是 PAM 的配置。它只
   在【用户登录(走 PAM)】时生效。systemd 启动
   守护进程,【根本不走 PAM】-> limits.conf 对它无效。
5. ★ systemd 管的服务,它的资源上限,由 service
   单元文件里的 LimitNOFILE= 决定(没写就用全局
   默认 DefaultLimitNOFILE)。我没改这个 -> 服务
   一直是 systemd 给的那个 1024。
6. 真相:我改了三个地方,但【没有一个】是那个
   服务真正继承限制的来源。我对着空气调了半天。
不是限制改不动,是限制根本没有"全局"这回事,
我改的限制,和那个进程继承的限制,不是一份。

修复 1:ulimit 是什么——每个进程各自的资源上限

# === ★ 先搞清楚 ulimit 到底在限制什么 ===

# === ★ ulimit 是"每个进程"的资源上限 ===
# 操作系统怕单个进程失控耗尽资源,给【每个进程】都
#   套了一组上限:能开多少文件、能用多大内存栈、
#   能开多少进程……这组上限,就是 ulimit(resource
#   limit)。
$ ulimit -a                          # 看当前 shell 的所有限制
# open files (-n) 1024               # ★ 最多能开的文件描述符数
# max user processes (-u) 4096       # 最多能开的进程数
# stack size (-s) 8192               # 栈大小 ...

# === ★ 本文的主角:open files(-n)===
# ★ Linux 里,"打开的文件"不只是磁盘文件 —— 每一个
#   网络连接(socket)、每一个管道,都占一个【文件
#   描述符(fd)】。
# ★ -n 限的就是"一个进程最多能同时持有多少个 fd"。
#   高并发服务,连接多 -> fd 多 -> 容易撞上这个上限
#   -> 报 Too many open files。
$ ulimit -n                          # 只看 open files 这一项

# === ★ 关键:每个限制都有"软"和"硬"两个值 ===
$ ulimit -Sn                         # 软限制(soft):实际生效的值
1024
$ ulimit -Hn                         # 硬限制(hard):软限制的天花板
4096
# ★ 软限制(soft):★ 真正起作用、进程实际受的那个值。
# ★ 硬限制(hard):软限制能调到的【最大值】。
# ★ 规则:普通用户,可以把软限制【在 0 到硬限制
#   之间】随便调;但【不能把硬限制调高】(只能调低)。
#   想突破硬限制,得 root。

# === ★ 一个常见的"改不动"陷阱 ===
$ ulimit -n 65535                    # 普通用户这么改
bash: ulimit: open files: cannot modify limit: ...
# ★ 报这个错,是因为 65535 【超过了硬限制】。普通
#   用户够不到那么高。要么先(以 root)抬高硬限制,
#   要么这个值本就由 root 在配置里设好。

# === 认知 ===
# ★ ulimit 是操作系统给【每个进程】套的资源上限,
#   open files(-n)限的是一个进程能同时持有多少
#   文件描述符,而每个网络连接也占一个 fd,所以高
#   并发服务容易撞上。每个限制有软、硬两个值:软
#   限制是实际生效的,硬限制是软限制的天花板,普通
#   用户只能在硬限制以内调软限制,抬硬限制要 root。

修复 2:ulimit 命令为什么"改了不生效"

# === ★ 把"ulimit -n 改了却没用"讲透 ===

# === ★ ulimit 命令,只改"当前这个 shell" ===
# ★ ulimit -n 65535 这条命令,改的是【你当前这个
#   shell 进程】的限制。它的作用范围,只有两个:
#  ① 你当前这个 shell 自己;
#  ② ★ 你【从这个 shell 里、往后】启动的子进程
#     —— 因为子进程会【继承】父进程的限制。

# === ★ 所以它有两个致命的局限 ===
# ★ 局限一:【关掉 shell 就没了】。ulimit 改的是
#   内存里这个 shell 进程的属性,不写任何文件。你
#   一退出登录,下次再进来,又是默认值。它【不持久】。
# ★ 局限二:★ 它【管不到】"不是这个 shell 启动的"
#   进程。一个早就在跑的服务、一个 systemd 启动的
#   服务 —— 它们的限制,在它们【出生时】就从【各自
#   的父进程】继承定了,和你这个 shell 八竿子打不着。

# === ★ 本文事故的第一个错,就在这 ===
# ★ 我 ulimit -n 65535,改的是【我登录的这个 shell】。
# ★ 那个 java 服务,是 systemd 早就启动好的,它的
#   父进程是 systemd。我那个 shell,和那个服务,是
#   两条【毫不相干的进程谱系】。
# ★ 我在我的 shell 里把限制喊到 65535,那个服务
#   听都听不见 —— 我俩根本不在一条继承链上。

# === ★ 顺带:限制只能在启动时继承,不能"事后改" ===
# ★ 一个进程【已经在跑】了,你【没有办法】从外面
#   把它的 fd 软限制改大(prlimit 能改,但需谨慎,
#   且很多程序启动时已按旧值分配好了内部结构)。
# ★ 所以正确姿势永远是:【先把限制配对,再启动 /
#   重启那个进程】,让它出生时就继承到大的限制。

# === ★ 验证"继承":从改过的 shell 启子进程 ===
$ ulimit -n
1024
$ ulimit -n 4096                     # 改当前 shell(不超硬限制)
$ bash -c 'ulimit -n'                # ★ 启个子 shell 看它继承到什么
4096                                 # ★ 子进程继承了 4096
# ★ 这印证了:限制是【父传子】继承的。改了爹,只有
#   爹和"改完之后生的儿子"受影响。

# === 认知 ===
# ★ ulimit 命令只改【当前这个 shell 进程】的限制,
#   作用范围仅限这个 shell 和它之后启动的子进程
#   (子进程继承父进程的限制)。它两个致命局限:
#   不写文件、退出登录就失效;管不到不是这个 shell
#   启动的进程 —— 服务的限制在它出生时就从自己的
#   父进程继承定了。所以要先配好限制再启动进程。

修复 3:limits.conf——持久化,但它只管"PAM 登录会话"

# === ★ limits.conf 能持久化,但有个关键的作用边界 ===

# === ★ /etc/security/limits.conf 是什么 ===
# ★ ulimit 命令不持久,那持久的配置在哪?——
#   /etc/security/limits.conf(及 limits.d/ 目录)。
$ vi /etc/security/limits.conf
# 格式:<对谁>  <soft/hard>  <限制项>  <值>
*        soft  nofile  65535         # 所有用户,软限制
*        hard  nofile  65535         # 所有用户,硬限制
deploy   soft  nofile  100000        # 只对 deploy 用户
root     hard  nofile  100000        # 对 root
# ★ nofile 就是 open files。soft/hard 对应软硬限制。
# ★ 两行都要写:hard 是天花板,soft 才是实际值。

# === ★ 致命的关键:limits.conf 由【谁】来执行 ===
# ★ limits.conf 这个文件,【不是内核读的】,也不是
#   "系统全局"自动生效的。它是 ★ PAM 的一个模块
#   (pam_limits.so)在读、在应用的。
$ grep pam_limits /etc/pam.d/*       # 看哪些场景挂了这个模块
/etc/pam.d/login: ... pam_limits.so
/etc/pam.d/sshd:  ... pam_limits.so
# ★ PAM 是"用户认证 / 会话"框架。pam_limits.so 只
#   在【一次 PAM 会话建立时】被触发 —— 也就是
#   【一个用户登录的那一刻】。

# === ★ 所以 limits.conf 的作用边界是 ===
# ★ 它【只对"经过 PAM 登录"的会话】生效:
#  - 你 SSH 登录 -> 走 pam_limits.so -> 生效 ✓
#  - 你 su / 切换用户登录 -> 生效 ✓
#  - 你在登录后的 shell 里启动的程序 -> 继承,生效 ✓
# ★ 它【对这些,完全无效】:
#  - ★ systemd 启动的守护进程 —— 【不走 PAM 登录】!
#  - 开机时随系统拉起的服务
# ★ 这就是本文第二个错:我改了 limits.conf,可那个
#   java 服务是 systemd 拉起的,根本没经过 PAM 登录
#   这道门 -> limits.conf 那行字,它一个标点都没读到。

# === ★ 怎么确认 limits.conf 对登录会话生效了 ===
# ★ 改完 limits.conf,必须【重新登录】(退出 SSH
#   再连)才生效 —— 因为它在"登录那一刻"才被应用。
$ exit                               # 退出,重新 SSH 登录
$ ulimit -n                          # 新会话里看,应是新值
65535

# === 认知 ===
# ★ /etc/security/limits.conf 能持久化资源限制,但它
#   不是内核读的、不是系统全局自动生效的 —— 它由
#   PAM 模块 pam_limits.so 在【用户登录建立 PAM 会话
#   那一刻】读取应用。所以它只对 SSH/su 等经过 PAM
#   登录的会话生效,对 systemd 启动的守护进程【完全
#   无效】(它们不走 PAM 登录)。改完要重新登录才生效。

修复 4:真凶——systemd 服务的限制,在 unit 文件里

# === ★ systemd 管的服务,限制到底由谁定 ===

# === ★ systemd 服务,资源限制是 systemd 自己给的 ===
# ★ 一个由 systemd 启动的服务,它【既不读你的
#   ulimit,也不读 limits.conf】。它的资源上限,
#   完全由 ★ systemd 的配置决定。
# ★ 看 systemd 实际给某服务设的值:
$ systemctl show myapp -p LimitNOFILE
LimitNOFILE=1024                     # ★ 这才是那个 java 进程的真上限

# === ★ 两个层级:服务单独设 / 全局默认 ===
# ★ 层级一(优先):service 单元文件里的 LimitNOFILE=
#   —— 只管这一个服务。
# ★ 层级二(兜底):/etc/systemd/system.conf 里的
#   DefaultLimitNOFILE= —— 所有没单独设的服务,
#   都用这个全局默认。
$ grep DefaultLimitNOFILE /etc/systemd/system.conf

# === ★ 正确做法:给服务单独设 LimitNOFILE ===
# ★ 不要直接手改 /usr/lib/systemd 里的原始单元文件
#   (升级会被覆盖)。用 systemctl edit 加覆盖配置:
$ systemctl edit myapp
# 在打开的编辑器里写:
[Service]
LimitNOFILE=65535                    # ★ 给 myapp 单独设 fd 上限
# ★ 保存后,它会生成 /etc/systemd/system/myapp.service.d/
#   override.conf,不污染原文件,升级也不丢。

# === ★ 改完必须 daemon-reload + 重启服务 ===
$ systemctl daemon-reload            # ★ 让 systemd 重新读配置
$ systemctl restart myapp            # ★ 重启服务,新限制才被继承
# ★ 再次强调:限制是【启动时继承】的。不重启服务,
#   改 LimitNOFILE 对【正在跑的】那个进程毫无作用。

# === ★ 验证:回到唯一的真相源 ===
$ ps -ef | grep myapp                # 拿新 PID
$ cat /proc/<新PID>/limits | grep 'open files'
Max open files   65535   65535       # ★★ 这下真的是 65535 了

# === ★ 想一次性抬高所有服务的默认值 ===
$ vi /etc/systemd/system.conf
DefaultLimitNOFILE=65535             # ★ 改全局默认
$ systemctl daemon-reexec            # 让 systemd 重新执行自身
# ★ 之后【新启动】的服务都用这个默认。但已在跑的
#   要重启才生效。

# === 认知 ===
# ★ systemd 管的服务既不读 ulimit 也不读 limits.conf,
#   它的资源上限由 systemd 决定:优先看 service 单元
#   文件里的 LimitNOFILE=,没写则用 /etc/systemd/
#   system.conf 里的 DefaultLimitNOFILE=。正确做法是
#   systemctl edit 服务名 加 [Service] LimitNOFILE=,
#   再 daemon-reload + restart 服务,新限制才被继承。

修复 5:唯一的真相——/proc/PID/limits

# === ★ 别再"猜"限制,去看那个进程真正的限制 ===

# === ★ 排查 ulimit 问题,最大的弯路是"看错地方" ===
# ★ 本文我栽的根,是我一直在看【我以为的限制】:
#   我在 shell 里 ulimit -n,看到 65535,我就以为
#   "限制是 65535"。可那是【我 shell 的限制】,不是
#   【那个出问题的服务进程的限制】。
# ★ 我对着一个不相干的数字,自我感觉良好了半天。

# === ★ 唯一可信的真相:/proc/PID/limits ===
# ★ 每个正在运行的进程,内核都在 /proc/<它的PID>/limits
#   里,如实记录着【它自己实际受的】每一项限制。
# ★ 这个文件,不骗人。它显示什么,这个进程就真的受
#   什么限制 —— 不管你在别处怎么设、怎么以为。
$ ps -ef | grep myapp                # ① 先拿到出问题进程的 PID
$ cat /proc/8888/limits              # ② 看它【真正】的全部限制
Limit              Soft Limit  Hard Limit  Units
Max open files     65535       65535       files   # ★ 它真受的 fd 上限
Max processes      4096        4096        processes
...

# === ★ 排查纪律:先看 /proc/PID/limits,再谈改哪 ===
# ★ 一报 Too many open files,第一件事【不是】去
#   ulimit -n,而是:
$ cat /proc/$(pgrep -n -f myapp)/limits | grep 'open files'
# ★ 看到的如果是 1024 —— 那不管你 ulimit 显示啥、
#   limits.conf 写了啥,这个进程【就是】只有 1024。
#   接下来才去判断:它是 systemd 起的(改
#   LimitNOFILE)?还是登录会话起的(改 limits.conf
#   并重新登录)?

# === ★ 配套:看进程现在用了多少 fd ===
$ ls /proc/8888/fd | wc -l           # 这个进程当前打开了多少 fd
$ lsof -p 8888 | wc -l               # 另一种数法
# ★ 把"当前用量"和"/proc/limits 的上限"一比,就
#   知道是真撞上限了,还是 fd 泄漏(只涨不降)。

# === ★ 还有一层:应用自己可能也有限制 ===
# ★ 有些服务,在 OS 的 ulimit 之内,自己【又设了
#   一道更小的限制】。比如 Nginx:
$ grep worker_rlimit_nofile /etc/nginx/nginx.conf
worker_rlimit_nofile 65535;          # ★ Nginx 自己的 fd 上限
# ★ 如果 OS 给了 65535,但 Nginx 配置里只写了 1024,
#   那 Nginx 进程实际还是受 1024 限制。OS 层、应用层,
#   两层都要对上。

# === 认知 ===
# ★ 排查 ulimit 问题别看"你以为的限制"(shell 里
#   ulimit -n 是 shell 自己的),唯一可信的真相是
#   /proc/<出问题进程PID>/limits —— 内核在这里如实
#   记录该进程实际受的每项限制。一报 Too many open
#   files 先看这个文件,再判断该改哪。注意应用层
#   (如 Nginx worker_rlimit_nofile)可能还有自己的限制。

修复 6:文件描述符上限排查纪律

# === 这次事故暴露的认知盲区,定几条纪律 ===

# === 1. ★ ulimit 是每个进程各自的限制,启动时从父进程继承,没有"全局"这回事 ===

# === 2. ★ 排查第一步永远是 cat /proc/出问题进程PID/limits,看它真正的限制 ===
$ cat /proc/$(pgrep -n -f 服务名)/limits | grep 'open files'

# === 3. ★ ulimit 命令只改当前 shell,不持久,管不到别的进程谱系 ===

# === 4. ★ limits.conf 只对经过 PAM 登录的会话生效,对 systemd 服务无效 ===

# === 5. ★ systemd 服务的限制看 LimitNOFILE,用 systemctl edit 改 ===
$ systemctl edit 服务名     # 加 [Service] / LimitNOFILE=65535

# === 6. 改完 systemd 配置必须 daemon-reload,改完限制必须重启服务才继承 ===

# === 7. 全局默认在 /etc/systemd/system.conf 的 DefaultLimitNOFILE ===

# === 8. ★ 限制只能在进程启动时继承,正在跑的进程改不了,要先配好再重启 ===

# === 9. 应用自己可能还有限制,如 Nginx worker_rlimit_nofile,两层都要对 ===

# === 10. 排查 Too many open files 的步骤链 ===
$ cat /proc/PID/limits | grep 'open files'   # ① 进程真实上限
$ ls /proc/PID/fd | wc -l                    # ② 当前用了多少 fd
$ systemctl status PID所属服务               # ③ 它是不是 systemd 起的
# 是 systemd 起的 -> 改 LimitNOFILE;是登录会话起的
# -> 改 limits.conf 并重新登录。改完都要重启进程。

命令速查

需求                        命令
=============================================================
看当前 shell 所有限制       ulimit -a
看/改 open files 软限制     ulimit -Sn / ulimit -n 65535
看 open files 硬限制        ulimit -Hn
★ 看某进程真实的限制        cat /proc/PID/limits
看某进程当前打开多少 fd     ls /proc/PID/fd | wc -l
看 systemd 给服务的限制     systemctl show 服务 -p LimitNOFILE
给 systemd 服务改限制       systemctl edit 服务名
改完 systemd 配置           systemctl daemon-reload
看 systemd 全局默认         grep DefaultLimitNOFILE /etc/systemd/system.conf
持久化登录会话的限制        编辑 /etc/security/limits.conf

口诀:ulimit 是每个进程各自的限制 启动时从父进程继承 没有全局开关
      systemd 服务不读 limits.conf 要改 LimitNOFILE,真相只看 /proc/PID/limits

避坑清单

  1. ulimit 是每个进程各自的资源上限,进程启动时从父进程继承,根本没有"系统全局"这个东西
  2. 排查 Too many open files 第一步永远是 cat /proc/出问题进程PID/limits,看它真正受的限制
  3. ulimit -n 命令只改当前这个 shell 进程,不写文件不持久,也管不到别的进程谱系
  4. 软限制是实际生效的值,硬限制是软限制的天花板,普通用户只能在硬限制内调软限制
  5. limits.conf 由 PAM 模块在用户登录那一刻读取,只对经过 PAM 登录的会话生效
  6. systemd 启动的守护进程不走 PAM 登录,改了 limits.conf 对它一个标点都不生效
  7. systemd 服务的限制由 LimitNOFILE 决定,用 systemctl edit 加覆盖配置而不是改原单元文件
  8. 改完 systemd 配置要 daemon-reload,改完限制要重启服务,正在跑的进程改不了限制
  9. 全局默认在 /etc/systemd/system.conf 的 DefaultLimitNOFILE,改完新启动的服务才用
  10. 应用自己可能还设了更小的限制如 Nginx 的 worker_rlimit_nofile,操作系统层和应用层都要对上

总结

这次"ulimit 改了又改、服务岿然不动"的事故,纠正了我一个关于"设置"的、最根深蒂固的错觉。在我过去的脑子里,设置一个系统的限制,就像拧一个【墙上的总开关】。我以为机房某处,有一个叫"文件描述符上限"的旋钮,我把它拧到 65535,那么这台机器上的【所有进程】,就都跟着享受 65535。所以我登上服务器,ulimit -n 65535 一敲,ulimit -n 一查,显示 65535——在那一刻,我确信我已经"拧动了那个总开关"。我甚至没想过去问:我刚才到底拧的是【什么】?现场用一个冷冰冰的 /proc/8888/limits 文件,把我那个"总开关"的幻觉,击得粉碎。它告诉我:那个出问题的 java 进程,它身上的限制,清清楚楚还是 1024。我那个"已经拧到 65535"的操作,对它【完全没有发生过】。我这才被逼着去理解一件我从没正视过的事:这个系统里,根本【就没有】一个叫"文件描述符上限"的总开关。这个限制,它不是挂在"系统"这面墙上的,它是【长在每一个进程身上】的——每个进程,从它出生的那一刻起,就从它的【父亲】手里,继承了一份【属于它自己的】限制副本,然后揣着这份副本过完一生。我 ulimit -n 65535,我没有拧动任何总开关,我只是改写了【我登录的这个 shell 进程】身上揣的那一份。而那个 java 服务,它的父亲是 systemd,不是我的 shell;它继承的那份限制,来自 systemd,和我那个 shell 上的副本,是两份毫不相干、永不相通的纸。我朝着我手里那份纸大喊大叫,而那个服务,在另一条家谱上,安静地守着它从 systemd 那里继承来的、另一份纸。复盘到根上我才看清,我错的不是某条命令,而是我脑子里那个"系统是一个有中央控制台的整体"的模型。这个系统,它不是一栋有总闸的大楼,它更像一个【家族】——每一项属性,资源限制、环境变量、当前目录、打开的文件,都不是挂在"家族"名下的公共财产,而是每个成员在【出生时,从父母手里继承】的私产。你想改变一个成员的私产,你必须找到【他】,或者找到他【还没出生的父母】——你冲着家族的某个【远房亲戚】(我的 shell)调整,对他(那个服务)毫无意义,因为属性根本不在家族层面流动,它只沿着【父子继承】这一条线传递。这次最大的收获,是我学会了在动手"设置"任何东西之前,先停下来,问一个我以前从不问的问题:我现在要改的这个东西,它的作用域,究竟是【谁】?是我眼前这个进程,还是它将来要生的孩子,还是某个登录会话,还是 systemd 的某个角落?我面对的那个出问题的对象,它真正继承属性的【那条血脉】,源头又在哪里?我不能再凭"我改了一个看起来对的地方"就心安——因为一个设置,只有落在那条正确的继承链的【上游】,才会流到下游那个我真正想影响的进程身上;落错了链,它改得再大、我查得再确凿,对那个进程而言,都【等于什么都没发生】。验证一个限制是否真的生效,也永远不能问"我设了吗",只能去问那个进程自己——去读它身上那份 /proc/PID/limits,因为那张纸上写的,才是它这一生真正背负的东西,而我的"我以为",一个字都不算数。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

fstab 写错一行服务器再也起不来:一次开机救援模式的复盘

2026-5-21 0:58:02

Linux教程

脚本手动跑正常放进 crontab 就 command not found:一次环境变量加载顺序的复盘

2026-5-21 1:07:51

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索