在 Linux 面试中,文件描述符(File Descriptor,简称 fd)是一个高频考点。很多候选人能说出“fd 是一个整数”,但一旦追问“它到底怎么来的”“fd 耗尽怎么排查”,回答就开始模糊。这篇文章从内核视角把 fd 讲清楚,并给出一套可落地的排查方法。
一、文件描述符到底是什么
Linux 的设计哲学是“一切皆文件”。普通文件、目录、管道、套接字、设备、甚至 epoll 实例,在进程看来都是文件。当进程打开一个文件时,内核会返回一个非负整数,这个整数就是文件描述符。
它本质上是一个索引,指向进程级打开文件表中的一个表项。理解 fd 需要串起三层结构:
- 进程级文件描述符表:每个进程独立,本质是一个数组,fd 就是数组下标。表项指向一个
struct file。 - 系统级打开文件表:
struct file记录了文件偏移量、访问模式、文件状态标志等。多个 fd 可以通过dup或fork指向同一个struct file,因此共享偏移量。 - inode 表:真正描述文件元数据和磁盘数据位置。
所以 fd 不是文件本身,也不是 inode,它只是进程私有的一个“句柄编号”。这也解释了几个经典结论:
- 不同进程的 fd 0 可以指向完全不同的文件。
fork后子进程会复制父进程的 fd 表,父子 fd 指向同一个struct file,共享文件偏移。dup2可以让两个 fd 指向同一个打开文件,常用于重定向。
每个进程的 fd 表默认有上限,查看方式:
ulimit -n # 当前 shell 的软限制
ulimit -Hn # 硬限制
cat /proc/sys/fs/file-max # 系统级总上限
fd 0、1、2 分别固定为标准输入、标准输出、标准错误。程序启动时通常已经占用这三个。
二、fd 耗尽的典型表现
当进程打开的 fd 达到上限,新的 open、socket、accept、pipe 调用会失败,常见错误:
Too many open files(EMFILE):进程级限制被突破。Too many open files in system(ENFILE):系统级限制被突破。
典型症状包括:服务无法接受新连接、日志写入失败、定时任务莫名报错,但 CPU 和内存看起来都很正常。这类问题在 Web 服务器、网关、消息队列中尤其常见。
三、fd 耗尽的排查步骤
1. 确认是不是 fd 问题
先看错误信息,再确认当前限制:
ulimit -n
cat /proc/<pid>/limits | grep "open files"
如果进程当前 fd 数已经接近限制值,基本可以定位。
2. 统计进程打开的 fd 数量
ls /proc/<pid>/fd | wc -l
或者用 lsof:
lsof -p <pid> | wc -l
3. 定位 fd 都花在哪里
按类型聚合,快速看出是 socket、普通文件还是管道泄漏:
ls -l /proc/<pid>/fd | awk '{print $11}' | sort | uniq -c | sort -rn | head
也可以按文件系统或目录聚合:
lsof -p <pid> | awk '{print $5}' | sort | uniq -c | sort -rn
常见结论:
- 大量
socket:说明连接未关闭,可能是连接池泄漏或客户端未断开。 - 大量指向同一日志文件的 fd,说明文件句柄反复打开未释放。
- 大量
pipe或anon_inode,可能是线程/协程创建后未回收。
4. 查看具体 fd 指向
ls -l /proc/<pid>/fd
/proc/<pid>/fd 下的每个符号链接就是 fd 指向的真实对象。对 socket,可以进一步用 ss 关联:
ss -tanp | grep <pid>
5. 系统级排查
cat /proc/sys/fs/file-nr
输出三个值:已分配、已分配但未使用、系统上限。第一个值持续增长说明系统整体 fd 在泄漏。
四、常见原因与解决思路
原因一:连接未关闭。 HTTP 客户端、数据库连接、Redis 连接忘记 close,或异常路径没有走 defer close。解决方式是统一封装连接生命周期,配合连接池上限。
原因二:accept 后未处理。 服务器 accept 新连接后,如果处理逻辑抛异常提前返回,socket 可能泄漏。需要在入口统一兜底关闭。
原因三:日志或临时文件反复打开。 每次请求都 open 日志文件却不关闭。应改为长生命周期句柄或使用日志库统一管理。
原因四:限制设置过低。 高并发服务默认 1024 往往不够。可以调整:
# 临时
ulimit -n 65535
# 永久,写入 /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
# systemd 服务
LimitNOFILE=65535
同时关注 fs.file-max 和 fs.nr_open。
五、面试加分点
回答这类问题时,建议按“概念—结构—现象—排查—治理”的顺序展开。能说清 fd 是进程级索引、struct file 是系统级对象、fork 后共享偏移,就已经超过大多数候选人。再补一句“排查时先看 /proc/<pid>/fd 聚合,再结合 lsof 和 ss 定位泄漏源”,基本就是完整答案。
最后提醒:调大 ulimit 只是缓解,真正的解法是找到泄漏点并修复。fd 泄漏往往伴随连接泄漏,长期不处理会演变成更严重的资源问题。
未经允许不得转载:任鹏个人博客 » Linux 面试题:文件描述符到底是什么,fd 耗尽如何排查

