未公开的函数 NtQuerySystemInformation

原文地址:http://hi.baidu.com/%BD%BB%D3%D1%C6%B5%B5%C0/blog/item/a0426c229a03aaa04623e8c7.html

 

闲了没事,用SoftIce跟踪 TaskMgr 居然看出点门道来,将这些见解和大家共享,文章可能很长,也可能要花很长的时间,反正爱看就看,不爱看就别看。
幸亏微软提供了相应的 .PDB 和 .DBG文件,使跟踪变的比较容易。本文所有提到的技术都属于微软,我只不过是读懂了然后用Delphi改写,自己没有什么技术,不得用于商业用途,否则老盖找你打官司,可别来找我。

使用未公开的函数 NtQuerySystemInformation 点滴(自己动手写任务管理器)

(如何自己写一个任务管理器,从显示到功能和Windows TaskMgr完全一样,包括从注册表中读取Windows TaskMgr的所有启动参数(170个字节),并增加一些TaskMgr没有的功能)
gzgzlxg 2005年4月12日

该文章为近一个月研究Windows 2000和Windows 2003的任务管理器的体会,研究还没有最后结束,但文章必须先写,否则许多东西不写下来就忘了。该文章将分若干篇来写,这是第一段。


NtQuerySystemInformation 是所谓 Undocuments 函数,主要用来获取系统各类信息。Windows 2000的任务管理器 TaskMgr 主要就是使用该函数来获取各类信息,如CPU使用率,内核使用率,句柄总数,线程总数,进程总数...等等在任务管理器中的几乎所有信息都是来自该函数。(当然也可以用别的函数完成类似的工作,如PDH,ToolHelp,或读取注册表等方法,各种方法中,应该以使用NtQuerySystemInformation 最好,这就是 TaskMgr 为什么使用该函数的原因。)
在MSDN知识库中是这样描写该函数的:
[NtQuerySystemInformation is available for use in Windows 2000 and Windows XP. It may be altered or unavailable in subsequent versions. Applications should use the alternate functions listed in this topic.]
但幸运的是,至少在Windows 2003 的任务管理器中仍然是使用该函数来获取系统各类信息的。
下面将具体讲述该函数在Delphi中的使用
1. 函数NtQuerySystemInformation 
NtQuerySystemInformation函数隶属Ntdll.dll,函数的调用非常复杂,有许多入口参数,MSDN知识库中基本都是一带而过,没有具体的说明,这里所写的都是自己具体使用的感受,和网站上一些少的可怜的资料,而这些可怜的资料也都是别人自己的体会,所以难免有错误,因此在具体使用中,如有任何问题,概不负责。

1.1函数的调用格式:
function NtQuerySystemInformation(
SystemInformationClass: TSystemInformationClass;
{ SystemInformationClass
[in] One of the values enumerated in SYSTEM_INFORMATION_CLASS, indicating the kind of system information to be retrieved.}
pSystemInformation: PVOID;
{ SystemInformation
[in, out] Points to a buffer where the requested information is/ to be returned. The size and structure of this information varies depending on the value of the SystemInformationClass parameter:}
uSystemInformationLength: ULONG;
{ SystemInformationLength
[in] Size of the buffer pointed to by the SystemInformation parameter, in bytes.}
puReturnLength: PULONG
{ ReturnLength
[out, optional] Optional pointer to a location where the function writes the actual size of the information requested. If that size is less than or equal to the SystemInformationLength parameter, the function copies the information into the SystemInformation buffer; otherwise, it returns an NTSTATUS error code and returns in ReturnLength the size of buffer required to receive the requested information. }
): NTSTATUS; stdcall;
{Return Values
Returns an NTSTATUS success or error code. The forms and significance of NTSTATUS error codes are listed in the Ntstatus.h header file available in the Windows Device Driver Kit (DDK), and are described in the DDK documentation under Kernel-Mode Driver Architecture / Design Guide / Driver Programming Techniques / Logging Errors.}

{uses a NtQuerySystemInformation call to obtain information about the Cache's settings and NtSetSystemInformation to set new sizing information. The working-set information for a process serves as guidelines for NT's Memory Manager egarding how many pages of/ physical memory should be assigned to the application. Because they are guidelines, conditions can result such that the Memory Manager grows a working-set to a size greater than the maximum, or shrinks it to less than the minimum. However, the settings are factors that will affect the overall allocation, and hence responsiveness, of an application. In the case of CacheSet the application is the file system Cache.}

1.2 参数说明:
NtQuerySystemInformation的调用参数非常多,我这里只列出在TaskMgr中调用的部分。

