首先, 感觉是个蛋疼的话题, 不过是做某个软件遇到的.
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 lpUserTime13 );
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 TokenHandle11 );12 13 BOOL WINAPI LookupPrivilegeValue(14 _In_opt_ LPCTSTR lpSystemName,15 _In_ LPCTSTR lpName,16 _Out_ PLUID lpLuid17 );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 ReturnLength26 );
下面是获取进程CPU percent的完整代码:
1 //CpuUsage.h 2 #ifndef _CPU_USAGE_H_ 3 #define _CPU_USAGE_H_ 4 5 #include6 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 44 #include "CPUusage.h" 45 #include 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_USERTEXTIMES102 {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 #endif158 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;//milliseconds210 211 ULONGLONG dwCurrentTickCount = GetTickCount();212 return (dwCurrentTickCount - m_dwLastRun) > minElapsedMS;213 }214 #ifndef USE_DEPRECATED_FUNCS215 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 #endif239 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 code300 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 message316 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.cpp331 332 #include "cpuusage.h"333 #include 334 #include 335 #include 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: \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: