首先, 感覺是個蛋疼的話題, 不過是做某個軟件遇到的.
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/