一、实验目的
熟悉Windows存储器管理中提供的各种机制和实现的请求调页和群集技术。
Windows提供给应用程序的内存方式具有统一的简明和保护性的特点。另外,用户不需要知道操作系统如何分配内存,只需要知道应用程序如何分配内存即可。
通过实验,了解Windows内存结构和虚拟内存的管理,学习如何在应用程序中管理内存,体会Windows应用程序使用内存的简单性。了解当前系统中内存的使用情况,包括系统地址空间的布局和物理内存的使用情况;可以显示某个进程的虚拟地址空间和工作集信息等。
二、实验内容
在Windows平台上设计一个内存监视器,能实时地显示当前系统中内存的使用情况,包括系统地址空间的布局,物理内存的使用情况;能实时显示某个进程的虚拟地址空间布局和工作集信息等。
相关的系统调用:
GetSystemInfo,VirtualQueryEx, VirtualAlloc, GetPerformanceInfo,
GlobalMemoryStatusEx…
三、实验环境
硬件环境(如图1所示):
图1
软件环境(如图2所示):
图2
四、实验原理
Windows2000/XP是32位的操作系统,在Windows下运行的每个应用程序都认为能独占4GB的虚拟地址空间。其中,低2GB为进程私有的地址空间,用来存放用于程序和DLL,高2GB为所有进程共享区,也就是操作系统占用区。事实上,很少有进程能够占用2GB的存储空间。Windows把每个进程的虚拟内存地址映射为物理内存地址。
物理内存指的是计算机配置的RAM,系统可以管理所有的物理内存。Windows通过分配RAM、页面文件或者两者中的空间,可以准确的知道应用程序所需要的内存。
五、程序设计与实现
1、理解程序中需要用到的系统函数和结构体
1.1 系统函数
(1)、获得当前系统的一些特征信息GetSystemInfo()函数
函数格式:VOID GetSystemInfo(LPSYSTEM_INFOlpSystemInfo);
参数:lpSystemInfo为指向SYSTEM_INFO()结构体的指针,该结构由此函数填充。
返回值:该函数没有返回值。
(2)、检查进程虚拟内存的当前信息VirtualQueryEx()函数
函数格式:
DWORD VirtualQueryEx(
HANDLE hProcess, //进程句柄
LPCVOID lpAddress, //指向要查询的页基地址指针
PMEMORY_BASIC_INFORMATION lpBuffer, //用以接收要查询的内存信息
//指向包含MEMORY_BASIC_INFORMATION结构的缓冲区指针
SIZE_TdwLength //MEMORY_BASIC_INFORMATION结构的大小
);
返回值:如果函数调用成功,则返回写入结构lpBuffer的字节数。
(3)、在调用进程的虚拟地址空间保留或提交一部分页VirtualAlloc()函数
函数格式:
LPVOID VirtualAlloc(
LPVOID lpAddress, //待分配区域的起始地址
SIZE_T dwSize, //要分配或者保留的区域的大小
DWORDflAllocationType, //定义分配区域的类型属性
DWORD flProtect //指定分配区域保护属性
);
返回值:如果函数调用成功,返回值为所分配页面的基地址;如果函数失败,返回值为NULL。
(4)、获得当前系统的存储器使用情况GetPerformanceInfo()函数
函数格式:
BOOL WINAPI GetPerformanceInfo(
PPERFORMANCE_INFORMATIONpPerformanceInformation,
// pPerformanceInformation为指向PERFORMANCE_INFORMATION结构体的指针
DWORD cb //PERFORMANCE_INFORMATION结构体的大小
);
返回值:如果函数调用成功,返回值为TRUE,失败返回值为FALSE。
(5)、获取系统当前物理内存和虚拟内存的使用情况GlobalMemoryStatusEx()函数
函数格式:
BOOL WINAPI GlobalMemoryStatusEx(LPMEMORYSTATUSEXlpBuffer);
//lpBuffer为指向MEMORYSTATUSEX结构体的指针。
返回值:如果函数调用成功,返回非0,失败返回0.
(6)、将数字转换成字符串StrFormatByteSize()函数
函数格式:
LPTSTR StrFormatByteSize(
LONGLONG qdw, //要转变的数字值
LPTSTR pszBuf, //指向保存将数字转变为字符串的缓冲区指针
UINT uiBufSize //缓冲区的容量
);
返回值:如果函数调用成功,则返回一字符串地址指针。
(7)、获取当前进程已加载模块的文件的完整路径GetModuleFileName()函数
函数格式:
DWORD WINAPI GetModuleFileName(
HMODULE hModule, //模块句柄
LPTSTR lpFilename, //存放文件路径名的字符缓冲区
DWORD nSize //缓冲区的大小
);
返回值:如果函数调用成功,返回复制到lpFilename的实际字符数量,如果失败返回值为0。
(8)、去掉完整路径名的路径部分PathStripPath()函数
函数格式:
VOID PathStripPath(LPTSTR pszPath); //参数为完整的路径名
返回值:无返回值。
(9)、……
1.2 结构体
(1)、SYSTEM_INFO结构定义如下:
typedef struct _SYSTEM_INFO
{
union{
DWORD dwOemId;
struct {
WORD wProcessorArchitecture;
WORD wReserved;
};
};
DWORD dwPageSize; //内存页的大小
LPVOID lpMinimumApplicationAddress; //每个进程可用地址空间的最小内存地址
LPVOID lpMaximumApplicationAddress; //每个进程私有地址空间的最大内存地址
DWORD_PTR dwActiveProcessorMask; //系统配备的CPU掩码
DWORD dwNumberOfProcessors; //系统配备的CPU的数量
DWORD dwProcessorType; //系统配备的CPU的类型,向下兼容用
DWORD dwAllocationGranularity; //能够保留地址空间区域的最小单位
WORD wProcessorLevel; //CPU的级别
WORD wProcessorRevision; //步进级别
}SYSTEM_INFO;
(2)、MEMORY_BASIC_INFORMATION结构定义如下:
typedef struct _MEMORY_INFORMATION
{
PVOIDBaseAddress; //按页对齐方式分配时,分配包含基地址的最小页号
PVOIDAllocationBase; //APP的实际起始地址
DWORDAllocationProtect; //该区域初始设置的访问方式
SIZE_TRegionSize; //虚存区的大小
DWORDState; //区域的状态
DWORDProtect; //区域设置的访问方式
DWORDType; //区域的页面类型
}MEMORY_BASIC_INFORMATION, * PMEMORY_BASIC_INFORMATION;
注意:
①、AllocationProtect可能有的方式为PAGE_READONLY, PAGE_READWRITE,
PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE,
PAGE_EXECUTE_WRITECOPY(对于该地址空间的区域,不管执行什么操作,都不会引发访问违规。如果试图在该页上进行写入操作,就会将它自己的私有页复制赋予该进程),
PAGE_NOACCESS(禁止一切访问), PAGE_NOCACHE(禁用已提交页的高速缓存)。
②、State的状态有空闲、预留和提交(MEM_FREE, MEM_RESERVE,MEM_COMMIT)。
③、Type有可执行映像(MEM_IMAGE, 如EXE或DLL文件)、内存映射文件(MEM_MAPPING)或私有内存区(MEM_PRIVATE)。这些相邻页面拥有相同的保护属性、状态和类型。
(3)、PERFORMANCE_INFORMATION结构定义如下:
typedef struct _PERFORMANCE_INFORMATION
{
DWORDcb; //按字节算的结构体大小
SIZE_TCommitTotal; //系统当前提交的页面总数
SIZE_TCommitLimit; //系统当前可提交的最大页面总数
SIZE_TCommitPeak; //系统历史提交的页面峰值
SIZE_TPhysicalTotal; //按页分配的总物理内存
SIZE_TPhysicalAvailable; //当前可用的物理内存
SIZE_TSystemCache; //系统cache容量
SIZE_TKernelTotal; //按页算的内存容量
SIZE_TKernelPaged; //分页池大小
SIZE_TPageSize; //页的大小
DWORDHandleCount; //打开的句柄数
DWORDProcessCount; //打开的进程个数
DWORDThreadCount; //打开的线程个数
} PERFORMANCE_INFORMATION, *PERFORMANCE_INFORMATION;
(4)、MEMORYSTATUSEX结构定义如下:
typedef struct _MEMORYSTATUSEX
{
DWORDdwLength; //结构体的大小
DWORDdwMemoryLoad; //物理内存的使用率
DWORDLONGullTotalPhys; //总的物理内存
DWORDLONGullAvailPhys; //可用的物理内存
DWORDLONGullTotalPageFile; //总的交换文件
DWORDLONGullAvailPageFile; //可用的交换文件
DWORDLONGullTotalVirtual; //总的虚拟内存
DWORDLONGullAvailVirtual; //可用的虚拟内存
DWORDLONGullAvailExtendedVirtual; //扩展内存
}MEMORYSTATUSEX, *LPMEMORYSTATUSEX;
2、编写程序
(1)、调用GlobalMemoryStatusEx()输出内存信息;
(2)、调用GetSystemInfo()输出系统信息;
(3)、调用GetPerformanceInfo()输出系统的存储器使用情况信息;
(4)、获取各个进程的信息;
(5)、对于选定的进程,利用VirtualQueryEx()检测进程的虚拟地址空间信息。
六、实验结果
……
七、实验分析与总结
通过理解Windows 的相关API,然后在程序中调用它,可以很顺利的得到上面的结果。对于本实验,自己掌握了许多关于Windows的内存信息系统函数,如GetSystemInfo(), VirtualQueryEx(), VirtualAlloc(),GetPerformanceInfo(),GlobalMemoryStatusEx()等,获取系统以及进程特征信息的函数有了更深入的了解,如各个函数的参数的意义和应用。通过此次实验也解除了我自己的一个疑惑,明白进程占用的存储空间远没有我想的那么多。在Windows平台上,每个进程的虚拟内存地址会映射为物理内存地址,Windows通过分配RAM、页面文件或两者中的空间,准确的知道APP所需要的内存。
八、实验源代码
// 内存监视.cpp : 定义控制台应用程序的入口点。
/* TITLE:设计一个内存监视器,能实时地显示当前系统中内存的使用情况,包括系统地址空间
的布局,物理内存的使用情况;能实时显示某个进程的虚拟地址空间布局和工作集信息等。
作者:野狼
时间:2017/4/5
*/
#include "stdafx.h"
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <windows.h>
#include <psapi.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <iomanip>
#pragma comment(lib,"Shlwapi.lib")
using namespace std;
//显示保护标记,该标记表示允许应用程序对内存进行访问的类型
inline bool TestSet(DWORD dwTarget, DWORD dwMask)
{
return ((dwTarget &dwMask) == dwMask);
}
#define SHOWMASK(dwTarget,type) if(TestSet(dwTarget,PAGE_##type)){cout << "," << #type;}
void ShowProtection(DWORD dwTarget)
{//定义的页面保护类型
SHOWMASK(dwTarget, READONLY);
SHOWMASK(dwTarget, GUARD);
SHOWMASK(dwTarget, NOCACHE);
SHOWMASK(dwTarget, READWRITE);
SHOWMASK(dwTarget, WRITECOPY);
SHOWMASK(dwTarget, EXECUTE);
SHOWMASK(dwTarget, EXECUTE_READ);
SHOWMASK(dwTarget, EXECUTE_READWRITE);
SHOWMASK(dwTarget, EXECUTE_WRITECOPY);
SHOWMASK(dwTarget, NOACCESS);
}
//遍历整个虚拟内存,并显示各内存区属性的工作程序的方法
void WalkVM(HANDLE hProcess)
{
SYSTEM_INFO si; //系统信息结构
ZeroMemory(&si, sizeof(si)); //初始化
GetSystemInfo(&si); //获得系统信息
MEMORY_BASIC_INFORMATION mbi; //进程虚拟内存空间的基本信息结构
ZeroMemory(&mbi, sizeof(mbi)); //分配缓冲区,用于保存信息
//循环整个应用程序地址空间
LPCVOID pBlock = (LPVOID)si.lpMinimumApplicationAddress;
while (pBlock < si.lpMaximumApplicationAddress)
{
//获得下一个虚拟内存块的信息
if (VirtualQueryEx(
hProcess, //相关的进程
pBlock, //开始位置
&mbi, //缓冲区
sizeof(mbi)) == sizeof(mbi)) //长度的确认,如果失败返回0
{
//计算块的结尾及其长度
LPCVOID pEnd = (PBYTE)pBlock + mbi.RegionSize;
TCHAR szSize[MAX_PATH];
//将数字转换成字符串
StrFormatByteSize(mbi.RegionSize, szSize, MAX_PATH);
//显示块地址和长度
cout.fill('0');
cout << hex << setw(8) << (DWORD)pBlock << "-" << hex << setw(8) << (DWORD)pEnd << (strlen(szSize) == 7 ? "(" : "(") << szSize << ")";
//显示块的状态
switch (mbi.State)
{
case MEM_COMMIT:
printf("已提交");
break;
case MEM_FREE:
printf("空闲");
break;
case MEM_RESERVE:
printf("已预留");
break;
}
//显示保护
if (mbi.Protect == 0 && mbi.State != MEM_FREE)
{
mbi.Protect = PAGE_READONLY;
}
ShowProtection(mbi.Protect);
//显示类型
switch (mbi.Type)
{
case MEM_IMAGE:
printf(", Image");
break;
case MEM_MAPPED:
printf(", Mapped");
break;
case MEM_PRIVATE:
printf(", Private");
break;
}
//检验可执行的映像
TCHAR szFilename[MAX_PATH];
if (GetModuleFileName(
(HMODULE)pBlock, //实际虚拟内存的模块句柄
szFilename, //完全指定的文件名称
MAX_PATH) > 0) //实际使用的缓冲区长度
{
//除去路径并显示
PathStripPath(szFilename);
printf(", Module:%s", szFilename);
}
printf("\n");
//移动块指针以获得下一个块
pBlock = pEnd;
}
}
}
int main(int argc, char* argv[])
{
MEMORYSTATUSEX statex; //
statex.dwLength = sizeof(statex);
//获取系统内存信息
GlobalMemoryStatusEx(&statex);
printf("-----------------------内存信息-----------------------\n");
//内存使用率
printf("物理内存的使用率为:%ld%%\n", statex.dwMemoryLoad);
//物理内存
printf("物理内存的总容量为: %.2fGB.\n", (float)statex.ullTotalPhys / 1024 / 1024 / 1024);
//可用物理内存
printf("可用的物理内存为: %.2fGB.\n", (float)statex.ullAvailPhys / 1024 / 1024 / 1024);
//提交的内存限制
printf("总的交换文件为:%.2fGB.\n", (float)statex.ullTotalPageFile / 1024 / 1024 / 1024);
//当前进程可以提交的最大内存量
printf("可用的交换文件为:%.2fGB.\n", (float)statex.ullAvailPageFile / 1024 / 1024 / 1024);
//虚拟内存
printf("虚拟内存的总容量为:%.2fGB.\n", (float)statex.ullTotalVirtual/1024 / 1024 / 1024);
//可用虚拟内存
printf("可用的虚拟内存为:%.2fGB.\n", (float)statex.ullAvailVirtual/1024 / 1024 / 1024);
//保留字段
printf("保留字段的容量为:%.2fByte.\n",statex.ullAvailExtendedVirtual);
printf("------------------------------------------------------\n");
SYSTEM_INFO si; //系统信息结构
ZeroMemory(&si, sizeof(si));
GetSystemInfo(&si); //获得系统信息
printf("---------------------系统信息-------------------------\n");
printf("内存页的大小为:%dKB.\n", (int)si.dwPageSize/1024);
cout << "每个进程可用地址空间的最小内存地址为: 0x" << si.lpMinimumApplicationAddress << endl;
cout << "每个进程可用的私有地址空间的最大内存地址为: 0x" << si.lpMaximumApplicationAddress << endl;
cout << "能够保留地址空间区域的最小单位为: " << si.dwAllocationGranularity/1024 << "KB" << endl;
printf("------------------------------------------------------\n");
//获取系统的存储器使用情况
PERFORMANCE_INFORMATION pi;
pi.cb = sizeof(pi);
GetPerformanceInfo(&pi, sizeof(pi));
printf("----------------系统的存储器使用情况------------------\n");
cout << "结构体的大小为: " << pi.cb << "B" << endl;
cout << "系统当前提交的页面总数: " << pi.CommitTotal << endl;
cout << "系统当前可提交的最大页面总数: " << pi.CommitLimit << endl;
cout << "系统历史提交页面峰值: " << pi.CommitPeak << endl;
cout << "按页分配的总物理内存: " << pi.PhysicalTotal << endl;
cout << "当前可用的物理内存为: " << pi.PhysicalAvailable << endl;
cout << "系统Cache的容量为: " << pi.SystemCache << endl;
cout << "内存总量(按页)为: " << pi.KernelTotal << endl;
cout << "分页池的大小为: " << pi.KernelPaged << endl;
cout << "非分页池的大小为: " << pi.KernelNonpaged << endl;
cout << "页的大小为: " << pi.PageSize << endl;
cout << "打开的句柄个数为: " << pi.HandleCount << endl;
cout << "进程个数为: " << pi.ProcessCount << endl;
cout << "线程个数为: " << pi.ThreadCount << endl;
printf("------------------------------------------------------\n");
//获得每个进程的信息
printf("------------------各个进程的信息----------------------\n");
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bMore = ::Process32First(hProcessSnap, &pe);
while (bMore)
{
HANDLE hP = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
PROCESS_MEMORY_COUNTERS pmc;
ZeroMemory(&pmc, sizeof(pmc));
if (GetProcessMemoryInfo(hP, &pmc, sizeof(pmc)) == TRUE)
{
cout << " 进程ID:";
wcout << pe.th32ProcessID << endl;
cout << " 进程名称:";
wcout << pe.szExeFile << endl;
cout << " 虚拟内存的大小为:" << (float)pmc.WorkingSetSize / 1024 << "KB" << endl;
}
bMore = ::Process32Next(hProcessSnap, &pe);
}
printf("----------------------------------------------------\n");
printf("-------进程虚拟地址空间布局和工作集信息查询---------\n");
printf("输入要查询的进程ID:");
int x;
cin >> x;
HANDLE hP = OpenProcess(PROCESS_ALL_ACCESS, FALSE, x);
WalkVM(hP);
getchar();
getchar();
return 0;
}