1. 项目概述:为什么你改了 limits.conf 却发现 ulimit 没变?
“ulimit 修改无效”——这是 Linux 系统运维、后端开发、数据库调优、高并发服务部署中,最常被低估、却最让人抓狂的“幽灵问题”。我第一次遇到是在给一个 Kafka 集群节点调优时,明明在/etc/security/limits.conf里把nofile软硬限制都设成了65536,重启服务后ulimit -n一查,还是1024。当时翻了三遍文档,重试了七次,甚至怀疑是不是系统被谁动过内核参数……最后发现,问题根本不在配置文件本身,而在于谁读了它、什么时候读、以什么方式读、读完之后又是否被覆盖。
这根本不是“改个配置就生效”的简单操作,而是一场涉及 PAM(Pluggable Authentication Modules)认证链、会话生命周期、systemd 服务管理模型、shell 启动流程的多层协同。核心关键词ulimit、limits.conf、soft、hard、nofile,每一个都不是孤立概念:soft是当前会话可立即生效的软性上限,用户自己能临时调高(只要不超过 hard);hard是管理员设定的天花板,普通用户无法突破;nofile只是其中最常被卡住的一个——它直接决定单进程能打开多少文件描述符,而现代服务(Nginx、Redis、Elasticsearch、Java 应用)动辄需要上万连接,不调这个,服务起得来也跑不稳。
适合谁看?如果你是:
- 正在部署 Java/Go/Node.js 微服务,遇到
Too many open files报错; - 运维 MySQL/PostgreSQL,发现连接数上不去或慢查询堆积;
- 在容器外直接跑 Kafka/ZooKeeper,日志里反复出现
EMFILE错误; - 写 systemd service 文件时,发现
LimitNOFILE=设了但ulimit -n不认账; - 或者只是
ulimit -n 65536手动执行后,新开一个终端又掉回 1024……
那你不是配置写错了,而是没摸清 Linux 资源限制的“生效地图”。这篇文章不讲抽象原理,只讲我在生产环境踩过坑、验证过、能抄作业的完整路径——从临时调试到永久生效,从 shell 会话到 systemd 服务,从普通用户到 root,全部拆解清楚。
2. ulimit 修改与 limits.conf 生效机制深度拆解
2.1 ulimit 命令的本质:它只作用于当前 shell 进程及其子进程
ulimit是一个 shell 内置命令(bash/zsh),它的行为非常“本地化”:
ulimit -n 65536:仅将当前 shell 的RLIMIT_NOFILE软限制设为 65536;- 这个值不会写入任何配置文件,也不会影响其他已存在的终端、其他用户的会话,甚至不会影响你当前终端里新启动的另一个 bash 子进程(除非显式继承);
- 更关键的是:它无法突破当前会话的 hard limit。如果 hard limit 是 4096,你
ulimit -n 65536会直接报错bash: ulimit: open files: cannot modify limit: Operation not permitted——这就是热搜词里“ulimit: core file size:无法修改 limit 值: 不允许的操作”的典型场景。
提示:
ulimit -Hn查看当前 hard limit,ulimit -Sn查看 soft limit。两者必须同时检查,不能只看 soft。
为什么会有 hard limit?这是 Linux 内核的安全兜底机制。内核通过setrlimit()系统调用设置资源限制,而RLIMIT_NOFILE的 hard limit 默认由 PAM 模块在用户登录时从/etc/security/limits.conf加载。一旦加载完成,普通用户进程无权调高 hard limit(只有 root 或 CAP_SYS_RESOURCE 权限进程可以)。所以,当你看到“Operation not permitted”,第一反应不应该是“配置没生效”,而应是“我的登录会话压根没加载 limits.conf”。
2.2 limits.conf 的加载时机与 PAM 认证链依赖
/etc/security/limits.conf不是一个“开机自读”的全局配置。它只在用户通过 PAM 认证登录时,由pam_limits.so模块加载。这意味着:
- SSH 登录(
sshd服务)、图形界面登录(GDM/KDM)、控制台登录(getty)——这些走 PAM 流程的入口,才会触发limits.conf解析; su - user:会重新触发 PAM,因此能加载目标用户的 limits;su user(不带-):不重新登录,不触发 PAM,完全不读 limits.conf;sudo -i:等价于su -,会加载;sudo command:以 root 权限执行,但环境仍是调用者的,不加载目标用户 limits;- systemd 服务默认不走 PAM 登录流程:这是绝大多数人永久修改失败的根源!
systemd启动的服务(如nginx.service)由systemd直接 fork,绕过了login和pam_limits.so,因此/etc/security/limits.conf对它完全无效。
PAM 配置文件位置:/etc/pam.d/下的对应服务文件。例如 SSH 登录依赖/etc/pam.d/sshd,其内容通常包含:
session required pam_limits.so这一行就是limits.conf生效的“开关”。如果某服务的 PAM 配置里没有这行,或者写成了optional,那limits.conf就是摆设。我曾在线上 Redis 服务器发现sshd的 PAM 文件被误删了这行,导致所有 SSH 登录用户都无法提升 nofile,排查了两天才定位到。
2.3 soft 与 hard 的关系:不是并列,而是父子约束
很多教程把 soft/hard 描述成“两个独立数值”,这是严重误导。它们的关系是严格的父子约束:
- soft limit ≤ hard limit,永远成立;
- 用户可自行调高 soft limit(
ulimit -n 8192),但不能超过 hard limit; - 用户永远无法调高 hard limit(
ulimit -Hn 8192会失败),除非是 root; - root 可以任意设置两者(
ulimit -Hn 100000; ulimit -Sn 100000); - 当 soft = hard 时,该限制被“锁定”,用户彻底失去调整空间。
实际应用中,我们通常设:
* soft nofile 65536 * hard nofile 65536或更安全的:
* soft nofile 65536 * hard nofile 1048576前者防误操作,后者留出运维弹性。但注意:*表示所有用户,包括 root。如果只想针对特定用户(如kafka用户),应写:
kafka soft nofile 100000 kafka hard nofile 1000002.4 为什么“如何让 limits.conf 生效”是高频问题?——四个常见失效场景
| 失效场景 | 根本原因 | 快速验证方法 | 典型表现 |
|---|---|---|---|
| SSH 登录后 ulimit -n 仍是 1024 | /etc/pam.d/sshd缺少pam_limits.so或配置错误 | grep limits /etc/pam.d/sshd | 新开 SSH 终端,ulimit -Sn不是你设的值 |
| systemd 服务 ulimit 不生效 | systemd 服务不走 PAM,limits.conf完全不读 | `systemctl show nginx | grep LimitNOFILE` |
su user后 limits 不变 | su不触发 PAM 登录,不加载 limits | su - user对比su user | su user后ulimit -n仍是原用户值 |
| 容器内 ulimit 无效 | 容器启动时未传递宿主机 limits,或容器 runtime 限制 | docker run --ulimit nofile=65536:65536 ... | 容器内ulimit -n显示 1024,且无法调高 |
这四类场景覆盖了 95% 的“limits.conf 不生效”问题。解决思路不是反复改 conf,而是先确认“谁在加载它、谁没加载它”。
3. 实操全流程:从临时调试到永久生效的七步法
3.1 第一步:确认当前会话的 ulimit 状态(诊断起点)
不要跳过这一步。很多问题源于你以为的“当前值”其实是错的。执行:
# 查看所有资源限制(重点关注 nofile, nproc, core) ulimit -a # 单独查看 nofile 的 soft/hard 值 ulimit -Sn # soft nofile ulimit -Hn # hard nofile # 查看当前 shell 进程的 PID,并检查内核实际限制 echo $$ cat /proc/$$/limits | grep "Max open files"输出示例:
Max open files 1024 4096 files这里1024是 soft,4096是 hard。如果ulimit -Sn显示 1024,但limits.conf里写了 65536,说明当前会话根本没加载配置。
实操心得:
cat /proc/<pid>/limits是金标准。ulimit命令可能被 shell 缓存,而/proc是内核实时数据。务必用它验证最终效果。
3.2 第二步:临时修改(快速验证,不写配置)
当你要紧急修复一个报错的服务,或测试某个值是否有效时,用临时命令:
# 临时提高当前会话 soft nofile(需 ≤ hard limit) ulimit -n 65536 # 如果 hard limit 太低,先提 hard(仅 root 可行) ulimit -Hn 100000 ulimit -Sn 100000 # 验证 ulimit -Sn # 应输出 100000 cat /proc/$$/limits | grep "Max open files" # 应显示 100000 100000⚠️ 注意:此操作仅对当前终端及后续启动的子进程有效。关闭终端即失效。这是验证“值本身是否被内核接受”的最快方式。如果这步都失败(报Operation not permitted),说明 hard limit 卡死了,必须进第三步。
3.3 第三步:永久修改 limits.conf(针对交互式登录用户)
这是最经典、也最容易出错的步骤。按顺序操作:
1. 编辑/etc/security/limits.conf
sudo vim /etc/security/limits.conf在文件末尾添加(以kafka用户为例,替换为你自己的用户名):
# <domain> <type> <item> <value> kafka soft nofile 100000 kafka hard nofile 100000 kafka soft nproc 65536 kafka hard nproc 65536 # 如果要限制 core dump 大小(防止磁盘打满) kafka soft core 0提示:
<domain>可以是用户名、@groupname(组)、*(所有用户)。<type>用soft/hard。<item>常用:nofile(文件描述符)、nproc(进程数)、core(core dump 大小)、stack(栈大小)、memlock(锁定内存)。<value>为数字,unlimited表示不限制(慎用)。
2. 确保 PAM 配置启用pam_limits.so
检查/etc/pam.d/sshd(SSH)和/etc/pam.d/common-session(Debian/Ubuntu 通用)或/etc/pam.d/system-auth(RHEL/CentOS):
# Debian/Ubuntu grep "pam_limits.so" /etc/pam.d/common-session # 应有:session required pam_limits.so # RHEL/CentOS grep "pam_limits.so" /etc/pam.d/system-auth # 应有:session required pam_limits.so如果没有,手动添加:
echo "session required pam_limits.so" | sudo tee -a /etc/pam.d/common-session3. 强制重新登录(关键!)
修改后,必须退出当前 SSH 会话,重新登录。source或reload对 limits.conf 无效。重新登录后验证:
ulimit -Sn # 应为 100000 cat /proc/$$/limits | grep "Max open files" # 应显示 100000 100000实操心得:我曾因图省事在同一个终端里
exec bash,结果 limits 没刷新。Linux 的 limits 是进程创建时继承的,exec只是替换 shell,不触发新登录流程。唯一可靠方式:关掉终端,重开,再登录。
3.4 第四步:systemd 服务的永久 ulimit(绕过 PAM 的正确姿势)
这是企业级部署中最容易翻车的环节。limits.conf对 systemd 服务完全无效,必须用 systemd 原生机制:
1. 创建服务覆盖目录(推荐,不改原始 unit 文件)
# 以 nginx 为例 sudo mkdir -p /etc/systemd/system/nginx.service.d sudo vim /etc/systemd/system/nginx.service.d/limits.conf写入:
[Service] LimitNOFILE=100000 LimitNPROC=65536 # 如果要禁用 core dump LimitCORE=0提示:
LimitNOFILE=对应nofile,LimitNPROC=对应nproc。单位是数字,不是字符串。unlimited也可用,但建议设具体值便于监控。
2. 重载 systemd 配置并重启服务
# 重载配置(让 systemd 读取新文件) sudo systemctl daemon-reload # 重启服务(必须重启,reload 不会重读 limits) sudo systemctl restart nginx # 验证:查服务主进程的 limits sudo systemctl show nginx | grep LimitNOFILE # 输出应为:LimitNOFILE=100000 # 查进程实际值(找 master 进程 PID) sudo ps aux | grep "nginx: master" # 假设 PID 是 12345,则: sudo cat /proc/12345/limits | grep "Max open files"3. 针对用户级 systemd 服务(如--user服务)
如果服务是systemctl --user start myapp启动的,配置位置不同:
mkdir -p ~/.config/systemd/user/myapp.service.d vim ~/.config/systemd/user/myapp.service.d/limits.conf内容相同,但需运行:
systemctl --user daemon-reload systemctl --user restart myapp3.5 第五步:Shell 启动文件的补充加固(防漏网之鱼)
即使 PAM 正常,某些场景下 limits 仍可能被覆盖:
- 用户
.bashrc或.profile里有ulimit -n 1024; - 某些发行版的
/etc/profile.d/下有脚本重置 ulimit; - 使用
tmux/screen时,会话继承可能异常。
为保险起见,在用户 shell 配置中追加(以~/.bashrc为例):
# ~/.bashrc 末尾添加 if [ -f /etc/security/limits.conf ]; then # 仅当 limits.conf 存在且当前用户有权限时尝试加载(非必须,但可兜底) # 实际上,PAM 已加载,此处是心理安慰+防极端情况 ulimit -n $(grep "^$(whoami)" /etc/security/limits.conf 2>/dev/null | grep "nofile" | awk '{print $4}' | tail -n1 || echo "65536") fi更稳妥的做法是:删除所有可能重置 ulimit 的脚本。搜索:
grep -r "ulimit.*n" /etc/profile.d/ ~/.bashrc ~/.profile 2>/dev/null注释掉或删除相关行。
3.6 第六步:内核级兜底参数(应对极端高并发)
当nofile需要 > 100 万时,仅用户态设置不够,还需调内核参数:
# 临时生效 sudo sysctl -w fs.file-max=2097152 sudo sysctl -w fs.nr_open=2097152 # 永久生效:写入 /etc/sysctl.conf echo "fs.file-max = 2097152" | sudo tee -a /etc/sysctl.conf echo "fs.nr_open = 2097152" | sudo tee -a /etc/sysctl.conf sudo sysctl -pfs.file-max:系统级最大文件描述符总数(所有进程总和);fs.nr_open:单进程ulimit -n的 hard limit 上限(不能超过此值)。
提示:
fs.nr_open必须 ≥ 你设的hard nofile。如果limits.conf设了hard nofile 2000000,但fs.nr_open是默认的1048576,则 hard limit 会被内核截断为1048576,导致ulimit -Hn查不到你设的值。
3.7 第七步:全链路验证清单(上线前必做)
不要只信ulimit -n,要验证整个链路:
| 验证点 | 命令 | 期望结果 | 说明 |
|---|---|---|---|
| 1. 当前登录用户 limits | ulimit -Sn && ulimit -Hn | 100000100000 | 确认 PAM 加载成功 |
| 2. 服务进程实际 limits | cat /proc/$(pgrep -f "nginx: master")/limits | grep "Max open files" | 100000 100000 | 确认 systemd 配置生效 |
| 3. 服务子进程继承 | cat /proc/$(pgrep -f "nginx: worker")/limits | grep "Max open files" | 同上 | worker 进程必须继承 master |
| 4. 系统级上限 | cat /proc/sys/fs/file-max | ≥ 2097152 | 确保不成为瓶颈 |
| 5. 当前已用文件数 | lsof -u kafka | wc -l或cat /proc/$(pgrep -u kafka)/fd | wc -l | 远低于 hard limit | 防止已接近上限 |
| 6. 日志监控 | journalctl -u nginx -n 50 | grep -i "too many open files" | 无输出 | 确认业务不再报错 |
实操心得:我在线上 Kafka 集群升级时,漏了第 3 步(验证 worker 进程),结果 master 进程 limits 正确,但 worker 进程仍是 1024,导致部分分区 leader 选举失败。后来发现是 Kafka 自己用
fork()启动子进程时未显式继承,需在server.properties中加log4j.logger.kafka.network.Processor=DEBUG才暴露。所以“验证子进程”不是可选项,是必选项。
4. 常见问题与排查技巧实录
4.1 “ulimit -n 65536 报错:Operation not permitted” 的五层排查法
这不是配置错误,而是权限/流程卡点。按顺序逐层检查:
Layer 1:当前 hard limit 是否足够?
ulimit -Hn # 如果输出是 1024,那 65536 肯定失败→ 解决:先提 hard limit(root 执行ulimit -Hn 100000),再提 soft。
Layer 2:是否在容器内?Docker 默认限制 1048576,但可能被--ulimit覆盖
# 容器内执行 cat /proc/1/limits | grep "Max open files" # 看 init 进程限制→ 解决:启动容器时加--ulimit nofile=100000:100000,或在docker-compose.yml中:
services: app: ulimits: nofile: soft: 100000 hard: 100000Layer 3:是否是 systemd 服务?systemctl show查LimitNOFILE=
systemctl show nginx | grep LimitNOFILE # 如果是 4096,说明没配 systemd limits→ 解决:按 3.4 节配LimitNOFILE=。
Layer 4:PAM 是否启用?login进程是否加载了pam_limits.so?
# 查看当前会话的 login 进程(通常是 PID 1 的父进程) ps -o ppid= -p $$ | xargs ps -o comm= -p # 如果是 sshd 或 getty,再查其 PAM 配置→ 解决:确认/etc/pam.d/sshd有session required pam_limits.so。
Layer 5:内核fs.nr_open是否太小?
cat /proc/sys/fs/nr_open # 如果是 1048576,而你要设 hard 2000000,就会被截断→ 解决:sysctl -w fs.nr_open=2097152并写入/etc/sysctl.conf。
排查口诀:“先看 hard,再查容器,三盯 systemd,四验 PAM,五查内核”。按此顺序,99% 的
Operation not permitted都能定位。
4.2 “limits.conf 修改后,重启机器也不生效” 的真相
这不是 bug,是设计。limits.conf只在用户登录时加载一次,重启机器本身不触发任何用户登录。所以:
- 如果你改了
limits.conf,然后reboot,但没用新用户登录或没重新 SSH,那 limits 还是旧的; - 更隐蔽的是:某些云服务器(如 AWS EC2)的
cloud-init脚本会在首次启动时自动创建用户并登录,但后续重启不会重复此流程,导致你改了 conf 却以为“重启没用”。
→ 正确做法:改完limits.conf,必须手动重新登录一次(哪怕只是ssh localhost),才能触发 PAM 加载。
4.3 “Java 应用 ulimit 不生效” 的 JVM 特殊处理
Java 应用有个陷阱:JVM 启动时会读取启动用户的 ulimit,但某些 JVM 参数(如-XX:+UseG1GC)会隐式增加线程数,间接消耗更多文件描述符。更麻烦的是:
- 如果用
java -jar app.jar &启动,后台进程可能继承错误的 limits; - 如果用
nohup java -jar app.jar &,nohup会重定向 stdout/stderr,额外占用 fd。
→ 最佳实践:
- 用 systemd 管理 Java 服务(按 3.4 节配
LimitNOFILE=); - 启动脚本中显式设置:
#!/bin/bash ulimit -n 100000 exec java -Xms2g -Xmx2g -jar /opt/app.jar "$@"- 在 Java 代码中验证:
// Java 代码里打印当前 limits(需 jdk 10+) long maxFiles = sun.misc.VM.maxDirectMemory(); // 或更直接:Runtime.getRuntime().exec("ulimit -n");4.4 “nofile 设置了 100 万,但服务还是报 EMFILE” 的终极排查表
| 可能原因 | 检查命令 | 解决方案 |
|---|---|---|
| 服务进程未重启 | ps aux | grep your_app查 PID,对比cat /proc/<old_pid>/limits和cat /proc/<new_pid>/limits | systemctl restart your_app |
| 子进程未继承 | pgrep -P <master_pid>查子进程 PID,再cat /proc/<child_pid>/limits | 确保 master 进程启动子进程时未重置 ulimit(如用fork()后未调setrlimit()) |
| 网络连接泄漏 | lsof -p <pid> | grep "IPv4|IPv6" | wc -l,对比netstat -an | grep :your_port | wc -l | 修复代码中的 socket 未 close 问题,或加连接池超时 |
| 日志文件句柄泄漏 | lsof -p <pid> | grep "\.log$" | wc -l | 检查 log4j/logback 配置,确保RollingFileAppender的maxHistory合理 |
内核epoll限制 | cat /proc/sys/fs/epoll/max_user_watches | echo 524288 > /proc/sys/fs/epoll/max_user_watches(需 root) |
注意:
lsof -p <pid>输出的每一行代表一个打开的文件(包括 socket、pipe、regular file)。如果lsof行数接近ulimit -n,说明确实是耗尽,而非配置问题。
4.5 高危操作警告与备份策略
- 永远不要在生产环境直接
echo "* hard nofile unlimited" >> /etc/security/limits.conf:unlimited会让进程无节制消耗 fd,可能拖垮整个系统。设一个合理上限(如1048576)更安全。 - 修改
fs.file-max前,计算系统内存:每 1000 个 fd 约占 1MB 内存。fs.file-max=2097152需预留约 2GB 内存。 - 备份原始配置:
sudo cp /etc/security/limits.conf /etc/security/limits.conf.bak.$(date +%Y%m%d) sudo cp /etc/sysctl.conf /etc/sysctl.conf.bak.$(date +%Y%m%d) - 灰度发布:先在一台非核心机器上验证全套流程(改 conf → 重登 → 启服务 → 压测),确认
lsof数量稳定增长且不报错,再批量推送。
5. 进阶场景:多用户、容器、Kubernetes 中的 ulimit 管理
5.1 多用户隔离:为不同服务用户设不同 limits
企业环境中,mysql、redis、kafka用户应有独立 limits,互不影响:
# /etc/security/limits.conf mysql soft nofile 65536 mysql hard nofile 65536 redis soft nofile 32768 redis hard nofile 32768 kafka soft nofile 100000 kafka hard nofile 100000 # 禁止普通用户提得太高 * soft nofile 4096 * hard nofile 8192提示:
*规则放在最后,避免覆盖前面的具体用户规则。PAM 按文件顺序匹配,第一个匹配的生效。
5.2 Docker 容器内的 ulimit 管理
容器共享宿主机内核,但默认ulimit继承自dockerd进程(通常是 1048576)。要为容器单独设:
# 启动时指定 docker run --ulimit nofile=65536:65536 -d nginx # docker-compose.yml version: '3.8' services: web: image: nginx ulimits: nofile: soft: 65536 hard: 65536关键点:容器内ulimit -n显示的是容器自己的限制,与宿主机无关。但fs.file-max是宿主机的,所以容器内lsof总数不能超过宿主机fs.file-max。
5.3 Kubernetes Pod 中的 ulimit 设置
K8s 本身不直接管理 ulimit,需通过securityContext或 initContainer:
方案 1:Pod securityContext(推荐)
apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: securityContext: # 设置整个 Pod 的 ulimit sysctls: - name: fs.file-max value: "2097152" containers: - name: nginx image: nginx securityContext: # 设置容器内 ulimit(需 kubelet 支持 --feature-gates=SupportPodPidsLimit=true) # 但更通用的是用 initContainer方案 2:initContainer 预设(最兼容)
apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: initContainers: - name: set-ulimit image: busybox command: ['sh', '-c', 'ulimit -n 100000 && echo "ulimit set"'] securityContext: privileged: true # 需要特权模式才能改系统参数 containers: - name: nginx image: nginx注意:K8s 中
ulimit设置受 CRI(containerd/docker)和 kubelet 配置影响。生产环境建议统一在 node 级别调好fs.file-max和limits.conf,Pod 内只设LimitNOFILE。
5.4 云环境特殊考量(AWS EC2, 阿里云 ECS)
- AWS EC2:
t3/t4g等突发性能实例,fs.file-max默认较低(如 386349),需手动调高; - 阿里云 ECS:某些镜像预装
cloud-init会覆盖/etc/security/limits.conf,需检查/var/lib/cloud/instances/*/sem/config_scripts_user; - 所有云厂商:
/proc/sys/fs/file-max可能被cloud-init或sysctl服务重置,建议在/etc/sysctl.d/99-cloud.conf中写死。
6. 我的实战经验总结:那些文档里不会写的细节
我在金融、电商、SaaS 三个行业的高并发系统里,调过上千台服务器的 ulimit。有些教训,是文档里绝对找不到的:
“ulimit -n 65536” 在 zsh 下可能不生效:zsh 的
ulimit内置命令行为与 bash 略有差异。如果用 zsh,务必用zsh -c 'ulimit -n 65536; exec bash'切换,或直接在/etc/shells里把用户默认 shell 改为 bash。我曾在一个客户环境折腾半天,最后发现是团队统一用了 zsh,而所有文档都默认 bash。systemd的LimitNOFILE=有单位陷阱:LimitNOFILE=65536是正确的,但LimitNOFILE=65536K会报错。systemd 不支持 K/M 后缀,必须是纯数字。这个错在 journalctl 里只显示Failed to parse resource limit,非常难 debug。lsof的统计不准:lsof -p <pid> | wc -l会把重复打开的文件(如多个线程打开同一个 log 文件)算多次。真实 fd 数应看/proc/<pid>/fd/目录下的文件数:ls -l /proc/<pid>/fd/ | wc -l。这才是内核维护的精确计数。nofile不是越大越好:设1000000后,lsof执行变慢(因为要遍历百万级 fd),ps aux