1.2.1 TSystemInformationClass
TSystemInformationClass有许多类,这里列出的是能够找到的,可能还有一些。
PSystemInformationClass = ^TSystemInformationClass;
_SYSTEM_INFORMATION_CLASS = (
SystemBasicInformation, 
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation, 
SystemPathInformation, 
SystemProcessInformation, 
SystemCallCountInformation, 
SystemConfigurationInformation, 
SystemProcessorPerformanceInformation, 
SystemGlobalFlag, 
SystemCallTimeInformation, 
SystemModuleInformation, 
SystemLockInformation, 
SystemStackTraceInformation, 
SystemPagedPoolInformation, 
SystemNonPagedPoolInformation, 
SystemHandleInformation, 
SystemObjectInformation, 
SystemPageFileInformation, 
SystemVdmInstemulInformation, 
SystemVdmBopInformation, 
SystemFileCacheInformation, 
SystemPoolTagInformation, 
SystemInterruptInformation, 
SystemDpcBehaviorInformation,
SystemFullMemoryInformation, 
SystemLoadGdiDriverInformation,
SystemUnloadGdiDriverInformation,
SystemTimeAdjustmentInformation,
SystemSummaryMemoryInformation,
SystemNextEventIdInformation, 
SystemEventIdsInformation, 
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemExtendServiceTableInformation, 
SystemPrioritySeperation, 
SystemPlugPlayBusInformation, 
SystemDockInformation, 
SystemPowerInformation, 
SystemProcessorSpeedInformation, 
SystemCurrentTimeZoneInformation, 
SystemLookasideInformation,
SystemSetTimeSlipEvent, 
SystemCreateSession, // set mode only
SystemDeleteSession, // set mode only
SystemInvalidInfoClass1, // invalid info class
SystemRangeStartInformation, // 0x0004 (fails if size != 4)
SystemVerifierInformation,
SystemAddVerifier,
SystemSessionProcessesInformation, // checked build only
MaxSystemInfoClass);
TSystemInformationClass = _SYSTEM_INFORMATION_CLASS;
这里用Delphi的枚举类型列出了 SYSTEM_INFORMAION_CLASS,这些命名除了少数在MSDN上有简单的说明外,其余的都是根据使用者或从别的途径得到的,所以在网上,你可以见到不同的叫法,我这里列出的命名是我个人认为比较合适的或者说我比较喜欢的。
在TaskMgr中只使用了下面列出的5个,本文将主要介绍这5个类的具体调用。
SystemBasicInformation // 0
SystemPerformanceInformation // 2
SystemProcessInformation // 5
SystemProcessorPerformanceInformation // 8
SystemFileCacheInformation // 21

1.2.2 SystemInformation
根据 SYSTEM_INFORMAION_CLASS 类,SystemInformation 是相应的结构,下面只列出在TaskMgr中使用的5个结构,用Delphi的格式列出:
//----------
//对应 SystemBasicInformation 0号调用
PSYSTEM_BASIC_INFORMATION = ^TSystemBasicInformation;
_SYSTEM_BASIC_INFORMATION = packed record
dwUnknown1: DWORD; //34
uKeMaximumIncrement: ULONG; //一个时钟的计量单位 //30
uPageSize: ULONG; //一个内存页的大小 //2c
uMmNumberOfPhysicalPages: ULONG; //系统管理着多少个页 //28
uMmLowestPhysicalPage: ULONG; //低端内存页 //24
uMmHighestPhysicalPage: ULONG; //高端内存页 //20
uAllocationGranularity: ULONG; //1c
pLowestUserAddress: Pointer; //低端用户地址 //18
pMmHighestUserAddress: Pointer; //高端用户地址 //14
uKeActiveProcessors: ULONG; //激活的处理器 //10
bKeNumberProcessors: BYTE; //有多少个处理器 //0c
bUnknown2: BYTE;
wUnknown3: WORD;
end;
TSystemBasicInformation = _SYSTEM_BASIC_INFORMATION;
SYSTEM_BASIC_INFORMATION = _SYSTEM_BASIC_INFORMATION;

