linux下读取/proc/self/fd是最直接方式,通过opendir/readdir遍历并用readlink获取路径,fdinfo提供更细粒度状态但需逐个读取,getdents64可绕过glibc实现轻量枚举,需注意权限、race condition及字段语义。

Linux下读取 /proc/self/fd 目录是最直接的方式
在 Linux 系统上,C++ 程序无法通过标准库或 POSIX API 直接枚举当前进程所有打开的 fd,但内核通过 /proc/self/fd(即当前进程的 /proc/PID/fd)以符号链接形式暴露了全部活动描述符。这是最可靠、开销最小的方案。
实操要点:
- 用
opendir()打开/proc/self/fd,再用readdir()遍历;每个目录项(如"0"、"3")即一个 fd 编号 - 对每个项调用
readlink()可获取其指向路径(如"socket:[12345]"、"pipe:[67890]"、"/tmp/file.txt"),注意缓冲区需足够(PATH_MAX) - 跳过
"."和"..";readdir()返回的d_name是字符串,需用strtol()或std::stoi()转为整数判断是否为纯数字名 - 不要假设所有 fd 都可被
readlink()解析成功——例如某些匿名 fd(eventfd、timerfd)可能返回ENOENT或内容为空,应容错处理
fdinfo 提供更细粒度状态,但需逐个读取
/proc/self/fdinfo/<fd></fd> 文件包含每个 fd 的访问模式、偏移、锁状态、引用计数等,比单纯看符号链接更有价值。但它不是目录,必须对每个已知 fd 单独打开读取。
关键细节:
- 每行是
key: value格式,例如pos: 4096、flags: 02004002(八进制)、mnt_id: 12;flags值需用O_*宏反查(如O_RDWR | O_APPEND) - 某些字段只在特定 fd 类型存在:如
socket:[...]对应的 fdinfo 中有st(socket type)、tr(tx queue)、tm->when(timer expiry)等 - 频繁读取大量
fdinfo文件会有明显 I/O 开销,生产环境慎用于高频采样;建议仅在诊断时按需拉取 - 注意权限:普通用户可读自己进程的
fdinfo,但若进程由其他用户启动(如 setuid)则可能被拒绝(EACCES)
getdents64() 绕过 glibc 封装可避免 opendir 内存分配
当需要极致轻量或嵌入式场景下规避 opendir() 的堆内存分配时,可直接调用系统调用 getdents64()。它不依赖 libc 的 DIR 结构,适合资源受限或信号安全上下文。
注意事项:
- 需手动定义
struct linux_dirent64(glibc 不导出),并用syscall(SYS_getdents64, ...)调用;缓冲区需自行 malloc(通常 4KB~8KB 足够) - 返回的
d_type字段可快速过滤非数字项(DT_UNKNOWN居多,但DT_DIR或DT_LNK可辅助判断) - 相比
readdir(),你得自己解析d_name的 null 结尾与下一个 entry 的偏移,容易因边界计算错误导致越界或漏项 - 该方式无跨平台性,仅限 x86_64/aarch64 Linux;Windows/macOS 完全不适用
注意 fd 0/1/2 的特殊性和 race condition
标准输入输出错误(fd 0、1、2)常被重定向或关闭,且它们的状态变化极快——比如子进程 exec 后父进程仍在遍历 /proc/self/fd,就可能遇到 readlink(): No such file or directory。
稳健做法:
- 遍历时对每个
readlink()和open()(fdinfo)操作加errno检查,容忍ENOENT、EBADF、EACCES - 避免在信号处理函数中执行该逻辑——
opendir()和readlink()非异步信号安全 - 不要假设 fd 列表“静态”;两次遍历之间 fd 可能被关闭或复用,尤其在多线程程序中;如需一致性快照,需配合
fork()+prctl(PR_SET_DUMPABLE, 0)防止被 ptrace 干扰(仅限调试场景)
真正难的不是读出 fd 列表,而是理解每个 fdinfo 字段含义和对应内核对象生命周期。比如 pos: 对 pipe 无效,flags: 中的 O_CLOEXEC 位是否置位会影响 fork 行为——这些细节不查内核文档很容易误判。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











