WIN-PROCFS
要是三四年前要问我最善于什么编程技术,毫无疑问 Windows API。 其实我 WIN32 的熟练程度根本不能与 C# 和 PHP 比,但是因为 WIN32 了解的人比较少,所以显得最拿得出手。
WIN32 是一套成熟的 API 体系,如果把它看成是一套类库就太小看了。WIN32 写程序很麻烦,比 C# 什么麻烦的多,但并非 C++ 做不到,而是 WIN32 提供的是从操作系统层面的接口。 比如一般的类库会提供类似命令 dir(或者ls)的函数,WIN32 却不提供,你需要调用多个函数实现迭代查找,只因为 dir 这种接口不可控,这就是 WIN32 的特点。
WIN-PROCFS
WIN-PROCFS 主要目的是实现 WINDOWS 系统监控,但是不使用 WMI,因为毕竟考虑到 COMLESS 环境,WMI 可能在极端情况下无法执行。 所以直接调用 WIN32 API 实现系统状态信息汇总,类似 PROCFS,且优先使用 WINDOWS 通用 API,其次使用 NT 特有 API。 [!code=vc]
内存信息获取
WinBase.h 下的 GlobalMemoryStatusEx,返回 MEMORYSTATUSEX 结构体。
MEMORYSTATUSEX memory_status_ex;
memory_status_ex.dwLength = sizeof (MEMORYSTATUSEX);
GlobalMemoryStatusEx(&memory_status_ex);
_tprintf(_T("MemTotal: %I64d\n"), memory_status_ex.ullTotalPhys);
_tprintf(_T("MemFree: %I64d\n"), memory_status_ex.ullAvailPhys);
_tprintf(_T("SwapTotal: %I64d\n"), memory_status_ex.ullTotalPageFile);
_tprintf(_T("SwapFree: %I64d\n"), memory_status_ex.ullAvailPageFile);
处理器信息获取
NtQuerySystemInformation
这件事说来话有点长,网上用关键词找到的大部分资料都不太合适。 由于某些原因,微软一直没提供CPU性能相关的公共API,虽然一些官方组件(如PDH,WMI)进行查询,但是没有直接提供系统级的API。 对于NT系统,微软后来公布了 NtQuerySystemInformation 的使用说明,但是似乎并没有被太多人发现。 [url]http://msdn.microsoft.com/en-us/library/windows/desktop/ms724509.aspx[/url]
NtQuerySystemInformation函数的C语言定义如下
__kernel_entry NTSTATUS NTAPI NtQuerySystemInformation (
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL);
这是一个私有函数,不提供链接库,所以使用时,需要使用函数指针+运行时动态连接。
typedef __kernel_entry NTSTATUS (NTAPI * LPNtQuerySystemInformation) (
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
LPNtQuerySystemInformation NtQuerySystemInformation =
(LPNtQuerySystemInformation) GetProcAddress(LoadLibrary(_T("Ntdll.dll")), _T("NtQuerySystemInformation"));
然后就可以使用了。假如已知CPU有4个核,那么可以这么获取基本信息。
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION sppi[4];
ULONG size;
NtQuerySystemInformation (SYSTEM_INFORMATION_CLASS::SystemProcessorPerformanceInformation, &sppi, sizeof(sppi), &size);
IdleTime, KernelTime, UserTime
无论是每个CPU(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)或者整个系统(GetSystemTimes),都能取到这个三个时间。 KernelTime是执行内核的时间计数器,UserTime是用户模式下的时间计数器。 单位均为 100-nanosecond,注意是从系统开始至今的所有统计,需要自己计算百分比,使用多线程配合Sleep比较合适,单线程算时差可能会不太准确。
GetSystemTimes
[url]http://msdn.microsoft.com/en-us/library/windows/desktop/ms724400.aspx[/url]
网络信息获取
Adapter,Interface
微软在自己的MSDN不同模块有着矛盾的概念。 有的模块认为Interface和Adapter一对一,只是Interface是软件为主的抽象,Adapter是硬件为主的封装。 而有的地方,Adapter和Interface却没直接关系。Interface包容了所有的连接,包括桥接,ppp等。而Adapter是指硬件设备。 这导致我一开始使用GetIfTable接口,却找不到办法过滤虚拟网卡和物理网卡。
GetAdaptersInfo & GetAdaptersAddresses
两者都能获取主要物理网卡的信息,不一样的是。GetAdaptersInfo 函数比较老,只获取物理网卡的信息附带IPV4的信息,GetAdaptersAddresses 则获取任何带IP的Adapter信息,包含虚拟的Adapter(比如Software Loop)。GetAdaptersAddresses 更通用,但是操作更难,并且很多字段是Vista之后才有(比如当前流速)。从兼容的角度,还是应该使用如 GetIfTable 获取对应 Interface 的网络流量。
磁盘信息获取
到这个时候,不可回避地要说明一个问题。 Windows API本身不是系统内部的API,而是专门暴露给程序员使用的开发API,它具有很好的扩展性和跨平台性。因此我们拿到的API都是经过逻辑抽象之后的。 比如网络信息获取的时候,就存在Interface和Adapter两个概念,但是我们拿到的仍然并非是网卡本身。 类似的,我们能看出内存的信息,但是却不知道内存什么型号。这种硬件级别的信息,需要从完全另一套设备管理的API走(比如Setup API)。
系统API、网络管理API、存储管理API 设备管理API
------------------------------------
系统管理:系统管理、网络管理、存储管理
内存 虚拟内存 处理器 处理器核 网络 存储
--------------------------------------------------------------
设备管理:驱动和设备管理
内存条 处理器 网卡 硬盘 光盘 其他设备
---------------------------------------------------------------
到硬盘这边,我们就遇到了一点麻烦。通常我们希望得到如sda、sda1这种信息,但是存储这边的API,只能得到sda1,得不到sda。
虽然实际开发中,我们通常使用文件函数和 \\.\PhysicalDriveN
来枚举可能存在的硬盘,但是需要指明的是,这不是合理的操作。很多程序无法自适应硬盘拔插,跟这个有直接关系。
如果需要找到具体有哪些 \\.\PhysicalDriveN
,最安全的方法是使用 SetupAPI 遍历设备,然后取得所有存储设备 ID。
从监控的角度,我们不需要知道硬盘的具体型号,所以遍历抽象概念Volume即可。
Volume
FindFirstVolume
, FindNextVolume
, FindVolumeClose
就跟文件操作一样,没什么特别的.
测试代码
#pragma once
#include <WinSock2.h>
#include <IPHlpApi.h>
#include <Windows.h>
#include <Psapi.h>
#include <winternl.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "psapi.lib")
typedef __kernel_entry NTSTATUS(NTAPI * LPNtQuerySystemInformation) (
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
LPNtQuerySystemInformation _NtQuerySystemInformation;
int fetch_cpu();
int fetch_mem();
int fetch_disk();
int fetch_net();
int _tmain(int argc, _TCHAR* argv[])
{
_NtQuerySystemInformation = (LPNtQuerySystemInformation)GetProcAddress(LoadLibrary(_T("Ntdll.dll")), "NtQuerySystemInformation");
while (true) {
fetch_net();
fetch_cpu();
fetch_mem();
fetch_disk();
Sleep(1000);
}
return 0;
/*
MultiByteToWideChar(CP_ACP, NULL, iaa->AdapterName, -1, name, 255);
HRESULT hr = StringCchPrintf(pszDest, cchDest, pszFormat);
*/
}
int fetch_net() {
DWORD size = 0;
ULONG ret = GetAdaptersAddresses(AF_UNSPEC, NULL, NULL, NULL, &size);
IP_ADAPTER_ADDRESSES * iaas = NULL;
iaas = (IP_ADAPTER_ADDRESSES *)HeapAlloc(GetProcessHeap(), 0, size);
ret = GetAdaptersAddresses(AF_UNSPEC, NULL, NULL, iaas, &size);
IP_ADAPTER_ADDRESSES * iaa = iaas;
MIB_IFROW mib_ifrow;
WCHAR name[256];
while(1) {
if (iaa == NULL) break;
if (iaa->OperStatus != IF_OPER_STATUS::IfOperStatusUp) {
iaa = iaa->Next;
continue;
}
if (iaa->IfType != IF_TYPE_ETHERNET_CSMACD && iaa->IfType != IF_TYPE_IEEE80211) {
iaa = iaa->Next;
continue;
}
//_tprintf(_T("%s\n"), iaa->AdapterName);
_tprintf(_T("%ws\n"), iaa->Description);
ZeroMemory(&mib_ifrow, sizeof(mib_ifrow));
mib_ifrow.dwIndex = iaa->IfIndex;
GetIfEntry(&mib_ifrow);
_tprintf(_T("Data In %ld\n"), mib_ifrow.dwInOctets);
_tprintf(_T("Data Out %ld\n"), mib_ifrow.dwOutOctets);
_tprintf(_T("\n"));
iaa = iaa->Next;
}
return 0;
}
int fetch_cpu() {
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION spi[12];
ULONG size;
NTSTATUS x = _NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS::SystemProcessorPerformanceInformation, &spi, sizeof(spi), &size);
LONGLONG t = spi[0].IdleTime.QuadPart;
_tprintf(_T("MemTotal: %I64d\n"), t);
return 0;
}
int fetch_mem() {
PERFORMANCE_INFORMATION pi;
pi.cb = sizeof(pi);
GetPerformanceInfo(&pi, sizeof(pi));
_tprintf(_T("MemTotal: %ld MB\n"), (LONG)(((ULONGLONG)pi.PageSize * pi.PhysicalTotal) / 1024 / 1024));
_tprintf(_T("MemFree: %ld MB\n"), (LONG)(((ULONGLONG)pi.PageSize * pi.PhysicalAvailable) / 1024 / 1024));
_tprintf(_T("Cached: %ld MB\n"), (LONG)(((ULONGLONG)pi.PageSize * pi.SystemCache) / 1024 / 1024));
_tprintf(_T("Buffers: %ld MB\n"), (LONG)(((ULONGLONG)pi.PageSize * pi.KernelPaged) / 1024 / 1024));
_tprintf(_T("SwapTotal: %ld MB\n"), (LONG)(((ULONGLONG)pi.PageSize * pi.CommitLimit) / 1024 / 1024));
_tprintf(_T("SwapFree: %ld MB\n"), (LONG)(((ULONGLONG)pi.PageSize * (pi.CommitLimit - pi.CommitTotal)) / 1024 / 1024));
return 0;
}
int fetch_disk() {
_TCHAR VolumeName[MAX_PATH] = _T("");
HANDLE FindHandle = FindFirstVolume(VolumeName, ARRAYSIZE(VolumeName));
DWORD Error = ERROR_SUCCESS;
if (FindHandle == INVALID_HANDLE_VALUE)
{
Error = GetLastError();
wprintf(L"FindFirstVolumeW failed with error code %d\n", Error);
return 0;
}
_TCHAR VolumePathName[MAX_PATH] = _T("");
DWORD CharCount = 0;
while (true)
{
// _tprintf(_T("%s\n"), VolumeName);
DWORD junk;
BOOL xx = GetVolumePathNamesForVolumeName(VolumeName, VolumePathName, MAX_PATH, &junk);
if (GetDriveType(VolumePathName) == DRIVE_FIXED) {
size_t len;
StringCchLength(VolumeName, MAX_PATH, &len);
_TCHAR VolumeNamex[MAX_PATH] = _T("");
ZeroMemory(VolumeNamex, sizeof(VolumeNamex));
StringCchCopy(VolumeNamex, len, VolumeName);
HANDLE hDevice = CreateFile(VolumeNamex, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
DISK_PERFORMANCE dp;
DeviceIoControl(hDevice, // device to be queried
IOCTL_DISK_PERFORMANCE, // operation to perform
NULL, 0, // no input buffer
&dp, sizeof(dp), // output buffer
&junk, // # bytes returned
(LPOVERLAPPED)NULL);
_tprintf(_T("%s\n"), VolumeName);
static ULARGE_INTEGER FreeBytesAvailable, TotalNumberOfBytes, TotalNumberOfFreeBytes;
_tprintf(_T("1111\n"));
GetDiskFreeSpaceEx(VolumePathName, &FreeBytesAvailable, &TotalNumberOfBytes, &TotalNumberOfFreeBytes);
_tprintf(_T("2222\n"));
_tprintf(_T("Volume: %s\n"), VolumePathName);
_tprintf(_T("BytesRead: %I64d\n"), dp.BytesRead);
_tprintf(_T("FreeBytesAvailable: %I64d\n\n"), FreeBytesAvailable);
}
BOOL Success = FindNextVolume(FindHandle, VolumeName, ARRAYSIZE(VolumeName));
if (!Success)
{
Error = GetLastError();
if (Error != ERROR_NO_MORE_FILES)
{
wprintf(L"FindNextVolumeW failed with error code %d\n", Error);
break;
}
Error = ERROR_SUCCESS;
break;
}
}
FindVolumeClose(FindHandle);
FindHandle = INVALID_HANDLE_VALUE;
DWORD drivers = GetLogicalDrives();
return 0;
}