//----------
//对应 SystemPerformanceInformation 2号调用
PSYSTEM_PERFORMANCE_INFORMATION = ^TSystemPerformanceInformation;
_SYSTEM_PERFORMANCE_INFORMATION = packed record
liIdleTime: LARGE_INTEGER;
IoReadTransferCount: LARGE_INTEGER;
IoWriteTransferCount: LARGE_INTEGER;
IoOtherTransferCount: LARGE_INTEGER;
IoReadOperationCount: ULONG;
IoWriteOperationCount: ULONG;
IoOtherOperationCount: ULONG;
AvailablePages: ULONG;
CommittedPages: ULONG;
CommitLimit: ULONG;
PeakCommitment: ULONG;
PageFaultCount: ULONG;
CopyOnWriteCount: ULONG;
TransitionCount: ULONG;
CacheTransitionCount: ULONG;
DemandZeroCount: ULONG;
PageReadCount: ULONG;
PageReadIoCount: ULONG;
CacheReadCount: ULONG;
CacheIoCount: ULONG;
DirtyPagesWriteCount: ULONG;
DirtyWriteIoCount: ULONG;
MappedPagesWriteCount: ULONG;
MappedWriteIoCount: ULONG;
PagedPoolPages: ULONG;
NonPagedPoolPages: ULONG;
PagedPoolAllocs: ULONG;
PagedPoolFrees: ULONG;
NonPagedPoolAllocs: ULONG;
NonPagedPoolFrees: ULONG;
FreeSystemPtes: ULONG;
ResidentSystemCodePage: ULONG;
TotalSystemDriverPages: ULONG;
TotalSystemCodePages: ULONG;
NonPagedPoolLookasideHits: ULONG;
PagedPoolLookasideHits: ULONG;
Spare3Count: ULONG;
ResidentSystemCachePage: ULONG;
ResidentPagedPoolPage: ULONG;
ResidentSystemDriverPage: ULONG;
CcFastReadNoWait: ULONG;
CcFastReadWait: ULONG;
CcFastReadResourceMiss: ULONG;
CcFastReadNotPossible: ULONG;
CcFastMdlReadNoWait: ULONG;
CcFastMdlReadWait: ULONG;
CcFastMdlReadResourceMiss: ULONG;
CcFastMdlReadNotPossible: ULONG;
CcMapDataNoWait: ULONG;
CcMapDataWait: ULONG;
CcMapDataNoWaitMiss: ULONG;
CcMapDataWaitMiss: ULONG;
CcPinMappedDataCount: ULONG;
CcPinReadNoWait: ULONG;
CcPinReadWait: ULONG;
CcPinReadNoWaitMiss: ULONG;
CcPinReadWaitMiss: ULONG;
CcCopyReadNoWait: ULONG;
CcCopyReadWait: ULONG;
CcCopyReadNoWaitMiss: ULONG;
CcCopyReadWaitMiss: ULONG;
CcMdlReadNoWait: ULONG;
CcMdlReadWait: ULONG;
CcMdlReadNoWaitMiss: ULONG;
CcMdlReadWaitMiss: ULONG;
CcReadAheadIos: ULONG;
CcLazyWriteIos: ULONG;
CcLazyWritePages: ULONG;
CcDataFlushes: ULONG;
CcDataPages: ULONG;
ContextSwitches: ULONG;
FirstLevelTbFills: ULONG;
SecondLevelTbFills: ULONG;
SystemCalls: ULONG;
end;
TSystemPerformanceInformation = _SYSTEM_PERFORMANCE_INFORMATION;
SYSTEM_PERFORMANCE_INFORMATION = _SYSTEM_PERFORMANCE_INFORMATION;

//----------
PVM_COUNTERS = ^TVmCounters;
_VM_COUNTERS = packed record
uPeakVirtualSize: ULONG;
uVirtualSize: ULONG;
uPageFaultCount: ULONG;
uPeakWorkingSetSize: ULONG;
uWorkingSetSize: ULONG;
uQuotaPeakPagedPoolUsage: ULONG;
uQuotaPagedPoolUsage: ULONG;
uQuotaPeakNonPagedPoolUsage: ULONG;
uQuotaNonPagedPoolUsage: ULONG;
uPagefileUsage: ULONG;
uPeakPagefileUsage: ULONG;
end;
TVmCounters = _VM_COUNTERS;
VM_COUNTERS = _VM_COUNTERS;

PIO_COUNTERSEX = ^TIoCountersex;
_IO_COUNTERSEX = packed record
ReadOperationCount: LARGE_INTEGER;
WriteOperationCount: LARGE_INTEGER;
OtherOperationCount: LARGE_INTEGER;
ReadTransferCount: LARGE_INTEGER;
WriteTransferCount: LARGE_INTEGER;
OtherTransferCount: LARGE_INTEGER;
end;
TIoCountersex = _IO_COUNTERSEX;
IO_COUNTERSEX = _IO_COUNTERSEX;

