先说明一个前提:在 Linux 环境下,获取当前进程所有打开的文件描述符,最可靠的方式并非调用外部命令,也不是依赖跨平台抽象库,而是直接读取 /proc/self/fd 目录。这个目录由内核直接以符号链接集合的形式暴露,每个条目的名称即文件描述符的数字编号,例如 0、1、37,它们指向实际打开的文件路径或设备节点。需要明确的是,这并非 C++ 标准功能,而是 Linux 特有的接口,无法在 Windows 上使用——不过,只要你在做 Linux 开发,这条路径基本属于标配方案。
另一个需要留意的要点是:进程必须具备读取 /proc/self/fd 的权限。绝大多数普通进程都拥有该权限,但如果你所在的沙箱或容器环境裁剪了 /proc 的访问权限,情况就另当别论了。

Linux 下通过 /proc/self/fd 目录读取 fd 列表最可靠
具体操作只需几步即可完成:
- 使用
opendir()打开/proc/self/fd,然后调用readdir()遍历条目,先将.和..过滤掉。 - 针对每个条目名称,调用
strtol()将其转换为整数——只有纯数字名称才是有效的 fd 编号,例如"5"是合法的,而"stderr"本质上是符号链接别名,它并不代表新增的 fd,仅便于查看,必须跳过。 - 如果你还想知道每个 fd 对应的真实路径,可以对
/proc/self/fd/N调用readlink(),但需要注意缓冲区大小以及截断风险。在实际开发中,很多场景仅需要 fd 编号本身,比如批量关闭或记录日志,没必要强行解析路径。
别走弯路:避免使用 lsof 或 system() 调用外部命令
代码中有一个常见误区:为了省事去调用 lsof -p $$ 或者 ls -l /proc/self/fd,然后解析输出。这种做法不仅会多开一个进程、引入 shell 依赖,更重要的是——输出格式极其不稳定,尤其是当文件名包含空格或换行符时,解析过程会让你怀疑人生。此外,lsof 本身可能因权限不足直接报错,或者在容器、chroot 环境中根本无法使用。
直接读取 /proc/self/fd 目录则绕过了所有解析歧义,这是内核保证一致性的接口。换用 system() 更不推荐,它会触发 fork+exec,破坏信号处理上下文,并且无法安全抓取全部输出;即便执行成功,还需要做字符串分割、字段定位、转义还原等操作,既繁琐又容易出错。
fd 数量上限与遍历边界问题
不能假设 fd 从 0 开始连续编号,更不要盲目写一个循环 for (int i = 0; i < some_limit; i++) 去调用 fcntl(i, F_GETFD) 探测——那样既低效又不可靠。Linux 中 fd 是稀疏分配的,存在大量空洞,而 F_GETFD 对无效 fd 会返回 -1 并设置 errno=EBADF,频繁系统调用的开销不容小觑。
唯一权威的来源就是 /proc/self/fd 目录中的内容,它只列出真实存在的 fd。fd 的最大值由 ulimit -n 决定,但分配是动态的,无需你预估范围。另外,readdir() 返回的目录项顺序是无序的,如果需要排序,可以自行收集到 vector 后调用 std::sort 处理。
C++ 代码片段示例(仅核心逻辑)
#include#include #include #include std::vector get_open_fds() { std::vector fds; DIR* dir = opendir("/proc/self/fd"); if (!dir) return fds; struct dirent* ent; while ((ent = readdir(dir)) != nullptr) { char* endptr; long fd_num = strtol(ent->d_name, &endptr, 10); if (*endptr == '\0' && fd_num >= 0 && fd_num < INT_MAX) { fds.push_back(static_cast (fd_num)); } } closedir(dir); return fds; }
这个函数返回当前进程中所有有效 fd 编号的 vector。代码中没有做 readlink,因为路径解析涉及编码、长度、权限等额外分支,大多数场景只需 fd 编号即可——比如用于 close() 批量清理或输出调试日志。
最后提一个容易被忽略的细节:/proc/self/fd 目录在多线程环境下读取是安全的,不过如果在遍历的同时,另一个线程正在 close() 某个 fd,readdir() 仍可能返回它(因为目录项的删除存在延迟)。因此,拿到的结果反映的是“遍历开始时刻”的快照,并非绝对的实时状态。这一点在实际排查问题时心里有数即可,通常不影响使用。
