文件描述符
文件描述符是进程引用已打开 I/O 对象的非负整数。Linux 文件、socket 和管道都可通过 fd 操作。本文负责引用关系、关闭责任和容量限制。
1. 面试核心
先记住这几条,再读正文- 描述符与打开文件描述。 fd 是进程描述符表的索引,打开文件描述保存偏移和状态标志。
- 共享偏移。 dup 和 fork 继承的 fd 共享原打开文件描述。卡 · 面试核心
- 关闭与编号复用。 close 释放一个描述符引用,之后其编号可被复用。
- 资源耗尽。 EMFILE 表示进程描述符超限,ENFILE 表示系统打开文件数超限。卡 · fd 配额与继承
2. 文件引用层次
进程 fd 3 ─┐
├→ 打开文件描述 → 文件对象
进程 fd 4 ─┘
open 成功后创建打开文件描述,并返回 fd。两次独立 open 同一路径,文件偏移分别保存。dup 则复制引用,两个 fd 共享偏移。
fork 后,子进程继承 fd 表中的引用。父子各自的 fd 指向同一打开文件描述。共享关系也包含 O_NONBLOCK 等文件状态标志。
FD_CLOEXEC 属于单个描述符的标志。它决定成功 exec 时是否关闭该 fd。dup 新建的描述符默认清除 FD_CLOEXEC。
3. 生命周期与泄漏
每次成功打开都要明确关闭责任。异常、提前返回和失败重试也要释放 fd。持续泄漏会耗尽描述符配额。
unlink 删除目录项。最后一个硬链接删除后,仍打开的文件继续存在。最后一个打开引用释放后,相关存储才可回收。因此删除日志后仍占空间,要检查进程持有的引用。
Linux close 在 EIO 等错误下也已释放 fd 编号。盲目重试 close 会误关已复用的新对象。关闭前需协调异步任务和并发使用者。
4. fd 配额与继承
RLIMIT_NOFILE 的软限制约束进程可分配的 fd 编号范围。open 或 accept 超限会返回 EMFILE。系统打开文件表达到上限时返回 ENFILE。
排查时先查看 /proc/PID/fd 和 /proc/PID/limits。再按文件、socket、管道分类统计。描述符持续增长时,应追查未关闭路径。确认是正常容量不足后,再调整服务限制。
设置 O_CLOEXEC 可在 open 时原子启用关闭继承标志。先 open 再 fcntl 存在多线程竞争窗口。另一线程在窗口中执行 fork 与 exec,会把 fd 传入新程序。
5. 使用边界
异步任务只保存 fd 数字时,要防止编号复用。fd 3 关闭后可成为另一个连接。任务取消、引用生命周期和事件回调必须协同。
close 释放引用,文件内容的持久性由 fsync 等机制控制。标准输入、输出、错误分别使用 0、1、2。它们也能被重定向到其他 I/O 对象。
6 · 面试题卡片
本页题目与参考回答
6.1 为什么两个文件描述符可能共享读取位置,关闭一个后另一个还能使用?
考察重点:描述符与打开文件描述的区别、共享偏移和引用生命周期。
参考回答
dup 后两个 fd 指向同一打开文件描述,因此共享偏移。
close 释放当前 fd 的引用。另一个引用仍有效时,仍能访问底层对象。两次独立 open 则分别保存文件偏移。
6.2 服务出现 EMFILE 时应该怎样排查?
考察重点:描述符配额、泄漏与正常容量。
相关内容:fd 配额与继承。
参考回答
EMFILE 表示进程描述符超限。
先核对 RLIMIT_NOFILE 和 /proc/PID/fd。再区分泄漏与正常连接容量。泄漏需要修复关闭路径,正常容量不足则调整限制。
6.3 为什么多线程程序应优先使用 O_CLOEXEC?
考察重点:原子设置与 exec 继承窗口。
相关内容:fd 配额与继承。
参考回答
O_CLOEXEC 在 open 时原子设置关闭继承标志。
先 open 再 fcntl 会留下竞争窗口。另一线程在窗口中 fork、exec,新程序就会继承该 fd。成功 exec 时,带标志的描述符自动关闭。