熟悉Windows存储器管理中提供的各类机制和实现的请求调页技术。
通过实验,了解Windows内存结构和虚拟内存管理,学习如何在应用程序中管理内存。了解当前系统中内存的使用情况,包括系统地址空间的布局,物理内存的使用情况;能够实时显示某个进程的虚拟地址空间布局和工作集信息等
在Windows平台上设计一个内存监视器,能够实时地显示当前系统中的内存使用情况,包括地址空间的布局,物理内存的使用情况,能实时显示某个进程的虚拟地址空间布局和工作集信息等
相关的系统调用:
GetSystemInfo
VirtualQueryEx
VirtualAlloc
GetPerformanceInfo
GlobalMemoryStatusEx
本实验基于本机macOS系统下的Windows虚拟机完成,具体实验环境如下:
宿主机环境配置如下:
Windows虚拟机环境配置如下:
内存指的是计算机配置的RAM,系统可以管理所有的物理内存。操作系统(本实验特指Windows)通过分配RAM、页面文件或者二者中的空间,可以准确获取应用程序需要的内存
在Windows下运行的每个应用程序都认为能够独占4GB的虚拟地址空间,其中,低2GB为进程私有地址空间,用于存放程序和动态链接库,高2GB为所有进程共享区,也就是操作系统占用区。
事实上,少有进程可以占有2GB存储空间。Windows把每个进程的虚拟内存地址映射为物理内存地址。
Windows中使用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
:结构体大小,需要在调用GlobalMemoryStatusEx
之前设定dwMemoryLoad
:物理内存使用的百分比(从0到100)ullTotalPhys
:实际物理内存的大小(单位:字节)ullAvailPhys
:现在可用的物理内存大小(单位:字节)这部分物理内存是可以不需要写入磁盘立即被回收的内存。它是备用,空闲和零列表大小的综合ullTotalPageFile
:系统或当前进程已经提交的内存限制中的较小者(单位:字节)。要获取系统范围的已提交内存限制需要调用GetPerformanceInfo
ullAvailPageFile
:当前进程可以提交的最大内存量(单位:字节)ullTotalVirtual
:调用进程的虚拟地址空间的用户模式部分的大小(单位:字节)。此值取决于进程类型,处理机类型和操作系统配置,在x86处理器上多为2GBullAvailVirtual
:当前调用进程的虚拟地址空间的用户模式中未保留和未提交的内存量(单位:字节)ullAvailExtendevVirtual
:保留值,始终为0sysinfoapi.h
(包括在Windows.h
中)Windows使用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;
说明:(该结构体成员较多,只介绍部分)
dwPageSize
:页面大小和页面保护和提交的粒度,是VirtualAlloc
函数使用的页面大小lpMinimumApplictionAddress
:指向应用程序和动态链接库(DLL)可访问的最低内存指针lpMaximumApplicationAddress
:指向应用程序和动态链接库(DLL)可访问的最高内存指针dwActiveProcessorMask
:配置到系统中的处理器集掩码dwNumberOfProcessors
:当前组中的逻辑处理器数,若要检索,则需要调用GetLogicalProcessorInformation
函数dwAllocationGranularity
:可以分配虚拟内存的其实地址的粒度sysinfoapi.h
(包含于Windows.h
中)Windows使用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;
说明:(该结构体成员较多,只介绍部分)
cb
:结构体大小(单位:字节)CommitTotal
:系统当前提交的页数,提交页面(使用VirtualAlloc
和MEM_COMMIT
)会立即更新该值;但是在访问页面之前,物理内存不会被填充CommitPeak
:自上次系统重新引导以来同时处于已提交状态的最大页数PhysicalTotal
:实际物理内存,以页为单位PhysicalAvailable
:当前可用的物理内存量,以页为单位。这部分内存是可以立即重用无需写入磁盘的内存,是备用,空闲和零列表大小的总和SystemCache
:系统缓存内存量,以页为单位。该值为备用列表大小加上系统工作集KernelTotal
:分页和非分页内核池中当前内存的总和,以页为单位KernelPaged
:当前在分页内核池的内存,以页为单位KernelNonpaged
:当前在非分页内核池中的内存,以页为单位PageSize
:页面大小,以字节为单位HandleCount
:当前打开手柄的数量ProcessCount
:当前进程数ThreadCount
:当前线程数psapi.h
Windows使用PROCESSENTRY32
结构体描述在拍摄快照时驻留在系统地址空间中的进程列表中的条目
语法如下:
typedef struct tagPROCESSENTRY32 {
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID;
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads;
DWORD th32ParentProcessID;
LONG pcPriClassBase;
DWORD dwFlags;
CHAR szExeFile[MAX_PATH];
} PROCESSENTRY32;
说明:
dwSize
:结构体大小,以字节为单位。在调用Process32First
函数之前要设置为sizeof(PROCESSENTRY32)
,若没有设置,则函数调用会是失败th32ProcessID
:进程标识符PIDcntThreads
:进程启动的线程数th32ParentProcessID
:创建此进程的进程标识符,即父进程的PIDpcPriClassBase
:此进程创建的线程的基本优先级szExeFile
:进程的可执行文件tlhelp32.h
Windows使用PROCESS_MEMORY_COUNTERS
结构体保存进程的内存统计信息
语法如下:
typedef struct _PROCESS_MEMORY_COUNTERS {
DWORD cb;
DWORD PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
} PROCESS_MEMORY_COUNTERS;
说明:
cb
:结构体大小PageFaultCount
:页面错误的数量WorkingSetSize
:当前工作集大小(单位:字节)PeakWorkingSetSize
:峰值工作集大小(单位:字节)QuotaPeakPagedPoolUsage
:峰值分页池使用情况(单位:字节)QuotaPagedPoolUsage
:当前页面缓冲池使用情况(单位:字节)QuotaPeakNonPagedPoolUsage
:非页面缓冲池使用率的峰值(单位:字节)QuotaNonPagedPoolUsage
:当前非分页池使用情况(单位:字节)PagefileUsage
:此进程的Commit Charge值(单位:字节),Commit Charge是内存管理器为正在运行的进程提交的内存总量PeakPagefileUsage
:此进程的生命周期内提交的峰值(单位:字节)psapi.h
Windows使用MEMORY_BASIC_INFORMATION
保存有关进程虚拟地址空间的一系列页面的信息,提供给VirtualQuery
和VirtualQueryEx
使用
语法如下:
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;
说明:
BaseAddress
:指向页面区域的基址指针AllocationBase
:指向VirtualAlloc
函数分配的一系列页面的基址指针AllocationProtect
:最初分配区域时的内存保护选项RegionSize
:从基址开始的区域大小,其中所有页面都具有相同属性(单位:字节)State
:页面的状态,可以是以下值:
MEM_COMMIT
:表示已在内存中或磁盘页面文件中为其分配物理存储的已提交页面MEM_FREE
:表示调用进程无法访问且可以分配的空闲页面MEM_RESERVE
:表示保留进程的虚拟地址空间范围而不分配任何物理存储的保留页面Protect
:该区域中页面的访问保护Type
:页面的类型,可以是以下值:
MEM_IMAGE
:指示区域内的内存页面映射到映射文件到视图中MEM_MAPPED
:指示区域内的内存页面映射到节的仕途MEM_PRIVATE
:指示区域内页面是私有的winnt.h
(包含于Windows.h
中)Windows使用GlobalMemoryStatusEx
函数检索有关系统物理和虚拟内存当前使用情况的信息
语法如下:
BOOL GlobalMemoryStatusEx(
LPMEMORYSTATUSEX lpBuffer
);
说明:
lpBuffer
:指向MEMORYSTATUSEX
结构的指针,该结构接受有关当前内存可用性的信息sysinfoapi.h
(包括在Windows.h
中)KERNEL32.DLL
Windows使用GetSystemInfo
函数获取当前系统的信息
语法如下:
void GetSystemInfo(
LPSYSTEM_INFO lpSystemInfo
);
说明:
lpSystemInfo
:指向接受信息的SYSTEM_INFO
结构体指针sysinfoapi.h
(包括在Windows.h
中)KERNEL32.DLL
Windows使用GetPerformanceInfo
函数获取性能信息,填充PERFORMANCE_INFORMATION
结构中的性能值
语法如下:
BOOL GetPerformanceInfo(
PPERFORMANCE_INFORMATION pPerformanceInformation,
DWORD cb
);
说明:
pPerformanceInformation
:指向PERFORMANCE_INFORMATION
结构的指针cb
:PERFORMANCE_INFORMATION
结构的大小,(单位:字节)TRUE
FALSE
psapi.h
Psapi.dll
,否则使用Kernel32.dll
Windows使用CreateToolhelp32Snapshot
函数获取指定进程的快照,以及这些进程使用的堆、模块和线程
语法如下:
HANDLE CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
说明:
dwFlags
:包含在快照中的系统部分,本实验选用的是TH32CS_SNAPPROCESS
,表示包括快照中的所有进程,可以使用Process32First
枚举这些进程th32ProcessID
:要包含在快照中的进程的标识符,本实验该参数为0,指示当前进程INVALID_HANDLE_VALUE
tlhelp32.h
KERNEL32.DLL
Windows使用Process32First
函数获取有关系统快照中遇到的第一额进程的信息
语法如下:
BOOL Process32First(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
说明:
hSnapshot
:从先前调用的CreateToolhelp32Snapshot
函数返回的快照句柄ppe
:指向PROCESSENTRY32
结构的指针TRUE
,否则返回FALSE
PROCESSENTRY32
的dwSize
设置为结构大小,否则会失败tlhelp32.h
KERNEL32.DLL
Windows使用Process32Next
函数获取有关系统快照中记录的下一个进程的信息
语法如下:
BOOL Process32Next(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
说明:
hSnapshot
:从先前调用的CreateToolhelp32Snapshot
函数返回的快照句柄lppe
:指向PROCESSENTRY32
结构的指针tlhelp32.h
KERNEL32.DLL
Windows使用OpenProcess
函数打开现有的本地进程对象
语法如下:
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);
说明:
dwDesiredAccess
:对进程对象的访问权限binheritHandle
:若此值为TRUE,则该进程创建的进程将继承该句柄。否则,子进程不会继承该句柄dwProcessId
:要打开的本地进程的标识符,在实验中取PROCESSENTRY32
结构中的th32ProcessID
processthreadspai.h
(包含于Windows.h
中)KERNEL32.DLL
Windows使用GetProcessMemoryInfo
函数获取指定进程的内存使用情况的信息
语法如下:
BOOL GetProcessMemoryInfo(
HANDLE Process,
PPROCESS_MEMORY_COUNTERS ppsmemCounters,
DWORD cb
);
说明:
Process
:进程的句柄ppsemCounters
:指向PROCESS_MEMORY_COUNTERS
或PROCESS_MEMORY_COUNTERS_EX
结构的指针cb
:ppsemCounters
结构大小psapi.h
Psapi.dll
,否则使用Kernel32.dll
该段程序不是Windows提供的API,而是自己定义的若干函数
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_READ);
SHOWMASK(dwTarget,EXECUTE);
SHOWMASK(dwTarget,EXECUTE_READWRITE);
SHOWMASK(dwTarget,EXECUTE_WRITECOPY);
SHOWMASK(dwTarget,NOACCESS);
}
本实验源代码包含在文件MemoryWatch.cpp
中,如下:
#include
#include
#include
#include
#include
#include"Psapi.h"
#include"tlhelp32.h"
#include"shlwapi.h"
#define DIV_GB (1024*1024*1024)
#define DIV_KB (1024)
using namespace std;
void memoryInfo(){
MEMORYSTATUSEX statex;
statex.dwLength=sizeof(statex);
GlobalMemoryStatusEx(&statex);
printf("THE SYSTEM MEMORY INFORMATION FLOWING:\n\n");
printf("The usage of memory is %ld%%\n",statex.dwMemoryLoad);
printf("The total capacity of memory is %.2fGB\n",(float)statex.ullTotalPhys/DIV_GB);
printf("The available memory is %.2fGB\n",(float)statex.ullAvailPhys/DIV_GB);
printf("The total pages file is %.2fGB\n",(float)statex.ullTotalPageFile/DIV_GB);
printf("The available pages file is %.2fGB\n",(float)statex.ullAvailPageFile/DIV_GB);
printf("The total virtual space is %.2fGB\n",(float)statex.ullTotalVirtual/DIV_GB);
printf("The available virtual space is %.2fGB\n",(float)statex.ullAvailVirtual/DIV_GB);
printf("The available extended virtual space is %.2fGB\n",(float)statex.ullAvailExtendedVirtual/DIV_GB);
printf("\n\n");
}
void systemInfo(){
SYSTEM_INFO si;
ZeroMemory(&si,sizeof(si));
GetSystemInfo(&si);
printf("THE SYSTEM INFORMATION FLOWING:\n\n");
printf("The page size and the granularity of page protection and commitment is %dKB\n",(int)si.dwPageSize/DIV_KB);
printf("The pointer to the lowest memory address accessible to applications and dynamic-link libraries (DLLs) is 0x%.8x\n",si.lpMinimumApplicationAddress);//%.8x控制长度
printf("The pointer to the highest memory address accessible to applications and DLLs is 0x%x\n",si.lpMaximumApplicationAddress);
printf("The number of logical processors in the current group is %d\n",si.dwNumberOfProcessors);
printf("The granularity for the starting address at which virtual memory can be allocated is %dKB\n",si.dwAllocationGranularity/DIV_KB);
printf("\n\n");
}
void performanceInfo(){
PERFORMANCE_INFORMATION pi;
pi.cb=sizeof(pi);
GetPerformanceInfo(&pi, sizeof(pi));
printf("THE PERFORMANCE INFORMATION IS FOLLOWING:\n\n");
printf("The number of pages currently committed by the system is %d\n",pi.CommitTotal);
printf("The current maximum number of pages that can be committed by the system without extending the paging file(s) is %d\n",pi.CommitLimit);
printf("The maximum number of pages that were simultaneously in the committed state since the last system reboot is %d\n",pi.CommitPeak);
printf("The amount of actual physical memory in pages is %d\n",pi.PhysicalTotal);
printf("The amount of physical memory currently available is %d\n",pi.PhysicalAvailable);
printf("The amount of system cache memory is %d\n",pi.SystemCache);
printf("The sum of the memory currently in the paged and nonpaged kernel pools is %d\n",pi.KernelTotal);
printf("The memory currently in the nonpaged kernel pool is %d\n",pi.KernelNonpaged);
printf("The size of a page is %dKB",pi.PageSize/DIV_KB);
printf("The current number of open handles is %d\n",pi.HandleCount);
printf("The current number of processes is %d\n",pi.ProcessCount);
printf("The current number of threads is %d\n",pi.ThreadCount);
printf("\n\n");
}
void processInfo(){
PROCESSENTRY32 pe;
pe.dwSize=sizeof(pe);
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bMore = ::Process32First(hProcessSnap, &pe);
printf("THE ALL PROCESS INFORMATION IS FOLLOWING:\n\n");
printf("PID\t | Execute File\t\t\t\t\t | Working Set Size(KB)\n");
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))){
int len=strlen(pe.szExeFile);
printf("%d\t | %s",pe.th32ProcessID,pe.szExeFile);t
for(int i=0;i<=45-len;i++){
printf(" ");//控制长度,打表好看
}
printf("| %.2f\n",(float)pmc.WorkingSetSize/DIV_KB);
}
bMore = ::Process32Next(hProcessSnap, &pe);
}
CloseHandle(hProcessSnap);
}
//以下三个功能函数来自书本第292页
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_READ);
SHOWMASK(dwTarget,EXECUTE);
SHOWMASK(dwTarget,EXECUTE_READWRITE);
SHOWMASK(dwTarget,EXECUTE_WRITECOPY);
SHOWMASK(dwTarget,NOACCESS);
}
void processVirtualMemoryInfo(){
int pid;
printf("Please enter the pid that you want to search\n");
SYSTEM_INFO si;
ZeroMemory(&si,sizeof(si));
GetSystemInfo(&si);
HANDLE hProcess;
while(TRUE){
printf("PID:\t");
scanf("%d",&pid);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess==NULL) {
printf("You Enter A INVALID Pid, Please Check and Enter again:\n");
}
else{
break;
}
}
MEMORY_BASIC_INFORMATION mbi;
ZeroMemory(&mbi,sizeof(mbi));
LPCVOID pBlock = (LPVOID)si.lpMinimumApplicationAddress;
printf("Address\t\t\t | Size\t\t | State\t | Protect\t | Type\t\t | Module\n\n");
while(pBlock < si.lpMaximumApplicationAddress){
if (VirtualQueryEx(hProcess,pBlock,&mbi,sizeof(mbi))==sizeof(mbi)) {
LPCVOID pEnd = (PBYTE)pBlock + mbi.RegionSize;
TCHAR szSize[MAX_PATH];
StrFormatByteSize(mbi.RegionSize, szSize, MAX_PATH);
printf("%.8x-%.8x\t",(DWORD)pBlock,(DWORD)pEnd);
printf("| %s\t",szSize);
switch (mbi.State)
{
case MEM_COMMIT:
printf("| Commited\t");
break;
case MEM_FREE:
printf("| Free\t\t");
break;
case MEM_RESERVE:
printf("| Reserved\t");
break;
default:
break;
}
if (mbi.Protect == 0 && mbi.State != MEM_FREE) {
mbi.Protect = PAGE_READONLY;
}
ShowProtection(mbi.Protect);
printf("\t");
switch (mbi.Type)
{
case MEM_IMAGE:
printf("| Image\t");
break;
case MEM_MAPPED:
printf("| Mapped\t");
break;
case MEM_PRIVATE:
printf("| Private\t");
break;
default:
break;
}
TCHAR szFilename[MAX_PATH];
if (GetModuleFileName((HINSTANCE)pBlock,szFilename,MAX_PATH)>0) {
PathStripPath(szFilename);
printf("\t| %s",szFilename);
}
printf("\n");
pBlock=pEnd;
}
}
}
void clear(){
system("cls");
}
int main(int argc, char const *argv[])
{
while(TRUE){
printf("What do you want to know?\n");
printf("Memory Information press: 'u'\nSystem Information press 'i'\nPerformance Information press 'o'\nProcess Infomation press 'p'\nProcess Virtual Memory Information press 'm'\nQuit press 'q'\n");
char c=getchar();
if(c=='q'){
break;
}
else if(c=='u'){
getchar();//去掉回车
memoryInfo();
}
else if(c=='i'){
getchar();
systemInfo();
}
else if(c=='o'){
getchar();
performanceInfo();
}
else if(c=='p'){
getchar();
processInfo();
}
else if(c=='m'){
getchar();
processVirtualMemoryInfo();
getchar();
}
}
return 0;
}
程序运行方式如下:
g++ MemoryWatch.cpp -lPsapi -lShlwapi -o MemoryWatch.exe
MemoryWatch.exe
注意:由于实验中需要用到动态链接库,所以编译时需要添加动态链接库参数
实验者在主要程序外嵌套了一层交互程序,运行程序可以交互进行实时内存监控,如下
需要输入PID,若PID无效则会提示无效信息,可以通过4.5.5查询有效的PID