PSYSTEM_THREAD_INFORMATION = ^TSystemThreadInfo;
_SYSTEM_THREAD_INFORMATION = packed record
KernelTime: LARGE_INTEGER; // 100 nsec units //$000
UserTime: LARGE_INTEGER; // 100 nsec units //$008
CreateTime: LARGE_INTEGER; // relative to 01-01-1601 //$010
WaitTime: DWORD; //$018
pStartAddress: PVOID; //$01C
Cid: CLIENT_ID; // process/thread ids //$020
Priority: DWORD; //$028
BasePriority: DWORD; //$02C
ContextSwitches: DWORD; //$030
ThreadState: DWORD; // 2=running, 5=waiting //$034
WaitReason: DWORD; //KWAIT_REASON; //$038
uReserved01: DWORD; //$03C
end; //$040
TSystemThreadInfo = _SYSTEM_THREAD_INFORMATION;
SYSTEM_THREAD_INFORMATION = _SYSTEM_THREAD_INFORMATION;

PSYSTEM_PROCESS = ^TSystemProcess;
_SYSTEM_PROCESS = packed record // common members
uNext: DWORD; // relative offset //$000
ThreadCount: DWORD; //$004
Reserved01: LARGE_INTEGER; //$008
Reserved02: LARGE_INTEGER; //$010
Reserved03: LARGE_INTEGER; //$018
CreateTime: LARGE_INTEGER; // relative to 01-01-1601 //$020
UserTime: LARGE_INTEGER; // 100 nsec units //$028
KernelTime: LARGE_INTEGER; // 100 nsec units //$030
usName: UNICODE_STRING; //$038
BasePriority: DWORD; //KPRIORITY; //$040
UniqueProcessId: DWORD; //$044
InheritedFromUniqueProcessId: DWORD; //$048
HandleCount: DWORD; //$04C
SessionId: DWORD; //$050 W2K Only
Reserved08: DWORD; //$054
VmCounters: VM_COUNTERS; // see ntddk.h //$058
CommitCharge: DWORD; // bytes //$084
end; //$088
TSystemProcess = _SYSTEM_PROCESS;
SYSTEM_PROCESS = _SYSTEM_PROCESS;

PSYSTEM_PROCESS_NT4 = ^TSystemProcessNt4;
_SYSTEM_PROCESS_NT4 = packed record // Windows NT 4.0
Process: SYSTEM_PROCESS; // common members //$000
Threads: SYSTEM_THREAD_INFORMATION; // thread array //$088
end; //$088
TSystemProcessNt4 = _SYSTEM_PROCESS_NT4;
SYSTEM_PROCESS_NT4 = _SYSTEM_PROCESS_NT4;

PSYSTEM_PROCESS_NT5 = ^TSystemProcessNt5;
_SYSTEM_PROCESS_NT5 = packed record // Windows 2000
Process: SYSTEM_PROCESS; // common members //$000
IoCounters: IO_COUNTERSEX; // see ntddk.h //$088
aThreads: SYSTEM_THREAD_INFORMATION; // thread array //$0B8
end; //$0B8
TSystemProcessNt5 = _SYSTEM_PROCESS_NT5;
SYSTEM_PROCESS_NT5 = _SYSTEM_PROCESS_NT5;

//对应 SystemProcessInformation 5号调用
PSYSTEM_PROCESS_INFORMATION = ^TSystemProcessInformation;
_SYSTEM_PROCESS_INFORMATION = packed record
case Integer of
0: (Process_NT4: SYSTEM_PROCESS_NT4);
1: (Process_NT5: SYSTEM_PROCESS_NT5);
end;
TSystemProcessInformation = _SYSTEM_PROCESS_INFORMATION;
SYSTEM_PROCESS_INFORMATION = _SYSTEM_PROCESS_INFORMATION;

//----------
//对应 SystemProcessorPerformanceInformation 8号调用
PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = ^TSystemProcessorPerformanceInformation;
_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = packed record
IdleTime: LARGE_INTEGER;
KernelTime: LARGE_INTEGER;
UserTime: LARGE_INTEGER;
DpcTime: LARGE_INTEGER;
InterruptTime: LARGE_INTEGER;
InterruptCount: DWORD;
dwUnknown1: DWORD;
end;
TSystemProcessorPerformanceInformation = _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;

//----------
// 对应 SystemFileCacheInformation 21号调用

