第15章 在应用程序中使用虚拟内存(2)

15.6 改变保护属性

(1)VritualProtect函数

参数

描述

PVOID pvAddress

指向要修改属性的内存基地址

SIZE_T dwSize

区域的大小,以字节为单位

DWORD flNewProtect

PAGE_*(除PAGE_WRITECOPY、PAGE_EXCUTE_WRITECOPY外)

PDWORD pflOldProtect

返回原来的保护属性,有时虽然不需要返回这个信息,但必须传入一个有效的pflOldProtect参数

(2)注意点

  ①保护属性是与整个物理存储页相关联的,不能给一个字节指定保护属性。

  ②当若干连续的物理存储页跨越不同区域时,则VirtualProtect不能改变它们的保护属性。如果有相邻区域,又想改变跨区域的连续页面的保护属性,则必须多次调用该函数。

【VirtualProtect程序】

第15章 在应用程序中使用虚拟内存(2)

#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)结构

第15章 在应用程序中使用虚拟内存(2) 

(2)MEM_RESET标志:当内存中某页面内容被修改时,该页面的“脏”位置1,表示“己修改”。以后如果要从exe、DLL或页交换文件中载入新的页面到内存里。系统会在内存中查找可用的页面,如果找到的是己被修改过的页面,那么系统将把它们换出到页交换文件。但可以通过修改该页面的这个标志(即复位,即把“脏”位置0,表示没有修改过),此时该页面的内容将被当作垃圾而废弃,所以也就不会被写入页交换文件。这对一部分并不重要的数据来说,是可行的,而且这样做也有利于提高系统的性能。

(3)调用VirtualAlloc并传入MEM_RESET时,可能发生的两种情况

    ①要重置的页面还没被映射到物理内存中,这时系统将抛弃这些页面,当下次被映射到物理内存时,会使用新的、全部清零的内存页。

②重置页面己被映射进内存中,系统会将他们这些页面标志为没被修改过(即重置),从而这些页面的内容被当作垃圾,也就不会被写入页交换文件中。这些页面会在下次

(4)重置内存页面的注意事项

  ①当调用VirtualAlloc预订或提交时基地址能常会被向下取整到页面大小整数倍(即向地址小的方向)。大小则会被向上取整到页面大小的整数倍(即大的方向)。但重置存储器时,VirtualAlloc会从相反的方向进行取整!其目的是确保基地址之前的同一页面还有其他重要数据的情况下,不会被抛弃。同理大小向下取整也是出于同样的目的

  ②MEM_RESET只能单独使用,不能将其与其他标志按位或起来。

  ③传入MEM_RESET时,需要传一个有效的保护属性(如PAGE_READWITE),即使实际上并没有用到这个值。

【MemReset程序】重置存储器示例程序

 第15章 在应用程序中使用虚拟内存(2)  第15章 在应用程序中使用虚拟内存(2)

//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""
"
    "