首先, 感觉是个蛋疼的话题, 不过是做某个软件遇到的.
Windows系统的TaskManager里面其实就有这个功能, 显示一个进程的相关信息, 诸如pid,CPU占用率, 内存, 线程数等.
那么TaskManager是怎么求出某个进程的CPU占用率的呢? 用的NtQuerySystemInformation, NtQueryInformationProcess等吧, 貌似有人Debug过, 看到了这些函数的调用.
不过使用NtQuerySystemInformation这样的函数是有问题的, 1它们不是public的, 2是官方说它们在Vista以后的版本里面可能被修改.
一个可观的替代方案是使用GetSystemTimes和GetProcessTimes:
1 BOOL WINAPI GetSystemTimes( 2 _Out_opt_ LPFILETIME lpIdleTime, 3 _Out_opt_ LPFILETIME lpKernelTime, 4 _Out_opt_ LPFILETIME lpUserTime 5 ); 6 7 BOOL WINAPI GetProcessTimes( 8 _In_ HANDLE hProcess, 9 _Out_ LPFILETIME lpCreationTime, 10 _Out_ LPFILETIME lpExitTime, 11 _Out_ LPFILETIME lpKernelTime, 12 _Out_ LPFILETIME lpUserTime 13 );
GetSystemTimes获得系统(自开机以来)处于Kernel状态下面的CPU时间,以及系统处于User状态下的时间,以及Idle的时间.我们只用Kernel时间和User时间, 不用Idle时间.
相应的, GetProcess也能求出一个进程在上面3中状态下的时间.
下面公式可以求出进程的CPU占用率.
但是事情并没有就此结束, 因为我发现仅仅做上面的工作的话, 在某些系统(当然还是Windows系列...)下面会不奏效, 这就是很多软件开发者考虑的兼容性的问题.
是权限的问题, OpenProcess调用失败是因为你启动的进程没有能够Debug某个进程的权限.
虽然Admin下面有些权限是有的, 但默认的情况下进程的一些访问权限是没有被置为可用状态(即Enabled)的,所以我们要做的首先是使这些权限可用。
下面3个函数基本可以搞定:OpenProcessToken, LookupPrivilegeValue ,AdjustTokenPrivileges(详情见msdn..)
1 HANDLE WINAPI OpenProcess( 2 _In_ DWORD dwDesiredAccess, 3 _In_ BOOL bInheritHandle, 4 _In_ DWORD dwProcessId 5 ); 6 7 BOOL WINAPI OpenProcessToken( 8 _In_ HANDLE ProcessHandle, 9 _In_ DWORD DesiredAccess, 10 _Out_ PHANDLE TokenHandle 11 ); 12 13 BOOL WINAPI LookupPrivilegeValue( 14 _In_opt_ LPCTSTR lpSystemName, 15 _In_ LPCTSTR lpName, 16 _Out_ PLUID lpLuid 17 ); 18 19 BOOL WINAPI AdjustTokenPrivileges( 20 _In_ HANDLE TokenHandle, 21 _In_ BOOL DisableAllPrivileges, 22 _In_opt_ PTOKEN_PRIVILEGES NewState, 23 _In_ DWORD BufferLength, 24 _Out_opt_ PTOKEN_PRIVILEGES PreviousState, 25 _Out_opt_ PDWORD ReturnLength 26 );
下面是获取进程CPU percent的完整代码:

