c++实现Windows内存监视

问题描述设计一个内存监视器,能实时地显示当前系统中内存的使用情况,包括系统地址空间的布局,物理内存的使用情况;能实时显示某个进程的虚拟地址空间布局和工作集信息等。思路获取系统信息SYSTEM_INFOtypedef...

问题描述

设计一个内存监视器,能实时地显示当前系统中内存的使用情况,包括系统地址空间的布局,物理内存的使用情况;能实时显示某个进程的虚拟地址空间布局和工作集信息等。

思路

获取系统信息

  1. 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;
  2. GetNativeSystemInfo

    注意INTELx86_64体系最好用这个函数。其他的等价于GetSystemInfo

    void GetNativeSystemInfo(
      LPSYSTEM_INFO lpSystemInfo
    );
    • LPSYSTEM_INFO 指向SYSTEM_INFO的指针
  3. 信息输出

    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***是自定义函数。

  4. 展示

获取物理内存信息

主要使用到的数据结构和函数有MEMORYSTATUSEXGlobalMemoryStatusEx, PERFORMANCE_INFORMATIONGetPerformanceInfo

  1. 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)

  2. GlobalMemoryStatusEx

    BOOL GlobalMemoryStatusEx(
      LPMEMORYSTATUSEX lpBuffer
    );
    • lpBuffer 是指向MEMORYSTAUSEX的指针,用于保存信息。
  3. 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;
  4. GetPerformanceInfo

    BOOL GetPerformanceInfo(
      PPERFORMANCE_INFORMATION pPerformanceInformation,
      DWORD                    cb
    );
    • pPerformanceInformation 指向用于保存返回信息的指针
    • cb 需要指明PERFORMANCE_INFORMATION结构体的大小
  5. 效果展示

获取所有进程的基本信息

主要过程是先获取所有进程的一个snapshot,由于进程信息和数量是动态变化的,所以需要先获取一个静态的信息集;其次,类似于目录检索对snapshot进行顺序检索,获取进程信息。

创建进程snapshot

  1. CreateToolhelp32Snapshot

    HANDLE CreateToolhelp32Snapshot(
      DWORD dwFlags,
      DWORD th32ProcessID
    );
    • DWORD dwFlags 用于表明该函数获取多少有关属性到snapshot中。具体说明可参考MSDN, 这里选用的参数是TH32CS_SNAPALL
    • DWORD th32ProcessID 需要获取的进程的pid。0表示是最近的进程。

遍历进程

数据结构:PROCESSENTRY32

API: Process32First Process32Next

  1. 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)

  2. Process32First

    BOOL Process32First(
      HANDLE           hSnapshot,
      LPPROCESSENTRY32 lppe
    );
    • HANDEL hSnapshot 就是从上述CreateToolhelp32Snapshot 获得的。
    • LPPROCESSENTRY32 lppe 就是PROCESSENTRY32 的指针
  3. Process32Next

    BOOL Process32Next(
      HANDLE           hSnapshot,
      LPPROCESSENTRY32 lppe
    );

    含义同上。其中hSnapshot 是同一个,不同的是lppe 此时有了值,用于保存当前项的下一项的进程的状态信息。

效果展示

获取单个进程的详细信息

使用到的主要数据结构有:SYSTEM_INFO (已介绍),MEMORY_BASIC_INFORMATION,

使用到的主要API有:GetNativeSystemInfo(已介绍),VirtualQueryEx, OpenProcess

  1. 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
  2. OpenProcess

    HANDLE OpenProcess(
      DWORD dwDesiredAccess,
      BOOL  bInheritHandle,
      DWORD dwProcessId
    );
    • DWORD dwDesiredAccess 访问该进程的方式,我这里选用的是PROCESS_ALL_ACCESS
    • BOOL bInheritHandle 如果为真,该进程的子进程也将继承该函数的返回句柄
    • DWORD dwProcessId 要打开的进程的PID
  3. 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";
}

本文标题为:c++实现Windows内存监视