确认文件描述符耗尽需验证系统调用返回-1且errno为emfile或enfile;结合lsof -p | wc -l与getrlimit对比,若接近软限即证实耗尽;提限前须先排查fd泄漏,否则治标不治本。

怎么确认卡死真是文件描述符耗尽引起的
程序卡在 open、socket、epoll_ctl 或 accept 等系统调用上,且返回 -1 并置 errno == EMFILE(进程打开文件数超限)或 ENFILE(系统级总限制),才是文件描述符越界的明确信号。不能仅凭“卡住”就断定是 fd 问题——它更常被误判为死锁或阻塞 I/O。
实操建议:
- 在关键系统调用后立刻检查返回值和
errno,例如:int fd = open("log.txt", O_WRONLY | O_APPEND); if (fd == -1) { if (errno == EMFILE) { fprintf(stderr, "EMFILE: per-process fd limit exhausted\n"); } } - 运行时用
lsof -p <pid> | wc -l</pid>查看当前进程已打开 fd 数,对比getrlimit(RLIMIT_NOFILE, &rlim)获取的软限rlim.rlim_cur;若两者接近(比如相差 - 注意:
ulimit -n显示的是 shell 启动时的初始值,不是你程序运行时的真实限制;必须在main()开头调用getrlimit才可靠
为什么 getrlimit 返回 RLIM_INFINITY 却 still 卡在 open
RLIM_INFINITY(通常为 -1)只表示“无硬性上限”,不代表内核真能无限分配 fd——它仍受限于内存、/proc/sys/fs/file-max 系统总限制,以及内核为每个 fd 分配的 struct file 内存开销。尤其在小内存容器或 systemd 服务中,RLIM_INFINITY 常被 LimitNOFILE= 或 --ulimit nofile= 覆盖为实际有限值。
常见错误现象:
- 程序在开发机跑得好好的,上线后频繁
EMFILE——因为生产环境用了 systemd,配置了LimitNOFILE=4096 - fork 出的子进程 fd 表继承自父进程,但没 close 不必要的 fd(如日志文件、监听 socket),导致子进程更快触顶
- 使用
std::thread创建大量线程,每个线程又打开自己的pipe或eventfd,叠加后迅速耗尽
如何快速定位哪些 fd 没被正确关闭
不要靠代码肉眼扫——fd 泄漏往往藏在异常路径、RAII 失败、或 goto 跳转绕过 close 的地方。最有效的方式是运行时监控 fd 变化。
实操建议:
- 启动前加
exec 3>/tmp/fd_trace.log,然后在关键位置(如函数入口/出口、异常捕获块)用ls -l /proc/self/fd >&3快照 fd 列表,比对差异 - 用
strace -e trace=open,close,dup,dup2,socket,accept -p <pid></pid>实时抓取 fd 相关系统调用,重点看是否有open成功但无对应close - 在 RAII 封装类(如自定义
FileDescriptor)构造/析构时打日志,确保每open都有且仅有一次close;避免裸int fd在多个作用域间传递
setrlimit 提高上限但为什么还是失败
调用 setrlimit(RLIMIT_NOFILE, &new_rlim) 失败的最常见原因是:新软限 > 当前已用 fd 数,或 > 硬限。内核会拒绝这种“虚假提升”,防止程序误以为提限成功却仍无法打开新 fd。
关键约束:
- 必须在
main()最开头调用,甚至用__attribute__((constructor))保证早于任何库初始化(如 glibc 的 stdio 初始化会悄悄打开 fd) - 非 root 进程只能把软限设到硬限,不能提高硬限本身;若需突破系统默认硬限(如从 1024 到 65536),得提前用
sudo ulimit -Hn 65536或改/etc/security/limits.conf - systemd 服务中,
LimitNOFILE=会覆盖所有用户态setrlimit调用,此时必须改 service 文件并 reload daemon
真正棘手的点不在“怎么提限”,而在于:即使提到了 65536,如果程序每秒创建 100 个未关闭的 socket,不到 10 分钟又会回到 EMFILE。排查 fd 泄漏永远比盲目提限更重要。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