1 //CpuUsage.h 2 #ifndef _CPU_USAGE_H_ 3 #define _CPU_USAGE_H_ 4 5 #include <windows.h> 6 7 class CpuUsage 8 { 9 public: 10 CpuUsage(DWORD dwProcessID); 11 ULONGLONG GetUsageEx(); 12 ULONGLONG GetSystemNonIdleTimes(); 13 ULONGLONG GetProcessNonIdleTimes(); 14 private: 15 ULONGLONG SubtractTimes(const FILETIME& ftA, const FILETIME& ftB); 16 ULONGLONG AddTimes(const FILETIME& ftA, const FILETIME& ftB); 17 bool EnoughTimePassed(); 18 inline bool IsFirstRun() const { return (m_dwLastRun == 0); } 19 20 //system total times 21 FILETIME m_ftPrevSysKernel; 22 FILETIME m_ftPrevSysUser; 23 24 //process times 25 FILETIME m_ftPrevProcKernel; 26 FILETIME m_ftPrevProcUser; 27 28 ULONGLONG m_ullPrevSysNonIdleTime;//这个变量和后面的便利记录上次获取的非idle的系统cpu时间和进程cpu时间. 29 ULONGLONG m_ullPrevProcNonIdleTime;//这个类只绑定一个进程, 在构造函数里面初始化进来.. 30 31 ULONGLONG m_nCpuUsage; 32 ULONGLONG m_dwLastRun; 33 DWORD m_dwProcessID; 34 HANDLE m_hProcess; 35 volatile LONG m_lRunCount; 36 }; 37 38 #endif 39 40 41 //CpuUsage.cpp 42 43 #include <windows.h> 44 #include "CPUusage.h" 45 #include <strsafe.h> 46 //#define USE_DEPRECATED_FUNCS 47 void ErrorMsg(LPTSTR lpszFunction); 48 BOOL SetPrivilege(HANDLE hProcess, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege); 49 50 #ifdef USE_DEPRECATED_FUNCS 51 #define SystemBasicInformation 0 52 #define SystemPerformanceInformation 2 53 #define SystemTimeInformation 3 54 #define SystemProcessorPerformanceInformation 8 55 #define ProcessTimes 4 56 57 #define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 + (double)((x).LowPart)) 58 59 typedef struct 60 { 61 DWORD dwUnknown1; 62 ULONG uKeMaximumIncrement; 63 ULONG uPageSize; 64 ULONG uMmNumberOfPhysicalPages; 65 ULONG uMmLowestPhysicalPage; 66 ULONG uMmHighestPhysicalPage; 67 ULONG uAllocationGranularity; 68 PVOID pLowestUserAddress; 69 PVOID pMmHighestUserAddress; 70 ULONG uKeActiveProcessors; 71 BYTE bKeNumberProcessors; 72 BYTE bUnknown2; 73 WORD wUnknown3; 74 } SYSTEM_BASIC_INFORMATION; 75 76 typedef struct 77 { 78 LARGE_INTEGER liIdleTime; 79 DWORD dwSpare[312]; 80 } SYSTEM_PERFORMANCE_INFORMATION; 81 82 typedef struct 83 { 84 LARGE_INTEGER liKeBootTime; 85 LARGE_INTEGER liKeSystemTime; 86 LARGE_INTEGER liExpTimeZoneBias; 87 ULONG uCurrentTimeZoneId; 88 DWORD dwReserved; 89 } SYSTEMTEXTIME_INFORMATION; 90 91 typedef struct 92 _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION 93 { 94 LARGE_INTEGER IdleTime; 95 LARGE_INTEGER KernelTime; 96 LARGE_INTEGER UserTime; 97 LARGE_INTEGER Reserved1[2]; 98 ULONG Reserved2; 99 } SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; 100 101 typedef struct _KERNEL_USERTEXTIMES 102 { 103 LARGE_INTEGER CreateTime; 104 LARGE_INTEGER ExitTime; 105 LARGE_INTEGER KernelTime; 106 LARGE_INTEGER UserTime; 107 } KERNEL_USERTEXTIMES, *PKERNEL_USERTEXTIMES; 108 109 typedef LONG (WINAPI *PROCNTQSI)(UINT, PVOID, ULONG, PULONG); 110 PROCNTQSI NtQuerySystemInformation; 111 112 typedef LONG (WINAPI *PROCNTQIP)(HANDLE, UINT, PVOID, ULONG, PULONG); 113 PROCNTQIP NtQueryInformationProcess; 114 115 ULONGLONG CpuUsage::GetSystemNonIdleTimes() 116 { 117 SYSTEM_PERFORMANCE_INFORMATION SysPerfInfo; 118 SYSTEMTEXTIME_INFORMATION SysTimeInfo; 119 SYSTEM_BASIC_INFORMATION SysBaseInfo; 120 SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION SysProcPerfInfo[32]; 121 LONG status; 122 NtQuerySystemInformation = (PROCNTQSI)GetProcAddress(GetModuleHandle(TEXT("ntdll")), "NtQuerySystemInformation"); 123 if (!NtQuerySystemInformation) 124 return 0; 125 status = NtQuerySystemInformation(SystemBasicInformation, &SysBaseInfo, sizeof(SysBaseInfo), NULL); 126 if (status != NO_ERROR) 127 { 128 MessageBox(TEXT("FailSystemInfo")); 129 return 0; 130 } 131 status = NtQuerySystemInformation(SystemProcessorPerformanceInformation, SysProcPerfInfo, sizeof(SysProcPerfInfo), NULL); 132 if(status != NO_ERROR) return 0; 133 int nProcessors = SysBaseInfo.bKeNumberProcessors; //机器内部CPU的个数 134 ULONGLONG ullSysTotal = 0; 135 for(int i = 0; i < nProcessors; i++) 136 { 137 ullSysTotal += SysProcPerfInfo[i].KernelTime.QuadPart + SysProcPerfInfo[i].UserTime.QuadPart; 138 } 139 return ullSysTotal; 140 } 141 142 ULONGLONG CpuUsage::GetProcessNonIdleTimes() 143 { 144 KERNEL_USERTEXTIMES KernelUserTimes; 145 ::ZeroMemory(&KernelUserTimes, sizeof(KernelUserTimes)); 146 NtQueryInformationProcess = (PROCNTQIP)GetProcAddress(GetModuleHandle(TEXT("ntdll")), "NtQueryInformationProcess"); 147 LONG status = NtQueryInformationProcess(m_hProcess, ProcessTimes, &KernelUserTimes, sizeof(KernelUserTimes), NULL); 148 if(status == 0) 149 { 150 ErrorExit(TEXT("GetProcessNonIdleTimes")); 151 return 0; 152 } 153 return KernelUserTimes.KernelTime.QuadPart + KernelUserTimes.UserTime.QuadPart; 154 155 } 156 157 #endif 158 159 CpuUsage::CpuUsage(DWORD dwProcessID) 160 : m_nCpuUsage(0), 161 m_dwLastRun(0), 162 m_lRunCount(0), 163 m_dwProcessID(dwProcessID), 164 m_ullPrevProcNonIdleTime(0), 165 m_ullPrevSysNonIdleTime(0) 166 { 167 HANDLE hProcess = GetCurrentProcess(); 168 SetPrivilege(hProcess, SE_DEBUG_NAME, TRUE); 169 170 m_hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION , TRUE, m_dwProcessID); 171 if(m_hProcess == 0) 172 { 173 ErrorMsg(TEXT("OpenProcess")); 174 } 175 ZeroMemory(&m_ftPrevSysKernel, sizeof(FILETIME)); 176 ZeroMemory(&m_ftPrevSysUser, sizeof(FILETIME)); 177 178 ZeroMemory(&m_ftPrevProcKernel, sizeof(FILETIME)); 179 ZeroMemory(&m_ftPrevProcUser, sizeof(FILETIME)); 180 } 181 182 183 ULONGLONG CpuUsage::SubtractTimes(const FILETIME &ftA, const FILETIME &ftB) 184 { 185 LARGE_INTEGER a, b; 186 a.LowPart = ftA.dwLowDateTime; 187 a.HighPart = ftA.dwHighDateTime; 188 189 b.LowPart = ftB.dwLowDateTime; 190 b.HighPart = ftB.dwHighDateTime; 191 192 return a.QuadPart - b.QuadPart; 193 } 194 195 ULONGLONG CpuUsage::AddTimes(const FILETIME &ftA, const FILETIME &ftB) 196 { 197 LARGE_INTEGER a, b; 198 a.LowPart = ftA.dwLowDateTime; 199 a.HighPart = ftA.dwHighDateTime; 200 201 b.LowPart = ftB.dwLowDateTime; 202 b.HighPart = ftB.dwHighDateTime; 203 204 return a.QuadPart + b.QuadPart; 205 } 206 207 bool CpuUsage::EnoughTimePassed() 208 { 209 const int minElapsedMS = 250;//milliseconds 210 211 ULONGLONG dwCurrentTickCount = GetTickCount(); 212 return (dwCurrentTickCount - m_dwLastRun) > minElapsedMS; 213 } 214 #ifndef USE_DEPRECATED_FUNCS 215 216 ULONGLONG CpuUsage::GetSystemNonIdleTimes() 217 { 218 FILETIME ftSysIdle, ftSysKernel, ftSysUser; 219 if(!GetSystemTimes(&ftSysIdle, &ftSysKernel, &ftSysUser)) 220 { 221 ErrorMsg(TEXT("GetSystemTimes")); 222 return 0; 223 } 224 return AddTimes(ftSysKernel, ftSysUser); 225 } 226 227 228 ULONGLONG CpuUsage::GetProcessNonIdleTimes() 229 { 230 FILETIME ftProcCreation, ftProcExit, ftProcKernel, ftProcUser; 231 if(!GetProcessTimes(m_hProcess, &ftProcCreation, &ftProcExit, &ftProcKernel, &ftProcUser) && false) 232 { 233 ErrorMsg(TEXT("GetProcessNonIdleTimes")); 234 return 0; 235 } 236 return AddTimes(ftProcKernel, ftProcUser); 237 } 238 #endif 239 240 ULONGLONG CpuUsage::GetUsageEx() 241 { 242 ULONGLONG nCpuCopy = m_nCpuUsage; 243 if (::InterlockedIncrement(&m_lRunCount) == 1) 244 { 245 if (!EnoughTimePassed()) 246 { 247 ::InterlockedDecrement(&m_lRunCount); 248 return nCpuCopy; 249 } 250 ULONGLONG ullSysNonIdleTime = GetSystemNonIdleTimes(); 251 ULONGLONG ullProcNonIdleTime = GetProcessNonIdleTimes(); 252 if (!IsFirstRun()) 253 { 254 ULONGLONG ullTotalSys = ullSysNonIdleTime - m_ullPrevSysNonIdleTime; 255 if(ullTotalSys == 0) 256 { 257 ::InterlockedDecrement(&m_lRunCount); 258 return nCpuCopy; 259 } 260 m_nCpuUsage = ULONGLONG((ullProcNonIdleTime - m_ullPrevProcNonIdleTime) * 100.0 / (ullTotalSys)); 261 m_ullPrevSysNonIdleTime = ullSysNonIdleTime; 262 m_ullPrevProcNonIdleTime = ullProcNonIdleTime; 263 } 264 m_dwLastRun = (ULONGLONG)GetTickCount(); 265 nCpuCopy = m_nCpuUsage; 266 } 267 ::InterlockedDecrement(&m_lRunCount); 268 return nCpuCopy; 269 } 270 271 BOOL SetPrivilege(HANDLE hProcess, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 272 { 273 HANDLE hToken; 274 if(!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken)) 275 { 276 ErrorMsg(TEXT("OpenProcessToken")); 277 return FALSE; 278 } 279 LUID luid; 280 if(!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) 281 { 282 ErrorMsg(TEXT("LookupPrivilegeValue")); 283 return FALSE; 284 } 285 TOKEN_PRIVILEGES tkp; 286 tkp.PrivilegeCount = 1; 287 tkp.Privileges[0].Luid = luid; 288 tkp.Privileges[0].Attributes = (bEnablePrivilege) ? SE_PRIVILEGE_ENABLED : FALSE; 289 if(!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) 290 { 291 ErrorMsg(TEXT("AdjustTokenPrivileges")); 292 return FALSE; 293 } 294 return TRUE; 295 } 296 297 void ErrorMsg(LPTSTR lpszFunction) 298 { 299 // Retrieve the system error message for the last-error code 300 301 LPVOID lpMsgBuf; 302 LPVOID lpDisplayBuf; 303 DWORD dw = GetLastError(); 304 305 FormatMessage( 306 FORMAT_MESSAGE_ALLOCATE_BUFFER | 307 FORMAT_MESSAGE_FROM_SYSTEM | 308 FORMAT_MESSAGE_IGNORE_INSERTS, 309 NULL, 310 dw, 311 LANG_USER_DEFAULT, 312 (LPTSTR) &lpMsgBuf, 313 0, NULL ); 314 315 // Display the error message 316 317 lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 318 (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); 319 StringCchPrintf((LPTSTR)lpDisplayBuf, 320 LocalSize(lpDisplayBuf) / sizeof(TCHAR), 321 TEXT("%s failed with error %d: %s"), 322 lpszFunction, dw, lpMsgBuf); 323 MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 324 325 LocalFree(lpMsgBuf); 326 LocalFree(lpDisplayBuf); 327 ExitProcess(dw); 328 } 329 330 //main.cpp 331 332 #include "cpuusage.h" 333 #include <fstream> 334 #include <cstdlib> 335 #include <cstdio> 336 using namespace std; 337 338 const int second = 1000; 339 340 int main(int argc, char* argv[]) 341 { 342 if(argc != 2) 343 { 344 printf("Use the toolkit like: <toolkit name> <pid>\n"); 345 return 0; 346 } 347 DWORD dwProcId = atoi(argv[1]); 348 CpuUsage cu(dwProcId); 349 SYSTEMTIME st; 350 while(true) 351 { 352 GetLocalTime(&st); 353 printf("Process(pid:%d) uses %I64d%% cpu at %02d:%02d.%02d\n", dwProcId, cu.GetUsageEx(), st.wHour, st.wMinute, st.wSecond); 354 ::Sleep(second); 355 } 356 }
搞上面那坨代码的时候我发现了ProcessHacker, 它是个开源版本的Process Explorer, 如果你对Windows里面那些乌七八糟的东西感兴趣的话, 可以看看.
ProcessHacker: http://processhacker.sourceforge.net/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能