問題描述
設計一個內存監視器,能實時地顯示當前系統中內存的使用情況,包括系統地址空間的布局,物理內存的使用情況;能實時顯示某個進程的虛擬地址空間布局和工作集信息等。
思路
獲取系統信息
-
SYSTEM_INFO
typedef struct _SYSTEM_INFO { union { DWORD dwOemId; struct { WORD wProcessorArchitecture; WORD wReserved; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; DWORD dwPageSize; LPVOID lpMinimumApplicationAddress; LPVOID lpMaximumApplicationAddress; DWORD_PTR dwActiveProcessorMask; DWORD dwNumberOfProcessors; DWORD dwProcessorType; DWORD dwAllocationGranularity; WORD wProcessorLevel; WORD wProcessorRevision; } SYSTEM_INFO, *LPSYSTEM_INFO;
-
GetNativeSystemInfo
注意INTELx86_64體系最好用這個函數。其他的等價於
GetSystemInfo
void GetNativeSystemInfo( LPSYSTEM_INFO lpSystemInfo );
LPSYSTEM_INFO
指向SYSTEM_INFO
的指針
-
信息輸出
DWORD mem_size = (DWORD*)si.lpMaximumApplicationAddress - (DWORD*)si.lpMinimumApplicationAddress; printDword(L"處理器個數 ", si.dwNumberOfProcessors); printStrFormatByte(L"物理頁大小 ", si.dwPageSize); printAddress(L"進程最小尋址空間: 0x", si.lpMinimumApplicationAddress); printAddress(L"進程最大尋址地址: 0x", si.lpMaximumApplicationAddress); printStrFormatByte(L"進程可用空間大小: ", mem_size);
注意這里的
print***
是自定義函數。 -
展示
獲取物理內存信息
主要使用到的數據結構和函數有MEMORYSTATUSEX
與 GlobalMemoryStatusEx
, PERFORMANCE_INFORMATION
與 GetPerformanceInfo
-
MEMORYSTATUSEX
typedef struct _MEMORYSTATUSEX { DWORD dwLength; DWORD dwMemoryLoad; DWORDLONG ullTotalPhys; DWORDLONG ullAvailPhys; DWORDLONG ullTotalPageFile; DWORDLONG ullAvailPageFile; DWORDLONG ullTotalVirtual; DWORDLONG ullAvailVirtual; DWORDLONG ullAvailExtendedVirtual; } MEMORYSTATUSEX, *LPMEMORYSTATUSEX;
注意在使用該數據結構之前,
dwLength
必須進行指定,dwLength = sizeof(MEMORYSTATUSEX)
-
GlobalMemoryStatusEx
BOOL GlobalMemoryStatusEx( LPMEMORYSTATUSEX lpBuffer );
lpBuffer
是指向MEMORYSTAUSEX
的指針,用於保存信息。
-
PERFORMANCE_INFORMATION
typedef struct _PERFORMANCE_INFORMATION { DWORD cb; SIZE_T CommitTotal; SIZE_T CommitLimit; SIZE_T CommitPeak; SIZE_T PhysicalTotal; SIZE_T PhysicalAvailable; SIZE_T SystemCache; SIZE_T KernelTotal; SIZE_T KernelPaged; SIZE_T KernelNonpaged; SIZE_T PageSize; DWORD HandleCount; DWORD ProcessCount; DWORD ThreadCount; } PERFORMANCE_INFORMATION, *PPERFORMANCE_INFORMATION, PERFORMACE_INFORMATION, *PPERFORMACE_INFORMATION;
-
GetPerformanceInfo
BOOL GetPerformanceInfo( PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb );
pPerformanceInformation
指向用於保存返回信息的指針cb
需要指明PERFORMANCE_INFORMATION
結構體的大小
-
效果展示
獲取所有進程的基本信息
主要過程是先獲取所有進程的一個snapshot,由於進程信息和數量是動態變化的,所以需要先獲取一個靜態的信息集;其次,類似於目錄檢索對snapshot進行順序檢索,獲取進程信息。
創建進程snapshot
-
CreateToolhelp32Snapshot
HANDLE CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID );
DWORD dwFlags
用於表明該函數獲取多少有關屬性到snapshot中。具體說明可參考MSDN, 這里選用的參數是TH32CS_SNAPALL
DWORD th32ProcessID
需要獲取的進程的pid。0表示是最近的進程。
遍歷進程
數據結構:PROCESSENTRY32
API: Process32First
Process32Next
-
PROCESSENTRY32
typedef struct tagPROCESSENTRY32 { DWORD dwSize; DWORD cntUsage; DWORD th32ProcessID; ULONG_PTR th32DefaultHeapID; DWORD th32ModuleID; DWORD cntThreads; DWORD th32ParentProcessID; LONG pcPriClassBase; DWORD dwFlags; TCHAR szExeFile[MAX_PATH]; } PROCESSENTRY32, *PPROCESSENTRY32;
注意在使用前必須指定
dwSize = sizeof(PROCESSENTRY32)
-
Process32First
BOOL Process32First( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );
HANDEL hSnapshot
就是從上述CreateToolhelp32Snapshot
獲得的。LPPROCESSENTRY32 lppe
就是PROCESSENTRY32
的指針
-
Process32Next
BOOL Process32Next( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );
含義同上。其中
hSnapshot
是同一個,不同的是lppe
此時有了值,用於保存當前項的下一項的進程的狀態信息。
效果展示
獲取單個進程的詳細信息
使用到的主要數據結構有:SYSTEM_INFO
(已介紹),MEMORY_BASIC_INFORMATION
,
使用到的主要API有:GetNativeSystemInfo
(已介紹),VirtualQueryEx
, OpenProcess
-
MEMORY_BASIC_INFORMATION
typedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress; PVOID AllocationBase; DWORD AllocationProtect; SIZE_T RegionSize; DWORD State; DWORD Protect; DWORD Type; } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
PVOID BaseAddress
頁面區域的基地址DWORD AllocationProtect;
如果這個頁面區域是初始獲取的,這里就是其頁面區域的保護方式SIZE_T RegionSize
當前塊的大小DWORD State
當前頁面塊的狀態,MEM_COMMIT
MEME_FREE
MEM_RESERVE
三種狀態DWORD Protect
當前塊中的頁面訪問方式DWORD Type
塊類型,三種MEM_IMAGE
MEM_MAPPED
MEM_PRIVATE
-
OpenProcess
HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
DWORD dwDesiredAccess
訪問該進程的方式,我這里選用的是PROCESS_ALL_ACCESS
BOOL bInheritHandle
如果為真,該進程的子進程也將繼承該函數的返回句柄DWORD dwProcessId
要打開的進程的PID
-
VirtualQueryEx
SIZE_T VirtualQueryEx( HANDLE hProcess, LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength );
hProcess
要查詢的進程的句柄lpAddress
要查詢的進程的虛存的塊的基地址lpBuffer
指向要保存相關信息的數據的指針就是上文提到的MEMORY_BASIC_INFORMATION
dwLength = sizeof(MEMORY_BASIC_INFORMATION)
效果展示
實現的功能如下,具體的展示已在上文說明。
源代碼
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include <iomanip>
#include <Windows.h>
#include <Tlhelp32.h>
#include <shlwapi.h>
#include <Psapi.h>
#pragma comment(lib,"Shlwapi.lib")
#pragma comment(lib, "Psapi.Lib")
#pragma comment(lib,"Kernel32.lib")
using namespace std;
enum ProgramState { QUERY_SYS_INFO, QUERY_MEM_INFO, QUERY_PRE_INFO, EXIT };
//查詢系統配置信息
void getSystemInfo();
//物理內存使用情況
void getMemoryInfo();
//打印所有進程的基本信息
void getProcessInfo();
//獲得單個內存的使用情況
void showSingleProcessMemDetail(int PID);
void help();
int main()
{
setlocale(LC_ALL, "");
int state = ProgramState::EXIT;//默認為退出狀態
while (1)
{
help();
std::cout.fill(' ');
std::cout.setf(ios::dec);//確保cout輸出為十進制
cin >> state;
std::cout << "\n";
if (state == ProgramState::QUERY_SYS_INFO)
{
getSystemInfo();
}
else if (state == ProgramState::QUERY_MEM_INFO)
{
getMemoryInfo();
}
else if (state == ProgramState::QUERY_PRE_INFO)
{
getProcessInfo(); //當前所有運行進程基本信息
std::cout << "輸入進程PID以查看其虛擬內存信息" << endl;
int PID;
cin >> PID;
showSingleProcessMemDetail(PID);
}
else if (state == ProgramState::EXIT)
{
return 0; //結束程序的運行
}
}
return 0;
}
//將字節數轉為字符串打印輸出
inline void printStrFormatByte(const WCHAR* info, DWORDLONG bytes)
{
TCHAR tmp[MAX_PATH];
ZeroMemory(tmp, sizeof(tmp));
StrFormatByteSize(bytes, tmp, MAX_PATH);
wcout << info << tmp << endl;
return;
}
//打印地址
inline void printAddress(const WCHAR* info, LPVOID addr)
{
wcout << info << hex << setw(8) << addr << endl;
}
inline void printDword(const WCHAR* info, DWORDLONG dw)//將DWORD轉為DWORDLONG
{
wcout << info;
std::cout << dw << endl;
}
//查詢系統配置信息
void getSystemInfo()
{
SYSTEM_INFO si;
ZeroMemory(&si, sizeof(si));
GetNativeSystemInfo(&si);
DWORD mem_size = (DWORD*)si.lpMaximumApplicationAddress - (DWORD*)si.lpMinimumApplicationAddress;
printDword(L"處理器個數 ", si.dwNumberOfProcessors);
printStrFormatByte(L"物理頁大小 ", si.dwPageSize);
printAddress(L"進程最小尋址空間: 0x", si.lpMinimumApplicationAddress);
printAddress(L"進程最大尋址地址: 0x", si.lpMaximumApplicationAddress);
printStrFormatByte(L"進程可用空間大小: ", mem_size);
return;
}
//物理內存使用情況
void getMemoryInfo()
{
MEMORYSTATUSEX mem_stat;
ZeroMemory(&mem_stat, sizeof(mem_stat));
mem_stat.dwLength = sizeof(mem_stat);//必須執行這一步
GlobalMemoryStatusEx(&mem_stat); //取得內存狀態
std::cout << "內存利用率 \t" << mem_stat.dwMemoryLoad << endl;
printStrFormatByte(L"物理內存: \t", mem_stat.ullTotalPhys);
printStrFormatByte(L"可用物理內存: \t", mem_stat.ullAvailPhys);
printStrFormatByte(L"總共頁文件大小: \t", mem_stat.ullTotalPageFile);
printStrFormatByte(L"空閑頁文件大小: \t", mem_stat.ullAvailPageFile);
printStrFormatByte(L"虛擬內存大小: \t", mem_stat.ullTotalVirtual);
printStrFormatByte(L"空閑虛擬內存大小:\t", mem_stat.ullAvailVirtual);
printStrFormatByte(L"空閑拓展內存大小:\t", mem_stat.ullAvailExtendedVirtual);
cout << endl << endl;
PERFORMANCE_INFORMATION pi;
GetPerformanceInfo(&pi, sizeof(pi));
DWORDLONG page_size = pi.PageSize;
printStrFormatByte(L"Commit Total \t", pi.CommitTotal * page_size);
printStrFormatByte(L"Commit Limit \t", pi.CommitLimit * page_size);
printStrFormatByte(L"Commit Peak \t", pi.CommitPeak * page_size);
printStrFormatByte(L"Physical Memory \t", pi.PhysicalTotal * page_size);
printStrFormatByte(L"Physical Memory Avaliable ", pi.PhysicalAvailable * page_size);
printStrFormatByte(L"System Cache \t", page_size*pi.SystemCache);
printStrFormatByte(L"Kerbel Total \t", page_size * pi.KernelTotal);
printStrFormatByte(L"Kernel Paged \t", page_size * pi.KernelPaged);
printStrFormatByte(L"Kernel Nonpaged \t", page_size * pi.KernelNonpaged);
printStrFormatByte(L"Page Size \t", page_size * pi.PageSize);
printDword(L"Handle Count \t", page_size * pi.HandleCount);
printDword(L"Process Count \t", page_size * pi.ProcessCount);
printDword(L"Thread Count \t", page_size * pi.ThreadCount);
}
//打印所有進程的基本信息
void getProcessInfo()
{
//創建進程snapshot
HANDLE h_process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
if (h_process_snapshot == INVALID_HANDLE_VALUE)
{
cout << "CreateToolhelp32Snapshot調用失敗!\n";
exit(-1);
}
PROCESSENTRY32 process_entry;
process_entry.dwSize = sizeof(process_entry);//必須要指定大小
//循環遍歷輸出所有進程的信息
if (Process32First(h_process_snapshot, &process_entry))
{
wcout << setiosflags(ios::left) << setw(40) << L"Process Name";
cout << setiosflags(ios::right) << setw(15) << "PID";
wcout << L"\t\t線程數量" << endl << endl;
do
{
wcout << setiosflags(ios::left) << setw(40) << process_entry.szExeFile;//進程名
std::cout << "\t" << setw(7) << process_entry.th32ProcessID;//pid
std::cout << " \t" << setw(3) << process_entry.cntThreads << endl;//線程數目
} while (Process32Next(h_process_snapshot, &process_entry));
}
CloseHandle(h_process_snapshot);
}
//顯示當前塊頁面訪問方式
void printPageProtection(DWORD dwTarget)
{
const int width = 20;
switch (dwTarget)
{
case(PAGE_READONLY):
{
std::cout << setiosflags(ios::left) << setw(width) << "READONLY";
break;
}
case(PAGE_GUARD):
{
std::cout << setiosflags(ios::left) << setw(width) << "GUARD";
break;
}
case(PAGE_NOCACHE):
{
std::cout << setiosflags(ios::left) << setw(width) << "NOCACHE";
break;
}
case(PAGE_NOACCESS):
{
std::cout << setiosflags(ios::left) << setw(width) << "NOACCESS";
break;
}
case(PAGE_READWRITE):
{
std::cout << setiosflags(ios::left) << setw(width) << "READWRITE";
break;
}
case(PAGE_WRITECOPY):
{
std::cout << setiosflags(ios::left) << setw(width) << "WRITECOPY";
break;
}
case(PAGE_EXECUTE):
{
std::cout << setiosflags(ios::left) << setw(width) << "EXECUTE";
break;
}
case(PAGE_EXECUTE_READ):
{
std::cout << setiosflags(ios::left) << setw(width) << "EXECUTE_READ";
break;
}
case(PAGE_EXECUTE_READWRITE):
{
std::cout << setiosflags(ios::left) << setw(width) << "EXECUTE_READWRITE";
break;
}
case(PAGE_EXECUTE_WRITECOPY):
{
std::cout << setiosflags(ios::left) << setw(width) << "EXECUTE_WRITECOPY";
break;
}
default:
break;
}
}
//輸出單個進程的詳細信息
void showSingleProcessMemDetail(int PID)
{
SYSTEM_INFO si;
ZeroMemory(&si, sizeof(si));
GetNativeSystemInfo(&si); //獲得系統信息
//循環訪問整個進程地址空間
LPCVOID p_begin = (LPVOID)si.lpMinimumApplicationAddress; //p_begin指向開始的地址
std::cout.setf(ios::left);
//輸出表頭
wcout << setiosflags(ios::left) << setw(21) << L"塊地址"
<< setw(10) << L"塊大小"
<< setw(10) << L"塊內頁狀態"
<< setw(12) << L"塊內頁保護方式"
<< setw(10) << L"塊類型" << endl;
HANDLE h_process = OpenProcess(PROCESS_ALL_ACCESS, 0, PID); //得到PID的值
if (h_process == INVALID_HANDLE_VALUE)
{
std::cout << "Failed to OpenProcess" << endl;
exit(-1);
}
MEMORY_BASIC_INFORMATION mem; //虛擬內存空間的基本信息結構
ZeroMemory(&mem, sizeof(mem));
while (p_begin < (LPVOID)si.lpMaximumApplicationAddress)
{
//查詢進程在p_begin開始的塊信息
VirtualQueryEx(
h_process, //進程句柄
p_begin, //開始位置的地址
&mem, //緩沖區
sizeof(mem));
//塊結束地址
LPCVOID p_end = (PBYTE)p_begin + mem.RegionSize;
//輸出塊起始、結束地址
std::cout << hex << setw(8) << setfill('0') << (DWORD*)p_begin
<< "-"
<< hex << setw(8) << setfill('0') << (DWORD*)p_end;
//輸出塊大小
TCHAR tmp[MAX_PATH];
ZeroMemory(tmp, sizeof(tmp));
StrFormatByteSize(mem.RegionSize, tmp, MAX_PATH);
std::wcout << "\t" << setw(8) << tmp;
//輸出塊的狀態
std::cout.fill(' ');
if (mem.State == MEM_COMMIT)
{
std::cout << setw(10) << "已提交";
}
else if (mem.State == MEM_FREE)
{
std::cout << setw(10) << "空閑";
}
else if (mem.State == MEM_RESERVE)
{
std::cout << setw(10) << "保留";
}
//顯示塊內頁的保護方式
if (mem.Protect == 0 && mem.State != MEM_FREE)
{
mem.Protect = PAGE_READONLY;
}
printPageProtection(mem.Protect);
//顯示塊的類型 鄰近頁面物理存儲器類型指的是與給定地址所在頁面相同的存儲器類型
std::cout.fill(' ');
if (mem.Type == MEM_IMAGE)
{
std::cout << "\t\tImage";
}
else if (mem.Type == MEM_PRIVATE)
{
std::cout << "\t\tPrivate";
}
else if (mem.Type == MEM_MAPPED)
{
std::cout << "\t\tMapped";
}
cout << endl;
//移動塊指針獲得下一個塊
if (p_begin == p_end)//部分進程如0號進程無法進行空間遍歷
break;
p_begin = p_end;
}
}
void help()
{
std::cout << "\n\nMenu:" << endl
<< ProgramState::QUERY_SYS_INFO << " - 查看系統信息" << endl
<< ProgramState::QUERY_MEM_INFO << " - 查看內存情況" << endl
<< ProgramState::QUERY_PRE_INFO << " - 查看當前運行進程信息及其虛擬地址空間布局和工作集信息" << endl
<< ProgramState::EXIT << " - 退出\n\n";
}