今天组长让我对动态调整帧率的代码进行核查,然后发现之前写的代码完全不能起到统计CPU占用率的作用,因为GetThreadTIme这个函数居然有15ms的误差(多核CPU的情况下,单核有10ms的误差)。之前的想法是通过获得线程的运行时间除以总共经过的时间来求线程占用CPU的比率,但是有个问题给忽略了,用这种方法会得到相反的结果。因为CPU空闲时线程运行的时间会增加,而CPU忙时反而会减小。所以之前的代码首先就有逻辑错误,而且这个函数本身就有很大的误差。所以祭起google大神,搜索了一把,发现原来微软隐藏了一个内核函数,这个函数可以获得进程的全部信息包括CPU占用率以及内存占用情况。好吧,其实任务管理器就是基于这个函数的。可恶的微软……

闲话不多说了,直接上代码吧。

#include <windows.h>
#include <conio.h>
#include <stdio.h>

#define SystemBasicInformation 0
#define SystemPerformanceInformation 2
#define SystemTimeInformation 3

#define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 + (double)((x).LowPart))

typedef struct
{
    DWORD dwUnknown1;
    ULONG uKeMaximumIncrement;
    ULONG uPageSize;
    ULONG uMmNumberOfPhysicalPages;
    ULONG uMmLowestPhysicalPage;
    ULONG uMmHighestPhysicalPage;
    ULONG uAllocationGranularity;
    PVOID pLowestUserAddress;
    PVOID pMmHighestUserAddress;
    ULONG uKeActiveProcessors;
    BYTE bKeNumberProcessors;
    BYTE bUnknown2;
    WORD wUnknown3;
} SYSTEM_BASIC_INFORMATION;

typedef struct
{
    LARGE_INTEGER liIdleTime;
    DWORD dwSpare[76];
} SYSTEM_PERFORMANCE_INFORMATION;

typedef struct
{
    LARGE_INTEGER liKeBootTime;
    LARGE_INTEGER liKeSystemTime;
    LARGE_INTEGER liExpTimeZoneBias;
    ULONG uCurrentTimeZoneId;
    DWORD dwReserved;
} SYSTEM_TIME_INFORMATION;
// ntdll!NtQuerySystemInformation (NT specific!)
//
// The function copies the system information of the
// specified type into a buffer
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// NtQuerySystemInformation(
// IN UINT SystemInformationClass, // information type
// OUT PVOID SystemInformation, // pointer to buffer
// IN ULONG SystemInformationLength, // buffer size in bytes
// OUT PULONG ReturnLength OPTIONAL // pointer to a 32-bit
// // variable that receives
// // the number of bytes
// // written to the buffer
// );
typedef LONG (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG);

PROCNTQSI NtQuerySystemInformation;
void main(void)
{
    SYSTEM_PERFORMANCE_INFORMATION SysPerfInfo;
    SYSTEM_TIME_INFORMATION SysTimeInfo;
    SYSTEM_BASIC_INFORMATION SysBaseInfo;
    double dbIdleTime;
    double dbSystemTime;
    LONG status;
    LARGE_INTEGER liOldIdleTime = {0,0};
    LARGE_INTEGER liOldSystemTime = {0,0};
  
    NtQuerySystemInformation = (PROCNTQSI)GetProcAddress(GetModuleHandle(TEXT(“ntdll”)),”NtQuerySystemInformation”);
    if (!NtQuerySystemInformation)
        return;
  
    // get number of processors in the system

  
    printf(“\nCPU Usage (press any key to exit) \n”);
    while(!_kbhit())
    {
  status = NtQuerySystemInformation(SystemBasicInformation,&SysBaseInfo,sizeof(SysBaseInfo),NULL);
  if (status != NO_ERROR)
        return;
        // get new system time
        status = NtQuerySystemInformation(SystemTimeInformation,&SysTimeInfo,sizeof(SysTimeInfo),0);
        if (status!=NO_ERROR)
            return;
      
        // get new CPU’s idle time
        status =NtQuerySystemInformation(SystemPerformanceInformation,&SysPerfInfo,sizeof(SysPerfInfo),NULL);
        if (status != NO_ERROR)
            return;
      
        // if it’s a first call – skip it
        if (liOldIdleTime.QuadPart != 0)
        {
            // CurrentValue = NewValue – OldValue
            dbIdleTime = Li2Double(SysPerfInfo.liIdleTime) – Li2Double(liOldIdleTime);
            dbSystemTime = Li2Double(SysTimeInfo.liKeSystemTime) –
              
                Li2Double(liOldSystemTime);
          
            // CurrentCpuIdle = IdleTime / SystemTime
            dbIdleTime = dbIdleTime / dbSystemTime;
          
            // CurrentCpuUsage% = 100 – (CurrentCpuIdle * 100) / NumberOfProcessors
            dbIdleTime = 100.0 – dbIdleTime * 100.0 /
              
                (double)SysBaseInfo.bKeNumberProcessors + 0.5;

            printf(“CPU Usage (press any key to exit) : %3d%% \n”,(UINT)dbIdleTime);
        }
      
        // store new CPU’s idle and system time
        liOldIdleTime = SysPerfInfo.liIdleTime;
        liOldSystemTime = SysTimeInfo.liKeSystemTime;
      
        // wait one second
        Sleep(1000);
    }
    printf(“\n”);
}

由于比较晚了,所以直接引用了一个大牛的代码。注释中说这个函数只能用在NT系统上。但是经过我的测试在Win7下也是正常使用的,至少得到CPU占用率是没有问题的。

有兴趣的同学们可以深入研究下这个函数和相关结构体,然后应该可以自己写一个类似任务管理器中的性能监视程序。

最后还有一点,这个函数在程序中调用不可以太频繁,因为这是一个内核函数,每次调用都会涉及到用户态到内核态的切换,这个是比较耗时间的。这也解释了为啥任务管理器里那个CPU占用率图标刷新的比较慢的原因(普通更新速率大约是1s更新一次)。

OK,写完了,睡觉去了,看看明天还能有什么收获跟大家共享。

About Sextant

爱好图形学,爱好游戏,希望与大家一起学习,一起分享经验。

发表评论

电子邮件地址不会被公开。

Post Navigation