PSYSTEM_CACHE_INFORMATION = ^TSystemCacheInformation;
_SYSTEM_CACHE_INFORMATION = packed record
uFileCache: ULONG; // bytes
uFileCachePeak: ULONG; // bytes
PageFaultCount: ULONG;
MinimumWorkingSet: ULONG;
MaximumWorkingSet: ULONG;
TransitionSharedPages: ULONG;
TransitionSharedPagesPeak: ULONG;
Reserved: array[0..1] of ULONG;
end;
TSystemCacheInformation = _SYSTEM_CACHE_INFORMATION;
SYSTEM_CACHE_INFORMATION = _SYSTEM_CACHE_INFORMATION;
2. 任务管理器――》性能
在讲述性能前先大致描述任务管理器的三大部分。
任务管理器由【性能】、【进程】、【应用程序】三大部分组成。其实这三大部分是有机的联系在一起的,尤其是【性能】和【进程】,【性能】中除了CPU使用率以外,其余的数据都是由进程模块顺便取得的。
TaskMgr可能是一个叫David的人主持开发的,程序启动初始化的时候,将系统的 Button Class 类的WndProc的调用指向自己(DavesFrameWndProc),将类名改为DavesFrameClass,保存原来的地址,在处理完DavesFrameWndProc的信息后调用原来的过程,也就是所谓的钩子。David也许很喜欢各类水果,程序中有四个菜单是用水果来命名的,分别是 Apple、Banana、Peach、Pear。
2.1 获取CPU、内核、内存使用率、:
TaskMge 通过 NtQuerySystemInformation 的SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION 调用获得 CPU、内核使用率,SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION 的结构如下:
PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = ^TSystemProcessorPerformanceInformation;
_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = packed record
IdleTime: LARGE_INTEGER;
KernelTime: LARGE_INTEGER;
UserTime: LARGE_INTEGER;
DpcTime: LARGE_INTEGER;
InterruptTime: LARGE_INTEGER;
InterruptCount: DWORD;
dwUnknown1: DWORD;
end;
TSystemProcessorPerformanceInformation = _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION = _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;
这个结构为48个字节,当调用 SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION 时,返回的结果为 1536个字节,WINDOWS 操作系统最多允许配置 32 颗 CPU,所以返回的结果存放了 32 颗CPU的运行状态,每个CPU占48个字节,头尾相连。我见过许多文章都使用这个调用或部分的使用这个调用获取 CPU使用率,但都没有认识到这个调用的返回结果的长度,幸亏 WINDOWS 操作系统往往在一个新区域分配内存,所以即使你只申请了48个字节的内存来存放返回结果,在许多情况下不至于因为返回结果太长而覆盖了程序代码或其他的数据,但这确实是一个大的Bug,随时都会发生系统崩溃的致命错误。
内存使用率是通过 SYSTEM_PERFORMANCE_INFORMATION 调用获得。
计算公式如下:
CpuUsage = (IdleTime - PreviousCPUIdleTime) / (UserTime + KernelTime - PreviousCPUTotalTime);
KernelUsage = (KernelTime - IdleTime - PreviousCPUKernelTime) / (UserTime + KernelTime - PreviousCPUTotalTime); 
其中:
IdleTime = TSystemProcessorPerformanceInformation.IdleTime; // CPU 空闲时间
UserTime = TSystemProcessorPerformanceInformation.UserTime; // CPU 使用时间
KernelTime = TSystemProcessorPerformanceInformation.KernelTime; // 内核使用时间
PreviousCPUIdleTime = IdleTime; //上一次 CPU 空闲时间
PreviousCPUTotalTime = (UserTime + KernelTime); //上一次 CPU 总使用时间
PreviousCPUKernelTime = (KernelTime - IdleTime); //上一次内核时间
从上面的计算公式可以看出 TSystemProcessorPerformanceInformation.KernelTime 其实是 内核+空闲,这个命名很容易叫人产生误解。似乎应该取这样的名字比较合适:KernelAndIdle_Time;

下面是具体程序实现,这个程序完全是模仿 TaskMgr 写的,版权属于微软,这里只是从技术角度来研究,请不要用于商业用途。当然你硬要用,我也没有办法,但我声明了,我就推卸责任了,这也是跟微软学的。这里只列出了获取CPU 部分,其余部分将在后面阐明。
CalcCPUTime的命名也是微软的版权;
TaskMgr 在初始化过程时根据 CPU的个数,为每个 CPU 使用率和内核使用率各自固定分配了8000个字节,用于存放CPU和内核使用率的历史记录。每个新产生的使用率总是放在首位,然后利用memmove函数将历史记录向后移。在这里我采用了略微不同的方法,我建立了一个记录,并在记录中设立了记录长度,位置,和记录数三个参数。根据CPU历史记录绘图需要的记录长度动态的分配内存,并且也避免了每次使用memmove来移动记录,而只是移动记录的指针。
type
UsageRecord = packed record
uLength: Word; //记录的长度
Position: Word; //当前指针位置
ReNo: Word; //现在已经有的记录数
Usages: array of Word; // 使用率 动态数组根据绘图需要分配长度。
end;

