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

15.1 预订(Reserve)地址空区域

(1)VirutalAlloc(pvAddress,dwSize,fdwAllocationType,fdwProtect)

   ①PVOID pvAddress参数:内存地址,要预订的地址空间中的哪一块。

    A.这个参数也说明也该函数是较底层函数的原因,因为可以由我们指定在哪个地址分配内存!但这也是有限制的,即只能在用户模式分区中分配内存,否则将会失败。

    B.预订区域时,系统始终是按CPU的分配粒度来分配区域的。如果指定的pvAddress不是64K的整数倍时,则系统会将pvAddress向下取整到64K的整数倍作为预订区域的起始地址(也叫区域的基地址)。如pvAddress=300×65536 + 8192时,会被取整为300×65536。(但应注意:在提交时,起始地址始终(与区域大小一样),始终都是页面大小的整数倍

    C.当该参数为NULL时,系统会自动找一块空闲的区域来预订。但系统不保证一定会从地址空间的底部往上分配,也不保证一定从地址空间的顶部往下分配。

    D.如果在fdwAllocatioType指定MEM_TOP_DOWN时会尽可能地在高地址中分配空间

  ②SIZE_T dwSize参数——用来指定预订区域的大小,以字节为单位

    A.区域大小是以CPU页面大小的整数倍来预订的(一般为4KB)。

    B.如果指定的dwSize不是4KB的整数倍,则会被向上取整。如预订大小为62KB,则最终得到的区域大小为64KB。

  ③DWORD fdwAllocationType参数——要预订还是提交等


类型


备注


MEM_RESERVE


A.预留区域时并没有分配物理存储器(物理存储器指物理内存、页交换文件或文件映像),只是增加了一个描述进程虚拟地址空间使用状态的数据结构,用来记录这个区域己被预订。

B.因没有真正分配物理存储器,所以这个区域不能直接访问,否则会引起“内存访问违规”。但“预订”操作的速度相对较快。


MEM_COMMIT


A.提交区域,为预订区域调拨后备的物理储存器类型。注意,提交时也并没有立刻从物理内存分配空间,而是提交给页交换文件!

B.直到当第一次访问这段区域时,系统会抛出“缺页错误”并处理该错误,这时才真正映射到物理内存。这种策略叫“demand-paging”策略,这既节省了时间,也节省了内存的浪费。


MEM_PHYSICAL


A.只用在“地址空间扩展(AWE)”中,该标志必须且只能与MEM_RESERVE一起使用。

B.页面必须具有可读写属性(具体该标志的使用见后面的《AWE》一节)


MEM_TOP_DOWN


传NULL给pvAddress,同时对MEM_TOP_DOWN与MEM_RESERVE按位或传给fdwAllocationType可以让系统从尽可能高的内存地址来预订区域。这样可以防止从进程地址空间的中间预订,从而避免引起内存碎片

  ④DWORD fdwProtect参数——给区域指定保护属性

    A.预订区域时,可以使用PAGE_NOACCESS、PAGE_READWRITE、PAGE_READONLY、PAGE_EXECUTE_*等几类。

    B.但预订时不能使用PAGE_WRITECOPY、PAGE_EXECUTE_WRITECOPY、PAGE_GUARD、PAGE_NOCACHE和PAGE_WRITECOMBINE,因为这些标志跟物理存储器有关。

15.2 给区域调拨(Commit)物理存储器

(1)调用VirtualAlloc时传入MEM_COMMIT标志,在给物理存储器指定页面保护属性时,可以与预订时一致,也可以完全不同!但最终的保护性页以物理页面的保护属性为准(而不是区域的保护属性)

(2)在己预订的区域中,可以无须一下子给整个区域都调拨物理存储器,可以用pvAddress和dwSize来指定要想提交的部分。(即哪个内存地址,大小为多少,同时x86中都是以4KB的页面大小为基本单位)

(3)由于系统是基于一个完全页面(4KB大小)来指定保护属性的,所以同一个物理存储页不可能有不同的保护属性。但同一区域中的另一个页面可以是另一种保护属性

15.3 同时预订和调拨物理存储器

(1)调用VirtualAlloc,并传入MEM_RESERVE|MEM_COMMIT标志

(2)大页面内存的分配

  ①返回大页面分配粒度:SIZE_T GetLargePageMinimum(),如果系统不支持大页面,则返回0

  ②调用VirtualAlloc时传入MEM_SERVE|MEM_COMMIT|MEM_LARGE_PAGE,即必须是同时预订和提交内存,而不能分开来实现。

  ③同时要分配的内存块大小(dwSize)必须是大页面分配粒度的整倍数,页面的保护属性fdwProtect也必须指定为PAGE_READWRITE。

【注意】

  ①大页面分配到的内存是不可换页的,也就是必须驻留内存,而不会被换出到页交换文件。所以就要求物理内存页面要被锁定。

  ②默认下,要使用“锁定内存页面”要先得到授权。(方法是“控制面板”→“管理工具”→“本地安全策略”→“本地策略”→“用户权限分配”→“锁定内存页”中添加用户或组)

15.4 何时调拨物理存储器

(1)先预订区域而不提交物理存储器(再次提醒,实际上是提交到页交换文件的)可以节省大量的物理存储器。

(2)当应用程序要访问未提交的内存地址时,会引发异常,系统会通知我们的应用程序。我们要用的就是给应用程序设置一个异常处理程序,在这里面再去真正地执行提交物理存储器的操作,从而实现内存的“按需分配”。

15.5 撤消调拨物理存储器及释放区域

(1)VirtualFree(pvAdress,dwSize,fdwFreeType)

  ①撤消并释放整个区域:pvAddress指定为区域的基地址,即预订区域时VirtualAlloc的返回值。同时dwSize必须传入0,因为系统知道该区域的大小。第3个参数传MEM_RELEASE。(注意此时不能只撤消一部分区域。此外,这时提交的内存被释放且预订的虚拟地址空间也归返,这就意味着MEM_RELEASE不能得MEM_DECOMMAND一起使用!

  ②撤消部分物理存储器(但保留虚拟地址空间):指定pvAddress和dwSize,并传入MEM_DECOMMIT。与预订和提交物理存储器一校址,撤消也是基于页面粒度的,即系统会撤消地址空间被pvAddress到pvAddress+dwSize覆盖的所有页面(注意,页面是个4KB的空间,即如果pvAddress是位于某个页面中间,那该页面也作为整体被撤消)。

(2)何时撤消调拨物理存储器

  ①由于撤消操作是以页面粒度为基本单位的,当我们进行撤消时,可能会把当前正在使用的某个变量的内存也给撤消掉。

  ②要安全的撤消操作,就要为每个变量或结构体增加一个标志位,用来记录它们正在被使用的情况。只有等到同一个页面所有相邻的结构不在被使用时,才能进行撤消操作。

【DemandPaging程序】利用结构化异常机制进行内存的“按需分配”

     

#include <windows.h>
#include <tchar.h>
#include <locale.h>

#define PAGELIMIT 80    //请求页面的总数
LPBYTE lpNextPage=NULL;      //下一次请求的页面地址
DWORD dwPageSize = 0;   //页面的大小(分配粒度)
DWORD dwPages = 0;      //当前己经请求的页面数量

INT PageFaultExceptionFilter(DWORD dwCode){
    LPVOID lpvResult;

    //如果不是页面错误,则退出
    if (dwCode !=EXCEPTION_ACCESS_VIOLATION){
        _tprintf(_T("错误发生[%d]\n"), dwCode);
        return EXCEPTION_EXECUTE_HANDLER; //当错误发生时,执行except代码块中的内容。
    }

    _tprintf(_T("页面访问错误,"));

    if (dwPages >=PAGELIMIT){
        _tprintf(_T("超出页面数量%d\n"), PAGELIMIT);

        return EXCEPTION_EXECUTE_HANDLER; //当错误发生时,执行except代码块中的内容。
    }

    lpvResult = VirtualAlloc((LPVOID)lpNextPage, dwPageSize, MEM_COMMIT, PAGE_READWRITE);
    if (lpvResult == NULL){
        _tprintf(_T("提交新页面失败!\n"));
        return EXCEPTION_EXECUTE_HANDLER;
    } else{
        _tprintf(_T("将重新提交一个新页面!\n"));
    }

    dwPages++;
    lpNextPage += dwPageSize;
    return EXCEPTION_CONTINUE_EXECUTION; //当错误发生时,执行except代码块后面的内容。
}

VOID ErrorExit(LPTSTR oops){
    _tprintf(_T("错误!%s,出错代码为%ld\n"),
             oops, GetLastError());
    exit(0);
}

int _tmain(){
    _tsetlocale(LC_ALL, _T("chs"));

    LPVOID lpvBase; //要测试的内存基地址
    LPTSTR lpPtr;   //通用的字符指针
    BOOL bSuccess;  //
    SYSTEM_INFO si; //系统信息结构体
    GetSystemInfo(&si);
    dwPageSize = si.dwPageSize;

    _tprintf(_T("CPU页面大小为%dKB.\n"), si.dwPageSize/1024);

    //在进程的虚拟空间中预订页面
    lpvBase = VirtualAlloc(NULL,   //系统自动选择基地址
                           PAGELIMIT*dwPageSize,//区域的大小
                           MEM_RESERVE, //预订(注意不是提交)
                           PAGE_NOACCESS);//保护属性,不可读写

    if (lpvBase == NULL){
        ErrorExit(_T("预订页面失败"));
    }

    lpPtr =(LPTSTR)lpvBase;
    lpNextPage = (LPBYTE)lpvBase;
    for (DWORD i = 0; i < PAGELIMIT*dwPageSize/sizeof(TCHAR);i++){
        __try{
            //写入内存
            lpPtr[i] = _T(‘a‘); //因未提交物理存储器,这里会引发异常
        }
        //如果发生页面错误,则提交另一个页面并尝试继续
        __except (PageFaultExceptionFilter(GetExceptionCode())){
            //以下的代码只有在“过滤函数”中提交下个页面失败时
            //才会被调用
            ExitProcess(GetLastError());
        }
    }

    //释放区域
    bSuccess = VirtualFree(lpvBase,
                           0,  //当使用MEM_RELEASE时,必须为0
                           MEM_RELEASE);//撤消提交的区域(这里不用加MEM_DECOMMIT)

    _tprintf(_T("释放操作%s.\n"), bSuccess ? _T("成功") : _T("失败"));

    return 0;
}

【VMAlloc程序】内存的垃极收集(VirtualFree函数的应用)

时间: 2024-10-10 06:12:11

第15章 在应用程序中使用虚拟内存(1)的相关文章

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

15.6 改变保护属性 (1)VritualProtect函数 参数 描述 PVOID pvAddress 指向要修改属性的内存基地址 SIZE_T dwSize 区域的大小,以字节为单位 DWORD flNewProtect PAGE_*(除PAGE_WRITECOPY.PAGE_EXCUTE_WRITECOPY外) PDWORD pflOldProtect 返回原来的保护属性,有时虽然不需要返回这个信息,但必须传入一个有效的pflOldProtect参数 (2)注意点 ①保护属性是与整个物理

SQL点滴15—在SQL Server 2008中调用C#程序

原文:SQL点滴15-在SQL Server 2008中调用C#程序 T-SQL的在执行普通的查询的时候是很高效的,但是在执行循环,判断这样的语句的时候效率就不那么的高了.这时可以借助CLR了,我们可以在SQL Server 2008中扩展C#程序来完成循环等过程式的查询,或者其他SQL不便实现的功能.这个随笔中将介绍在SQL Server中扩展C#程序实现正则表达式的替换功能. 新建一个类库程序命名为Regex,打开Visual Studio 2008,点击File,点击New,点击Proje

《Entity Framework 6 Recipes》中文翻译——第九章EntityFramework在N层架构程序中的应用(五)

问题 你有一个通过WCF获取的对象,你想把它删除掉 解决方案 你有如下的模型 我们的模型代表了发票Invoice上的付款Payment.在我们的应用程序中,我们实现了一个WCF服务处理来自客户端的数据库交互.在我们的情况下,我们要使用服务删除一个支付对象实体.保持尽可能简单的解决方案,我们将建立一个WCF服务库并定义模型里面按照以下步骤: 1.新建WCF服务库程序,命名Recipe5 2.右键单击recipe5项目,并选择“添加新项.选择数据?ADO.NET实体数据模型.使用向导添加一个模型包含

第15章 就不能换DB吗?—抽象工厂模式

由于抽象工厂在我们编程当中经常使用和常见,所有本篇文章对<大话设计模式>中的15章做了很详细的比较.通过一个Dao层可以更换访问任意数据库的例子来学习抽象工厂模式.例如:Dao层可以访问Sqlserver数据库,也可以访问Access数据库,当程序新增访问Oracle数据库时,无需修改现有代码,只需要添加访问Oracle相关的类就可以,实现了开闭原则.本篇文章的例子中每种数据库上都有User和Department表,我们Dao层对这两个表进行查询和插入操作. 最基本数据库访问 一下是访问Sql

第十一章:WEB浏览器中的javascript

客户端javascript涵盖在本系列的第二部分第10章,主要讲解javascript是如何在web浏览器中实现的,这些章节介绍了大量的脚本宿主对象,这些对象可以表示浏览器窗口.文档树的内容.这些章节同样涵盖重要的web应用所需要的网络编程API.本地存储和检索数据.画图等.主要包含内容有以下章节: web浏览器中的javascript / window对象 /  脚本化文档 /  脚本化css / 事件处理 / 校本化http / jQuery类库 / 客户端存储  /  多媒体和图形编程 /

第 15 章 组合模式【Composite Pattern】

以下内容出自:<<24种设计模式介绍与6大设计原则>> 大家在上学的时候应该都学过“数据结构”这门课程吧,还记得其中有一节叫“二叉树”吧,我们上 学那会儿这一章节是必考内容,左子树,右子树,什么先序遍历后序遍历什么,重点就是二叉树的的遍历,我还记得当时老师就说,考试的时候一定有二叉树的构建和遍历,现在想起来还是觉的老师是正确的,树状结果在实际项目应用的非常广泛. 咱就先说个最常见的例子,公司的人事管理就是一个典型的树状结构,你想想你公司的结构是不是这样: 老大,往下一层一层的管理,

C++ Primer Plus 第15章 友元、异常和其他

第15章 友元.异常和其他 1.友元不仅有友元函数,还能是友元类 还可以将类中的某一个成员函数指定为另一个类的友元 尽管友元被授予从外部访问私有部门的权限,单并不与面向对象编程思想相愽,相反,它们提高了公有接口的灵活性 2.类的成员函数作为其他类的友元,涉及到类的声明顺序. 还有一个函数作为两个类的友元 这方面内容看P607-611 3.嵌套类:在另一个类中声明的类被称为嵌套类 类嵌套与包含不一样.包含意味着将一个类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型. 4.对

Gradle 教程说明 用户指南 第10章 Web应用程序----快速入门

本章是一项正在进行的工作 本章介绍Web应用程序的Gradle支持.Gradle 为Web应用程序提供了两个插件:War插件和Jetty插件. War插件扩展了Java插件来构建你的项目WAR文件. Jetty插件扩展了War的插件,让你可以把你的Web应用程序部署到一个嵌入式的Jetty Web容器. 本章的示例程序:samples/webApplication/quickstart 10.1 构建一个WAR文件 要构建一个WAR文件,需要应用war 插件. 例,war插件 build.gra

3.30日第八次作业,第14章,采购管理,15章,信息文档和配置管理

3.30日第八次作业,第14章,采购管理,15章,信息文档和配置管理   第14章.采购管理1.采购管理包括哪些过程?(记)P382-383 答:1).编制采购计划.2).编制询价计划.3).询价.招投标.4).供方选择.5).合同管理.6).合同收尾. 2.编制采购计划过程的成果是什么?P386-387 答:1).采购管理计划.2).采购工作说明书. 3.判断:每个采购工作说明书都来自于项目范围基准.P387 答:是的. 4.结合P388页表14-1,工作说明书应该清楚地描述哪些内容?P388