linux用/proc/self/fd遍历数字目录项获取fd;macos用proc_pidlistfiles()配合fcntl验证;windows调ntquerysysteminformation过滤句柄。三者均仅提供瞬时快照,跨平台封装需注意排序、路径处理及句柄类型安全。

Linux下读取/proc/self/fd目录获取fd列表
在Linux系统中,C++程序无法直接通过标准库枚举当前进程的打开文件描述符(file descriptor),但可以借助/proc/self/fd这一内核虚拟文件系统路径实现。该目录下每个数字命名的条目(如0、1、3)即为一个有效fd,其内容是该fd指向的文件路径或设备符号链接。
实操时需注意:opendir() + readdir() 遍历该目录即可;但必须跳过.和..,且只处理纯数字名称的条目(避免误判fdinfo等子目录)。另外,readlink()可进一步读取每个fd对应的路径,但某些fd(如socket、eventfd、管道)会返回not a valid path类字符串,不是错误,而是正常行为。
- 使用
std::vector<int></int>收集解析出的数字fd值,而非字符串,便于后续dup()或close()操作 - 不要用
stat()判断fd是否有效——它可能因权限或竞态而失败;应以readdir()结果为准 - 遍历过程不加锁,但fd本身可能在遍历中被其他线程关闭,因此得到的列表只是瞬时快照,不可用于强一致性场景
macOS上没有/proc,改用libproc.h的proc_pidlistfiles
macOS不提供/proc,需依赖libproc.h(仅限Darwin内核,非POSIX标准)。函数proc_pidlistfiles()可列出指定pid的所有打开文件信息,包括fd号、路径、偏移、访问模式等字段。
调用前需链接-lproc,并确保进程有相应权限(通常需要root或被调试状态)。该函数返回的是struct proc_fileinfo*数组,其中fi_fd字段即为fd号,fi_path为路径(可能为空或截断)。
在无 root/sudo 权限的环境(云容器、VPS、隔离主机)中安装并配置 OpenClaw 浏览器工具的 headless Chrome。适用场景:...
-
proc_pidlistfiles()返回的fd列表包含已关闭但尚未释放的“僵尸fd”(如fork后父进程未wait),需结合fcntl(fd, F_GETFD)二次验证有效性 - 路径字段
fi_path长度受限(通常256字节),长路径会被截断,不能完全信任 - 该API在macOS 12+中部分功能被限制,尤其对沙盒进程几乎总返回空,实际部署前务必在目标系统版本上测试
Windows用NtQuerySystemInformation枚举句柄表
Windows没有“文件描述符”概念,对应的是句柄(handle),且无公开API直接列出当前进程所有句柄。最可靠方式是调用未文档化的NtQuerySystemInformation()配合SystemHandleInformation(或Win10后的SystemExtendedHandleInformation),然后过滤出当前进程的句柄项。
这需要:包含winternl.h、手动声明NtQuerySystemInformation函数指针、分配足够缓冲区(初始可试4KB,失败则按返回大小重分配)、遍历SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX结构体数组,并比对UniqueProcessId字段是否等于GetCurrentProcessId()。
- 必须以管理员权限运行才能获取完整句柄列表;否则只能看到本进程创建的、未继承/未跨进程传递的句柄
- 句柄类型(
ObjectTypeNumber)需查ntdll.dll导出的ObGetObjectType或硬编码映射表,常见值:0x1f=File,0x20=Directory,0x27=Event等 - 即使拿到句柄值,也不能直接
CloseHandle()任意句柄——比如CRT内部持有的stdin句柄若被关掉,后续printf()会崩溃
跨平台封装要注意的三个陷阱
若试图写一个统一接口(如std::vector<int> get_open_fds()</int>),以下三点极易出错:
- Linux下
/proc/self/fd遍历时,fd100可能比fd99先出现(目录项无序),不能假设数字递增;排序应在收集完毕后做 - macOS的
proc_pidlistfiles()返回路径含->符号链接指示,但不展开;若需真实路径,得自己调用realpath(),而realpath()在设备文件(如/dev/tty)上会失败 - Windows句柄值虽是
HANDLE(本质是void*),但数值范围远超int,强制转int会导致高位截断;应统一用intptr_t或uint64_t存储
真正麻烦的不是怎么列出来,而是列出来之后——你根本不知道哪个fd是自己开的、哪个是runtime偷偷持有着的、哪个正在被另一线程往里写。别指望靠这个做资源泄漏检测,除非你同时hook了所有open()/CreateFile()调用点。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