var
SysBaseInfo: TSystemBasicInformation;
SysProcPerfInfo: array[0..31] of TSystemProcessorPerformanceInformation;
SysPerfInfo: TSystemPerformanceInformation; 
PreviousCPUIdleTime: array[0..31] of LARGE_INTEGER;
PreviousCPUTotalTime: array[0..31] of LARGE_INTEGER;
PreviousCPUKernelTime: array[0..31] of LARGE_INTEGER;
CPUUsage: DWORD;
KernelUsage: DWORD;
MEMUsage: DWORD;
CPUHistory: array of UsageRecord;
KernelHistory: array of UsageRecord;


procedure TFormMainWin.CalcCPUTime;
var
status: NTSTATUS;
SumCPUIdleTime: LARGE_INTEGER;
SumCPUTotalTime: LARGE_INTEGER;
SumCPUKernelTime: LARGE_INTEGER;
tmpCPUIdleTime: LARGE_INTEGER;
tmpCPUKernelTime: LARGE_INTEGER;
tmpCPUTotalTime: LARGE_INTEGER;
I: DWORD;
begin
//一次读取32颗CPU的数据,不管你实际安装了几颗CPU。
Status := NtQuerySystemInformation(SystemProcessorPerformanceInformation, @sysprocperfInfo,
SizeOf(TSystemProcessorPerformanceInformation) * 32, nil);
//如果 Status不等于零,发生错误,退出,这里非常奇怪,为什么不显示错误信息?例如相这样加一句:
//if status <> 0 then
// if MessageDlg('NtQuerySystemInformation ' + GetNTSTATUS(Status) + ' Exit now?',
// mtConfirmation, [mbYes, mbNo], 0) = mrYes then
// Exit;
// 其中 GetNtstatus(Status) 是将返回的错误号转换成错误解释
// 既然原程序不做处理,我们也不做处理,忠于原作。
if status <> 0 then
Exit;
I := 0;
SumCPUIdleTime.QuadPart := 0;
SumCPUKernelTime.QuadPart := 0;
SumCPUTotalTime.QuadPart := 0;
// 循环体,遍历每一个 CPU,如果有这么多
repeat
with SysProcPerfInfo[I] do
begin
//计算每一颗CPU 空闲时间
tmpCPUIdleTime.QuadPart := (IdleTime.QuadPart - PreviousCPUIdleTime[I].QuadPart);
//计算每一颗CPU 总使用时间 (空闲+内核+使用)
tmpCPUTotalTime.QuadPart := (UserTime.QuadPart + KernelTime.QuadPart) - PreviousCPUTotalTime[I].QuadPart;
//计算每一颗 CPU 内核使用时间
tmpCPUKernelTime.QuadPart := (KernelTime.QuadPart - IdleTime.QuadPart) - PreviousCPUKernelTime[I].QuadPart;
//所有CPU 空闲时间 Sum(0..CPU个数)(空闲时间)
SumCPUIdleTime.QuadPart := SumCPUIdleTime.QuadPart + tmpCPUIdleTime.QuadPart;
//所有CPU 总使用时间 Sum(0..CPU个数)(空闲+内核+使用)
SumCPUTotalTime.QuadPart := SumCPUTotalTime.QuadPart + tmpCPUTotalTime.QuadPart;
//所有 CPU 内核使用时间 Sum(0..CPU个数)(内核使用时间)
SumCPUKernelTime.QuadPart := SumCPUKernelTime.QuadPart + tmpCPUKernelTime.QuadPart;
//保存 CPU 空闲时间
PreviousCPUIdleTime[I].QuadPart := IdleTime.QuadPart;
//保存 CPU 总使用时间
PreviousCPUTotalTime[I].QuadPart := (UserTime.QuadPart + KernelTime.QuadPart);
//保存 CPU 内核使用时间
PreviousCPUKernelTime[I].QuadPart := KernelTime.QuadPart - IdleTime.QuadPart;
end;
//这段程序如果用Delphi实现编译后代码太长,因为任务管理器是实时采集CPU数据,程序应尽可能简练,
//所以保留了原TaskMgr的处理方法,这里采用了汇编语言来实现;

