目录
- 实验三、存储管理
- 实验目的
- 实验内容
- 实验步骤
- 1、虚拟内存信息检测
- 2、分配虚拟内存
实验三、存储管理
实验目的
(1)通过实验了解windows内存的使用,学习如何在应用程序中管理内存、体会Windows应用程序内存的简单性和自我防护能力;
(2)了解windows的内存结构和虚拟内存的管理,进而了解进程堆和windows为使用内存而提供的一些扩展功能。
实验内容
(1)Windows提供了一个API即GetSystemInfo() ,以便用户能检查系统中虚拟内存的一些特性;
(2)使用VirtualQueryEX()函数来检查虚拟内存空间;
(3)能正确使用系统函数GetMeoryStatus()和数据结构MEMORY_STATUS了解系统内存和虚拟存储空间使用情况,会使用VirsualAlloc()函数和VirsualFree()函数分配和释放虚拟内存空间。
实验步骤
1、虚拟内存信息检测
实验描述
(1) 利用GetSystemInfo()获取系统信息,得到进程地址空间的下界和上界;
(2) 循环进程的地址空间,利用VirtualQueryEx()得到每个虚拟内存区域的信息,包括虚拟内存区域的块大小、块的状态、保护方式、显示类型以及可执行映像名;
CheckVM.cpp
// CheckVM.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <shlwapi.h>
#include <iomanip>
#pragma comment(lib, "Shlwapi.lib")// 显示内存保护的方法。
// 保护标记指示:允许应用程序对内存进行访问的类型以及操作系统强制访问的类型
inline bool TestSet(DWORD dwTarget, DWORD dwMask)
{return ((dwTarget &dwMask) == dwMask) ;
}//宏定义一个函数
//TestSet为内联函数
# define SHOWMASK(dwTarget, type) \
if (TestSet(dwTarget, PAGE_##type) ) \{std :: 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) ) // 大小的确认{// 计算下一虚拟内存块的起始地址LPCVOID pEnd = (PBYTE) pBlock + mbi.RegionSize;//保存当前虚拟内存块的长度TCHAR szSize[MAX_PATH];:: StrFormatByteSize(mbi.RegionSize, szSize, MAX_PATH) ;// 显示当前虚拟内存块的地址和大小std :: cout.fill ('0') ;std :: cout<< std :: hex << std :: setw(8) << (DWORD) pBlock<< "-"<< std :: hex << std :: setw(8) << (DWORD) pEnd<< (:: strlen(szSize)==7? " (" : " (") << szSize<< ") " ;// 显示当前虚拟内存块的状态switch(mbi.State){case MEM_COMMIT :std :: cout << "Committed" ;//虚拟页面映射到外存。break;case MEM_FREE :std :: cout << "Free" ; break;case MEM_RESERVE :std :: cout << "Reserved" ; // 以这个地址开始的虚拟内存块被预留,// 该进程再分配虚拟内存时不得使用这段内存。// 此时还没有映射到外存。break;}//重新调整当前虚拟内存块的保护方式if(mbi.Protect==0 && mbi.State!=MEM_FREE){mbi.Protect=PAGE_READONLY;}// 显示当前虚拟内存块的保护方式ShowProtection(mbi.Protect);// 显示当前虚拟内存块的类型switch(mbi.Type){case MEM_IMAGE : //该虚拟内存块映射的是可执行文件,如*.dll,*.exe。std :: cout << ", Image" ;break;case MEM_MAPPED: //该虚拟内存块映射的是数据文件,用CreateFileMapping()创建。std :: cout << ", Mapped";break;case MEM_PRIVATE : //该虚拟内存块不被共享,如堆栈。std :: cout << ", Private" ;break;}// 获得可执行的文件名。TCHAR szFilename [MAX_PATH] ;if (:: GetModuleFileName ((HMODULE) pBlock, // 一个模块的句柄。模块句柄跟一般的句柄不一样,// 模块句柄指向的就是EXE和DLL等在虚拟地址空间的位置。// 如果该参数为NULL,该函数返回该应用程序全路径。szFilename, // 文件名称MAX_PATH)>0) // 实际使用的缓冲区大小{// 除去文件名的路径并将文件名显示出来:: PathStripPath(szFilename) ;std :: cout << ", Module: " << szFilename;}std :: cout << std :: endl;// 移动虚拟内存块指针以获得下一个虚拟内存块pBlock = pEnd;}}
}int main(int argc, char* argv[])
{// 遍历当前进程的虚拟地址空间::WalkVM(::GetCurrentProcess());return 0;
}
思考题
1.进程的虚拟地址空间可以映射到哪些文件?
(1)可执行文件(2)库文件(3)数据文件(4)页面文件// 显示当前虚拟内存块的类型
switch (mbi.Type)
{
case MEM_IMAGE: //该虚拟内存块映射的是可执行文件,如*.dll,*.exe。std ::cout << ", Image";break;
case MEM_MAPPED: //该虚拟内存块映射的是数据文件,用CreateFileMapping()创建。std ::cout << ", Mapped";break;
case MEM_PRIVATE: //该虚拟内存块不被共享,如堆栈。std ::cout << ", Private";break;
}虚拟内存块地址 内存块大小 00010000-00011000 (4.00 KB)映射到外存) 虚拟内存块的状态 Committed(虚拟页面映射到外存), free(空闲状态没有使用)Reserved(预留状态,只是占了个地方没有映虚拟内存块的保护方式 虚拟内存块的类型READONLY(只读) Mapped 内存映射文件Image 可执行文件Private windows页文件(该虚拟内存块不被共享,如堆栈。)
2、分配虚拟内存
实验描述
(1) 在main函数中利用malloc()分配1G内存,然后将前1M字节填充为0,测试是否能够成功填充;
(2) 利用VirtualAlloc()分配1G内存,使用MEM_COMMIT标志,然后将前1M字节填充为0,测试是否能够成功填充;
(3) 利用VirtualAlloc()分配1G内存,使用MEM_RESERVE标志,然后将前1M字节填充为0,测试是否能够成功填充;
(4) 利用VirtualAlloc()分配1G内存,使用MEM_RESERVE标志,然后将前1M字节提交并填充为0,测试是否能够成功填充.
VMAllocation.cpp
// VMAllocation.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>
#include <iostream>void FillZero(LPVOID pBlock, DWORD dwSize)
{_try{BYTE* arFill = (BYTE *) pBlock;for (DWORD dwFill = 0; dwFill < dwSize; ++dwFill){arFill [dwFill] = 0; }std :: cout << "Memory zeroed." << std :: endl;}_except(EXCEPTION_EXECUTE_HANDLER){std :: cout << "Could not zero memory. " << std :: endl;}
}int main(int argc, char* argv[])
{LPVOID pBlock;//1G大小DWORD c_dwGigabyte = 1 << 30;//1M大小DWORD c_dwMegabyte = 1 << 20;//以下运行4种虚拟内存分配方式,分别调用FillZero(),//相当于使用这些内存,测试内存操作是否成功。//虚拟内存分配方式1pBlock = :: malloc(c_dwGigabyte) ;:: FillZero (pBlock, c_dwMegabyte) ;:: free(pBlock) ;//虚拟内存分配方式2pBlock = :: VirtualAlloc(NULL, // 不指定起始地址c_dwGigabyte, // 要求1GBMEM_COMMIT, // 虚拟页面映射到外存。// 此处指映射到页文件,但还没有分配外存空间,当换出时才分配外存。// 此时也没有分配物理内存,只有当程序访问这部分虚地址时才会真正分配物理内存。PAGE_READWRITE) ; // 读写操作:: FillZero(pBlock, c_dwMegabyte) ;:: VirtualFree(pBlock, 0, MEM_RELEASE) ;//虚拟内存分配方式3pBlock = :: VirtualAlloc(NULL, // 不指定起始地址c_dwGigabyte, // 要求1GBMEM_RESERVE, // 以这个地址开始的虚拟内存块程序要使用,// 进程其他分配虚拟内存的操作不得使用这段内存。// 还没有映射到外存。PAGE_READWRITE) ; // 读写操作:: FillZero(pBlock, c_dwMegabyte) ;:: VirtualFree(pBlock, 0, MEM_RELEASE) ;//虚拟内存分配方式4pBlock = :: VirtualAlloc(NULL, // 不指定起始地址c_dwGigabyte, // 要求1GBMEM_RESERVE, // 以这个地址开始的虚拟内存块被预留,// 该进程再分配虚拟内存时不得使用这段内存。// 此时还没有映射到外存。PAGE_READWRITE) ; // 读写操作//给虚拟内存调配外存。:: VirtualAlloc(pBlock, // 指定起始地址c_dwMegabyte,MEM_COMMIT, // 虚拟页面映射到外存。// 此处指映射到页文件,但还没有分配外存空间,当换出时才分配外存。// 此时也没有分配物理内存,只有当程序访问这部分虚地址时才会真正分配物理内存。PAGE_READWRITE) ;:: FillZero(pBlock, c_dwMegabyte) ;//虚拟内存清零:: VirtualFree(pBlock, 0, MEM_RELEASE) ;//释放虚拟内存return 0;
}
思考题
1.访问没有提交的进程空间能成功吗?不成功 没有提交的进程没有映射到外存页文件, 访问这部分虚地址也不会分配物理内存
2.使用MEM_COMMIT标志调用VirtualAlloc()成功后物理内存已经分配了吗?
没有分配物理内存, 只有当进行读写操作是才会分配。
只有在进行读写的时候会发生缺页中断,然后分配物理内存。
3.利用“虚拟内存的检测”程序检测上述虚拟内存分配方式4所分配虚拟地址块的信息。
#include "CheckVM.cpp"cout<<"pBlock====>"<<pBlock<<endl;//虚拟内存检测::WalkVM(::GetCurrentProcess());
操作及结果如下所示:
// CheckVM.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <shlwapi.h>
#include <iomanip>
#pragma comment(lib, "Shlwapi.lib")// 显示内存保护的方法。
// 保护标记指示:允许应用程序对内存进行访问的类型以及操作系统强制访问的类型
inline bool TestSet(DWORD dwTarget, DWORD dwMask)
{return ((dwTarget &dwMask) == dwMask) ;
}//宏定义一个函数
//TestSet为内联函数
# define SHOWMASK(dwTarget, type) \
if (TestSet(dwTarget, PAGE_##type) ) \{std :: 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) ) // 大小的确认{// 计算下一虚拟内存块的起始地址LPCVOID pEnd = (PBYTE) pBlock + mbi.RegionSize;//保存当前虚拟内存块的长度TCHAR szSize[MAX_PATH];:: StrFormatByteSize(mbi.RegionSize, szSize, MAX_PATH) ;// 显示当前虚拟内存块的地址和大小std :: cout.fill ('0') ;std :: cout<< std :: hex << std :: setw(8) << (DWORD) pBlock<< "-"<< std :: hex << std :: setw(8) << (DWORD) pEnd<< (:: strlen(szSize)==7? " (" : " (") << szSize<< ") " ;// 显示当前虚拟内存块的状态switch(mbi.State){case MEM_COMMIT :std :: cout << "Committed" ;//虚拟页面映射到外存。break;case MEM_FREE :std :: cout << "Free" ; break;case MEM_RESERVE :std :: cout << "Reserved" ; // 以这个地址开始的虚拟内存块被预留,// 该进程再分配虚拟内存时不得使用这段内存。// 此时还没有映射到外存。break;}//重新调整当前虚拟内存块的保护方式if(mbi.Protect==0 && mbi.State!=MEM_FREE){mbi.Protect=PAGE_READONLY;}// 显示当前虚拟内存块的保护方式ShowProtection(mbi.Protect);// 显示当前虚拟内存块的类型switch(mbi.Type){case MEM_IMAGE : //该虚拟内存块映射的是可执行文件,如*.dll,*.exe。std :: cout << ", Image" ;break;case MEM_MAPPED: //该虚拟内存块映射的是数据文件,用CreateFileMapping()创建。std :: cout << ", Mapped";break;case MEM_PRIVATE : //该虚拟内存块不被共享,如堆栈。std :: cout << ", Private" ;break;}// 获得可执行的文件名。TCHAR szFilename [MAX_PATH] ;if (:: GetModuleFileName ((HMODULE) pBlock, // 一个模块的句柄。模块句柄跟一般的句柄不一样,// 模块句柄指向的就是EXE和DLL等在虚拟地址空间的位置。// 如果该参数为NULL,该函数返回该应用程序全路径。szFilename, // 文件名称MAX_PATH)>0) // 实际使用的缓冲区大小{// 除去文件名的路径并将文件名显示出来:: PathStripPath(szFilename) ;std :: cout << ", Module: " << szFilename;}std :: cout << std :: endl;// 移动虚拟内存块指针以获得下一个虚拟内存块pBlock = pEnd;}}
}/*
int main(int argc, char* argv[])
{// 遍历当前进程的虚拟地址空间::WalkVM(::GetCurrentProcess());return 0;
}
*/
// VMAllocation.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include "C:\CheckVM\CheckVM.cpp"void FillZero(LPVOID pBlock, DWORD dwSize)
{_try{BYTE* arFill = (BYTE *) pBlock;for (DWORD dwFill = 0; dwFill < dwSize; ++dwFill){arFill [dwFill] = 0; }std :: cout << "Memory zeroed." << std :: endl;}_except(EXCEPTION_EXECUTE_HANDLER){std :: cout << "Could not zero memory. " << std :: endl;}
}int main(int argc, char* argv[])
{LPVOID pBlock;//1G大小DWORD c_dwGigabyte = 1 << 30;//1M大小DWORD c_dwMegabyte = 1 << 20;//虚拟内存分配方式4pBlock = :: VirtualAlloc(NULL, // 不指定起始地址c_dwGigabyte, // 要求1GBMEM_RESERVE, // 以这个地址开始的虚拟内存块被预留,// 该进程再分配虚拟内存时不得使用这段内存。// 此时还没有映射到外存。PAGE_READWRITE) ; // 读写操作//给虚拟内存调配外存。:: VirtualAlloc(pBlock, // 指定起始地址c_dwMegabyte,MEM_COMMIT, // 虚拟页面映射到外存。// 此处指映射到页文件,但还没有分配外存空间,当换出时才分配外存。// 此时也没有分配物理内存,只有当程序访问这部分虚地址时才会真正分配物理内存。PAGE_READWRITE) ;std::cout<<"pBlock====>"<<pBlock<<std::endl;//虚拟内存检测::WalkVM(::GetCurrentProcess());:: FillZero(pBlock, c_dwMegabyte) ;//虚拟内存清零:: VirtualFree(pBlock, 0, MEM_RELEASE) ;//释放虚拟内存return 0;
}