可通过NtQuerySystemInformation(需管理员权限和SeDebugPrivilege)枚举系统句柄表并统计条目数,或使用PDH API读取“Process(_Total)Handle Count”性能计数器(无需提权、兼容性高但为近似值)。

Windows 上怎么查进程的全局句柄总数(不是当前进程)
Windows 没有直接暴露“系统全局句柄总数”的公开 API,GetProcessHandleCount 只返回**当前进程**的句柄数,而系统级统计必须走内核对象枚举或性能计数器。真实需求里,多数人想监控的是整个系统的句柄资源压力,比如排查 ERROR_TOO_MANY_OPEN_FILES 类似现象,这时候得绕过用户态限制。
可靠路径只有一条:用 NTAPI 枚举所有句柄表项并去重统计类型。但注意——这需要 SeDebugPrivilege 权限,普通用户态程序默认拿不到;且微软不承诺该接口稳定,NtQuerySystemInformation 的 SystemHandleInformation 类型在不同 Windows 版本中结构微调(如 Win10 1903 后加了 ObjectPointer 字段)。
- 必须以管理员权限运行,否则
NtQuerySystemInformation返回STATUS_ACCESS_DENIED - 结构体定义要按实际系统版本对齐,
sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)在 Win11 和 Win7 上可能不同 - 枚举结果包含重复句柄(同一内核对象被多个进程引用),统计“总数”时应按
HandleValue+UniqueProcessId联合去重,而非只看对象地址
用 PDH 性能计数器快速获取近似值
如果只需要趋势判断(比如写监控脚本),PdhOpenQuery + PdhAddCounter 是更轻量的选择。Windows 性能库提供 Process(_Total)Handle Count 计数器,它反映的是所有进程句柄数之和(含重复),虽非严格意义上的“全局句柄表项数”,但和系统资源耗尽相关性极高,且无需提权。
实操要点:
立即学习“C++免费学习笔记(深入)”;
- 计数器路径必须用英文,即使系统区域设置是中文,硬编码
"\Process(_Total)\Handle Count" - 首次调用
PdhCollectQueryData后需等待 100–500ms 再读值,否则返回PDH_INVALID_DATA - 该值每秒更新一次,不适合毫秒级采样;若需高频采集,仍应回到
NtQuerySystemInformation方案
NtQuerySystemInformation 枚举句柄时常见崩溃原因
直接调用 NtQuerySystemInformation 容易 crash,根本原因是返回缓冲区大小预估失败或结构解析错位。Windows 并不保证每次返回相同长度数据,尤其当系统刚创建/关闭大量句柄时,两次调用间缓冲区需求可能翻倍。
安全做法:
- 先用
NULL缓冲区调用一次,捕获所需字节数(ntStatus == STATUS_INFO_LENGTH_MISMATCH) - 分配堆内存(不用栈),大小为返回值 + 若干余量(建议 +4096)
- 遍历结构体时,用
reinterpret_cast<byte>(buffer) + offset</byte>移动指针,别依赖sizeof累加——因为结构体含可变数组字段 - 遇到
ObjectTypeNumber == 0或HandleValue == 0的项直接跳过,它们是占位符或已失效项
跨平台兼容性问题与替代思路
C++ 项目若需兼顾 Linux/macOS,“全局句柄数”概念本身就不成立:Linux 用文件描述符(fd),由每个进程独立管理,系统级上限由 /proc/sys/fs/file-max 控制;macOS 则通过 sysctlbyname("kern.maxfiles") 查理论上限,但无实时已用数接口。
所以所谓“跨平台句柄统计”,实际只能做三件事:
- Windows:走
NtQuerySystemInformation或 PDH - Linux:汇总所有
/proc/[0-9]*/fd/目录下链接数(需 root),或读/proc/sys/fs/file-nr的第二字段(已分配未使用 fd 数) - macOS:无等效指标,只能监控单进程
getdtablesize()和ulimit -n,再结合lsof -n | wc -l估算
真正容易被忽略的是:Windows 句柄泄漏常伴随 WaitForSingleObject 调用后未关闭句柄,而 Linux fd 泄漏更多出现在 pipe/epoll_ctl 后忘记 close。统计数字只是表象,定位到具体进程+句柄类型(Event、Section、Mutant…)才真正有用。


