asm
MOV EAX, tmpCPUTotalTime.LowPart
MOV EDX, tmpCPUTotalTime.HighPart
OR EAX, EDX
JE @@exit
//计算CPU使用百分比
//入口参数
PUSH 0 //乘数的高位
PUSH $00000064 //乘数的低位 $64=100
PUSH tmpCPUIdleTime.HighPart //被乘数高位
PUSH tmpCPUIdleTime.LowPart //被乘数低位
CALL _allmul //_allmul 用于处理LARGE_INTEGER 的乘法函数,在ntdll.dll中
PUSH tmpCPUTotalTime.HighPart //除数高位
PUSH tmpCPUTotalTime.LowPart //除数低位
PUSH EDX           //被除数高位 _allmul的返回结果高位
PUSH EAX //被除数低位 _allmul的返回结果低位
CALL _alldiv //_alldiv 用于处理 LARGE_INTEGER 的除法函数,在ntdll.dll中
XOR EDX, EDX
MOV DL, $64
SUB DL, AL //CpuUsage = 100 - AL (_alldiv返回的结果)
PUSH EDX
POP CpuUsage //CpuUsage 百分数
//计算内核使用百分比和上面的类同
PUSH $00000000
PUSH $00000064
PUSH tmpCPUKernelTime.HighPart
PUSH tmpCPUKernelTime.LowPart
CALL _allmul
PUSH tmpCPUTotalTime.HighPart
PUSH tmpCPUTotalTime.LowPart
PUSH EDX
PUSH EAX
CALL _alldiv
XOR EDX, EDX
MOV DL, $64
SUB DL, AL
PUSH EDX
POP KernelUsage
@@exit:
end;
//保存CPU使用率,并移记录指针
if CPUHistory[i].Position = CPUHistory[i].uLength then
CPUHistory[i].Position := 0
else
inc(CPUHistory[i].Position);
CPUHistory[i].Usages[CPUHistory[i].Position] := CPUUsage;
if CPUHistory[i].ReNo < CPUHistory[i].uLength then
inc(CPUHistory[i].ReNo);
//保存内核使用率,并移动记录指针
if KernelHistory[i].Position = KernelHistory[i].uLength then
KernelHistory[i].Position := 0
else
inc(KernelHistory[i].Position);
KernelHistory[i].Usages[KernelHistory[i].Position] := KernelUsage;
if KernelHistory[i].ReNo < KernelHistory[i].uLength then
inc(KernelHistory[i].ReNo);
//CPU的个数加一
inc(I);
//Processors 是系统CPU的个数,在前面初始化单元中通过调用NtQuerySystemInformation的
//SYSTEM_BASES_INFORMATION功能获得
//遍历每一颗CPU
until I >= Processors;
//下面是计算所有CPU的平均(总)使用率,使用方法和上面相同。
I := Processors - 1;
asm
MOV EAX, SumCPUTotalTime.LowPart
MOV EDX, SumCPUTotalTime.HighPart
OR EAX, EDX
JE @@exit
PUSH I
PUSH $00000064
PUSH SumCPUIdleTime.HighPart
PUSH SumCPUIdleTime.LowPart
CALL _allmul
PUSH SumCPUTotalTime.HighPart
PUSH SumCPUTotalTime.LowPart
PUSH EDX
PUSH EAX
CALL _alldiv
XOR EDX, EDX
MOV DL, $64
SUB DL, AL
PUSH EDX
POP CpuUsage

PUSH $00000000
PUSH $00000064
PUSH SumCPUKernelTime.HighPart
PUSH SumCPUKernelTime.LowPart
CALL _allmul
PUSH SumCPUTotalTime.HighPart
PUSH SumCPUTotalTime.LowPart
PUSH EDX
PUSH EAX
CALL _alldiv
XOR EDX, EDX
MOV DL, $64
SUB DL, AL
PUSH EDX
POP KernelUsage
@@exit:
end;
//这里开始计算内存使用率
status := NtQuerySystemInformation(SystemPerformanceInformation, @SysPerfInfo, SizeOf(TSystemPerformanceInformation), nil);
if status <> 0 then
Exit;
// PageSize 页面大小是在前面初始化单元中通过调用NtQuerySystemInformation的
//SYSTEM_BASES_INFORMATION功能获得,
//内存使用率 = (页面大小 / 1024) * CommittedPages
MEMUsage := (PageSize shr 10) * SysPerfInfo.CommittedPages;
//保存内存使用率,并移动记录指针
if MEMHistory.Position = MEMHistory.uLength then
MEMHistory.Position := 0
else
inc(MEMHistory.Position);
MEMHistory.Usages[MEMHistory.Position] := tmpMem;
if MEMHistory.ReNo < MEMHistory.uLength then
inc(MEMHistory.ReNo);
end;
注:1、这些程序在后面出现时也许会有不同,因为这些程序都是在写这篇东西时根据需要而写的,并不是完整程序中的一部分,随着讨论问题的深入,或许会修正一些地方,后面就不在特别说明了。
2、因为是一边写一边帖(包括程序),所以会很慢,大家给点耐性。
下一段将讨论如何取得性能中的其他数据:
句柄数、线程数、进程数、物理内存总数、可用数系统缓存、认可用量总数、限制、峰值、核心内存总数、分页数、未分页。
另外,这些程序在我的文档中排列是非常整齐的,不知为什么一贴上来就乱七八糟了,这可不是我的错

