Windows无公开API获取单个句柄引用计数,仅能通过NtQuerySystemInformation获取进程句柄总数;Linux不可观测f_count,只能统计/proc/pid/fd下文件描述符数量。

Windows 上没有公开 API 能直接读取句柄引用计数
系统内核确实维护每个句柄的引用计数(ObReferenceObjectByHandle 底层依赖它),但微软**从未暴露该字段给用户态**。任何声称“调用 GetHandleInformation 或 NtQueryObject 就能拿到引用计数”的说法,都是混淆了概念——前者只返回句柄标志(如可继承、是否为伪句柄),后者在 ObjectBasicInformation 类型下根本**不包含引用计数字段**。
常见错误现象:NtQueryObject 返回 STATUS_INVALID_INFO_CLASS 或结构体中 HandleCount 字段恒为 0,就是误用了信息类或期望了不存在的数据。
-
GetHandleInformation:仅返回DWORD标志位,和引用计数无关 -
NtQueryObject+ObjectBasicInformation:返回的是对象的总句柄数(即多少个句柄指向该对象),不是当前句柄自身的引用计数 - 试图读取
EPROCESS或OBJECT_HEADER内存:需驱动权限 + 版本强耦合,蓝屏风险高,非调试/安全研究场景不应尝试
替代方案:用 NtQuerySystemInformation 统计进程级句柄总数
虽然拿不到单个句柄的引用计数,但你可以获取进程当前打开的句柄总数——这是唯一稳定、无需驱动、被文档化的指标,适用于资源泄漏监控。
关键点在于使用 SystemHandleInformation(旧版)或更推荐的 SystemHandleInformationEx(Win10 1809+),后者支持按进程过滤且性能更好。
- 调用前必须启用
SE_DEBUG_PRIVILEGE,否则只能查自己进程 -
SystemHandleInformationEx返回的SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX结构里,HandleValue是句柄值,ObjectTypeIndex可映射到对象类型名(需配合NtQueryObject查询ObjectTypeInformation) - 不要循环调用
NtQuerySystemInformation频繁采样——开销大,建议间隔 ≥500ms
示例片段(简化错误处理):
// 启用调试权限后
NTSTATUS status = NtQuerySystemInformation(SystemHandleInformationEx,
buffer, size, &retlen);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
// 分配足够 buffer,重试
}
// 解析 buffer 中的 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX 数组
Linux 下根本不存在“句柄引用计数”这个监控维度
Linux 没有 Windows 那种内核对象句柄抽象。文件描述符(fd)本质是进程 files_struct 中的一个数组索引,其“引用”由内核文件表项(struct file)的 f_count 字段维护——但它**完全不可被用户态直接观测**,且不等价于 fd 数量(例如 dup() 增加 f_count 但不新增 fd)。
你真正能拿到的,只有 /proc/pid/fd/ 目录下的符号链接数量,即当前打开的 fd 总数:
-
scandir("/proc/self/fd", ...)或opendir("/proc/self/fd")计数即可 - 注意过滤掉 "." 和 "..",以及可能因权限缺失跳过的条目
- 该数值 ≈ 用户态可见的 fd 数,但不反映内核级
f_count,也不含匿名管道、eventfd 等特殊 fd 的深层引用
别试图读 /proc/pid/status 的 FDSize 字段——那是分配的 fd 表大小,不是已用数量。
跨平台监控建议:聚焦可测、可归因、可告警的指标
执着于“实时句柄引用计数”本身是个伪需求。实际泄漏排查中,真正有效的是:进程句柄总数趋势 + 按类型分布(如 Event、Section、Key) + 关键句柄生命周期日志。
- Windows:用
NtQuerySystemInformation定期采样,结合ObjectTypeIndex分类统计,异常增长时 dump 全量句柄并比对 - Linux:监控
/proc/pid/fd/数量 +lsof -p PID | wc -l辅助验证,重点关注 socket、inotify、timerfd 等易泄漏类型 - 两者都应配套应用层日志:比如每次
CreateEvent/eventfd()后打点,匹配CloseHandle/close(),缺失则告警
最常被忽略的一点:句柄泄漏往往不是单次忘记关闭,而是异常路径未覆盖(如构造函数抛异常导致析构不执行)。监控数据再准,也得配合 RAII 或作用域守卫(std::unique_handle、std::shared_fd)才能根治。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











