实验三:存储管理
1.实验目的:
(1)通过实验了解windows内存的使用,学习如何在应用程序中管理内存、体会Windows应用程序内存的简单性和自我防护能力;
(2)了解windows的内存结构和虚拟内存的管理,进而了解进程堆和windows为使用内存而提供的一些扩展功能。
2.实验内容
(1)Windows提供了一个API即GetSystemInfo() ,以便用户能检查系统中虚拟内存的一些特性;
(2)使用VirtualQueryEX()函数来检查虚拟内存空间;
(3)能正确使用系统函数GetMeoryStatus()和数据结构MEMORY_STATUS了解系统内存和虚拟存储空间使用情况,会使用VirsualAlloc()函数和VirsualFree()函数分配和释放虚拟内存空间。
3.实验步骤:
程序一:虚拟内存信息检测
实验描述:
(1) 利用GetSystemInfo()获取系统信息,得到进程地址空间的下界和上界;
(2) 循环进程的地址空间,利用VirtualQueryEx()得到每个虚拟内存区域的信息,包括虚拟内存区域的块大小、块的状态、保护方式、显示类型以及可执行映像名;
代码实现:
// 实验三虚拟内存的检测.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) ;
// 显示当前虚拟内存块的地址和大小
//hex 使用16进制 setw(8)设置输出字段长度为8
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)windows页文件
// 显示当前虚拟内存块的类型
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(虚拟页面映射到外存), READONLY(只读), Mapped 内存映射文件
free(空闲状态没有使用)
Reserved(预留状态,只是占了个地方没有映射到外存) Image 可执行文件
Private windows页文件(该虚拟内存块不被共享,如堆栈。)
*/
程序二:分配虚拟内存
实验描述:
(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,测试是否能够成功填充.
代码实现:
// 虚拟内存分配.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include ".\..\实验三第三题\实验三虚拟内存的检测.cpp"
using namespace std;
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(),
//相当于使用这些内存,测试内存操作是否成功。
//虚拟内存分配方式1 malloc()动态分配虚拟内存
pBlock = :: malloc(c_dwGigabyte) ;
:: FillZero (pBlock, c_dwMegabyte);
:: free(pBlock) ;
//虚拟内存分配方式2 MEM_COMMIT
pBlock = :: VirtualAlloc(
NULL, // 不指定起始地址
c_dwGigabyte, // 要求1GB
MEM_COMMIT, // 虚拟页面映射到外存。
// 此处指映射到页文件,但还没有分配外存空间,当换出时才分配外存。
// 此时也没有分配物理内存,只有当程序访问这部分虚地址时才会真正分配物理内存。
PAGE_READWRITE) ; // 读写操作
:: FillZero(pBlock, c_dwMegabyte);
:: VirtualFree(pBlock, 0, MEM_RELEASE);
//虚拟内存分配方式3 MEM_RESERVE 招呼也没打
pBlock = :: VirtualAlloc(
NULL, // 不指定起始地址
c_dwGigabyte, // 要求1GB
MEM_RESERVE, // 以这个地址开始的虚拟内存块程序要使用,
// 进程其他分配虚拟内存的操作不得使用这段内存。
// 还没有映射到外存。
PAGE_READWRITE) ; // 读写操作
:: FillZero(pBlock, c_dwMegabyte) ;
:: VirtualFree(pBlock, 0, MEM_RELEASE) ;
*/
//虚拟内存分配方式4 MEM_RESERVE+映射到外存(在外存中产生映射) 相当于于外存打了个招呼
pBlock = :: VirtualAlloc(
NULL, // 不指定起始地址
c_dwGigabyte, // 要求1GB
MEM_RESERVE, // 以这个地址开始的虚拟内存块程序要使用,
// 进程其他分配虚拟内存的操作不得使用这段内存。
// 还没有映射到外存。
PAGE_READWRITE) ; // 读写操作
//给虚拟内存调配外存。
:: VirtualAlloc(
pBlock, // 指定起始地址
c_dwMegabyte,
MEM_COMMIT, // 虚拟页面映射到外存。
// 此处指映射到页文件,但还没有分配外存空间,当换出时才分配外存。
// 此时也没有分配物理内存,只有当程序访问这部分虚地址时才会真正分配物理内存。
PAGE_READWRITE) ;
/**/
cout<<"pBlock====>"<<pBlock<<endl;
//虚拟内存检测
::WalkVM(::GetCurrentProcess());
//虚拟内存清零
:: FillZero(pBlock, c_dwMegabyte);
//释放虚拟内存
:: VirtualFree(pBlock, 0, MEM_RELEASE);
return 0;
}
/*
思考题:
1.访问没有提交的进程空间能成功吗?
不成功 没有提交的进程没有映射到外存页文件, 访问这部分虚地址也不会分配物理内存
2.使用MEM_COMMIT标志调用VirtualAlloc()成功后物理内存已经分配了吗?
没有分配物理内存, 只有当进行读写操作是才会分配(什么时候用什么时候分配)
没有分配物理内存,只有当程序访问这部分虚拟地址时才会真正分配物理内存。
未分配,缺页中断里分配,数组元素清零之后发生缺页中断,然后分配物理内存
3.利用“虚拟内存的检测”程序检测上述虚拟内存分配方式4所分配虚拟地址块的信息。
实现:
#include ".\..\实验三第三题\实验三虚拟内存的检测.cpp"
cout<<"pBlock====>"<<pBlock<<endl;
//虚拟内存检测
::WalkVM(::GetCurrentProcess());
*/