2.2 获取 句柄数、线程数、进程数、物理内存总数、可用数,系统缓存、认可用量总数、限制、峰值、核心内存总数、分页数、未分页。
TaskMgr 调用 NtQuerySystemInformation 的 SystemProcessInformation Class类 通过结构 SYSTEM_PROCESS_INFORMATION 返回结果,SYSTEM_PROCESS_INFORMATION 的结构如上显示,非常复杂,我在上面列出了两个版本的结构,随着Windows的升级,结构会不断的发生变化。
SystemProcessInformation 的具体调用和上面讨论的 SystemProcessorPerformanceInformation 有异曲同工之妙,但更为复杂,下面我们分析一下 SYSTEM_PROCESS_INFORMATION 的结构,我们略去第四版,从第五版讨论:
SYSTEM_PROCESS_INFORMATION 结构的简化表示如下:

PSYSTEM_PROCESS_INFORMATION = ^TSystemProcessInformation;
_SYSTEM_PROCESS_INFORMATION = packed record
Process: SYSTEM_PROCESS; // common members //$000
IoCounters: IO_COUNTERSEX; // see ntddk.h //$088
aThreads: SYSTEM_THREAD_INFORMATION; // thread array //$0B8
end; //$0B8
TSystemProcessInformation = _SYSTEM_PROCESS_INFORMATION;
SYSTEM_PROCESS_INFORMATION = _SYSTEM_PROCESS_INFORMATION;

当中去掉了第四版的声明,其中Process: SYSTEM_PROCESS 是最早版的结构,后来不断的扩充,增加了 IoCounters,和aThreads,其中 aThreads,的声明其实不太正确,正确的应该如下:
aThreads: array[0..0] of SYSTEM_THREAD_INFORMATION;

aThreads: array of SYSTEM_THREAD_INFORMATION;
既是一个动态结构,为什么改成现在这样,主要是因为在计算结构长度时 Delpyi 的 SizeOf 不能返回动态结构的长度,所以才有这样的结果。
从上面的对athreads的声明,可以看出,SYSTEM_PROCESS_INFORMATION 是一个不定长的结构,它的具体长度取决于系统有多少进程,每个进程有多少线程。
我在网上查阅资料时,发现所有使用这个结构的应用,都没有正确的调用这个结构,原因是什么,老盖真厉害,一个 Undocuments 就把你搞晕了。
因为这个调用返回结果长度是不可确定的,按正常的调用,将在程序中造成一个大的 Bug,调用返回的结果有可能覆盖你的程序代码区,你的堆栈区,你的数据区,引发系统崩溃,其危险性更胜前一个,所以它的调用要采取特殊的方法。
返回结果的结构大致如下:
第一个进程:
TSystemProcessInformation.Process.Next; //下一个记录的偏移量
...
...
第一个进程的第一个线程:
TSystemProcessInformation.aThreads.
....
....
第二个进程:
TSystemProcessInformation.Process.Next;
第二个进程的第一个线程:
TSystemProcessInformation.aThreads.
...
...
第N个进程:
TSystemProcessInformation.aThreads.
...
...
第N个进程的第一个线程:
TSystemProcessInformation.aThreads.
...
...
最后一个进程:
TSystemProcessInformation.aThreads.
...
...
最后一个的第一个线程:
TSystemProcessInformation.aThreads.
...
...
结束。
现在一切都明了了,我想大部分的人都会完成这个调用了,自己动手写一个试试。注意:将记录放在那里,弄不好就把你的程序覆盖了。
要知后事如何,请看下回分解。看看你写的对不对。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值