15.6 改变保护属性
(1)VritualProtect函数
参数 |
描述 |
PVOID pvAddress |
指向要修改属性的内存基地址 |
SIZE_T dwSize |
区域的大小,以字节为单位 |
DWORD flNewProtect |
PAGE_*(除PAGE_WRITECOPY、PAGE_EXCUTE_WRITECOPY外) |
PDWORD pflOldProtect |
返回原来的保护属性,有时虽然不需要返回这个信息,但必须传入一个有效的pflOldProtect参数 |
(2)注意点
①保护属性是与整个物理存储页相关联的,不能给一个字节指定保护属性。
②当若干连续的物理存储页跨越不同区域时,则VirtualProtect不能改变它们的保护属性。如果有相邻区域,又想改变跨区域的连续页面的保护属性,则必须多次调用该函数。
【VirtualProtect程序】
#include <windows.h> #include <tchar.h> #include <time.h> #include <locale.h> #define MEMSIZE (1024*1024) int _tmain(){ _tsetlocale(LC_ALL, _T("chs")); srand((unsigned)time(NULL)); //1.保留并提交内存(1MB) VOID* pRecv = VirtualAlloc(NULL, MEMSIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (pRecv !=NULL){ _tprintf(_T("分配%dKB内存成功! "), MEMSIZE / 1024); } else{ _tprintf(_T("申请%dKB内存失败!(错误代码:%d) "), MEMSIZE / 1024,GetLastError()); return 0; } //2.内存写入操作 float* pfArray = (float*)pRecv; for (int i = 0; i < MEMSIZE / sizeof(float); i++){ pfArray[i] = 1.0f*(rand() % 10); } _tprintf(_T("从[0x%X]处开始,成功写入%d个Float型的数据! "), pRecv,MEMSIZE / sizeof(float));
//3.更改保护属性为只读 DWORD dwOldProtect = 0; BOOL bOk = VirtualProtect(pRecv, MEMSIZE, PAGE_READONLY, &dwOldProtect); if (bOk){ _tprintf(_T("成功修改申请的内存空间为只读属性! ")); } else{ _tprintf(_T("试图修改申请的内存空间为只读属性失败!(错误代码:%d) "),GetLastError()); return 0; }
//4.读取所有值进行求和 float fSum = 0.0f; for (int i = 0; i < MEMSIZE / sizeof(float);i++){ fSum += pfArray[i]; } _tprintf(_T("%d个随机数总和为%f "), MEMSIZE / sizeof(float),fSum); //5.试图写入第10个元素,这将引起异常 __try{ pfArray[9] = 0.0f; }__except (EXCEPTION_EXECUTE_HANDLER){ _tprintf(_T("非法访问内存,试图在只读内存中写入数据! ")); } //6.直接释放 bOk = VirtualFree(pRecv, 0, MEM_RELEASE); _tprintf(_T("释放内存%s! "),bOk? _T("成功"):_T("失败")); return 0; }
15.7 重置物理存储器的内容
(1)进程页表中的项(页表项,PTE)结构
(2)MEM_RESET标志:当内存中某页面内容被修改时,该页面的“脏”位置1,表示“己修改”。以后如果要从exe、DLL或页交换文件中载入新的页面到内存里。系统会在内存中查找可用的页面,如果找到的是己被修改过的页面,那么系统将把它们换出到页交换文件。但可以通过修改该页面的这个标志(即复位,即把“脏”位置0,表示没有修改过),此时该页面的内容将被当作垃圾而废弃,所以也就不会被写入页交换文件。这对一部分并不重要的数据来说,是可行的,而且这样做也有利于提高系统的性能。
(3)调用VirtualAlloc并传入MEM_RESET时,可能发生的两种情况
①要重置的页面还没被映射到物理内存中,这时系统将抛弃这些页面,当下次被映射到物理内存时,会使用新的、全部清零的内存页。
②重置页面己被映射进内存中,系统会将他们这些页面标志为没被修改过(即重置),从而这些页面的内容被当作垃圾,也就不会被写入页交换文件中。这些页面会在下次
(4)重置内存页面的注意事项
①当调用VirtualAlloc预订或提交时基地址能常会被向下取整到页面大小整数倍(即向地址小的方向)。大小则会被向上取整到页面大小的整数倍(即大的方向)。但重置存储器时,VirtualAlloc会从相反的方向进行取整!其目的是确保基地址之前的同一页面还有其他重要数据的情况下,不会被抛弃。同理大小向下取整也是出于同样的目的。
②MEM_RESET只能单独使用,不能将其与其他标志按位或起来。
③传入MEM_RESET时,需要传一个有效的保护属性(如PAGE_READWITE),即使实际上并没有用到这个值。
【MemReset程序】重置存储器示例程序
//MemReset.cpp
/************************************************************************ Module: MemReset.cpp Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre ************************************************************************/ #include "../../CommonFiles/CmnHdr.h" #include <tchar.h> ////////////////////////////////////////////////////////////////////////// int WINAPI _tWinMain(HINSTANCE, HINSTANCE, PTSTR, int){ TCHAR szAppName[] = TEXT("MEM_RESET 测试"); TCHAR szTestData[] = TEXT("Some text data"); //提交一页并修改他的内容;1024会被向上取整到一个页面的大小(4KB) PTSTR pszData = (PTSTR)VirtualAlloc(NULL, 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); _tcscpy_s(pszData, 1024, szTestData); //如果不访问数据,就把pszData里的当成垃圾看待 if (IDNO ==MessageBox(NULL,TEXT("是否要保存数据,以便稍后来访问呢?"),szAppName,MB_YESNO)){ //告诉系统将pszData所指空间当作垃圾,这样该内存中的数据就不会被写入页交换文件中 //注意:传入MEM_RESET给VVirtualAlloc函数,该函数会将基地址和大小设置到一个安全的 //范围,比如: // VirtualAlloc(pvData,5000,MEM_RESET,PAGE_READWRITE); //当CPU的分配粒度为4KB时,将被重置1个页面。如果大于4KB时,被重置0个页面。 MEMORY_BASIC_INFORMATION mbi; //以下调用总是会成功,先获取区域大小并将让重置的区域大小等于该值。(4KB的整数倍) VirtualQuery(pszData, &mbi, sizeof(mbi)); VirtualAlloc(pszData, mbi.RegionSize, MEM_RESET, PAGE_READWRITE); } //为了演示页面被重置过,这里可以给系统内存增加一些压力,如 //提交跟整个物理内存大小一样大的区域(注意,虽然是预订和提交这么大的 //地址空间,但系统并不会真正为其分配物理内存,只是提交到页交换文件中 MEMORYSTATUS mst; GlobalMemoryStatus(&mst); SIZE_T dwSize = mst.dwTotalPhys>mst.dwTotalVirtual ? mst.dwAvailPhys: mst.dwTotalPhys; PVOID pvDummy = VirtualAlloc(NULL, dwSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); //在刚申请的整个空间中写入数据,这会给系统的内存造成很大的压力, //并导致原来内存中的一些的页面被写入到页交换文件(被修改过的页面,如pszData //所指的页面,当然如果后来又有被重置的话,是不会被写入的) if (pvDummy == NULL){ MessageBox(NULL, TEXT("试图提交所有可用物理内存大小的空间失败!"), szAppName, MB_OK); VirtualFree(pszData, 0, MEM_RELEASE); return 0; } ZeroMemory(pvDummy, dwSize); //因写入整个物理内存,这些导致 //原来的pszData页面被换出。 //比较原始的数据,与当前pszData里数据是否相同 if (_tcscmp(pszData, szTestData) == 0) { //pszData的数据与原始数据一样,因为ZeroMemory会迫使页面被写入页交换文件 MessageBox(NULL, TEXT("修改的数据己被保存!"), szAppName, MB_OK); } else{ //pszData的数据与原始数据不同,ZeroMemory并没有引起我们的页面被写入页交换文件 MessageBox(NULL, TEXT("修改的数据未被保存!"), szAppName, MB_OK); } //释放地址空间 VirtualFree(pvDummy, 0, MEM_RELEASE); VirtualFree(pszData, 0, MEM_RELEASE); return 0; }
//resouce.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 15_MemReset.rc 使用 // #define IDI_MEMRESET 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
//MemReset.rc
// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h " END 2 TEXTINCLUDE BEGIN "#include ""winres.h"" " "