[笔记]Windows核心编程《十五》在应用程序中使用虚拟内存

简介: ]Windows核心编程《十五》在应用程序中使用虚拟内存

系列文章目录



[笔记]Windows核心编程《一》错误处理、字符编码

[笔记]Windows核心编程《二》内核对象

[笔记]Windows核心编程《三》进程

[笔记]Windows核心编程《四》作业

[笔记]快乐的LInux命令行《五》什么是shell

[笔记]Windows核心编程《五》线程基础

[笔记]Windows核心编程《六》线程调度、优先级和关联性

[笔记]Windows核心编程《七》用户模式下的线程同步

[笔记]Windows核心编程《八》用内核对象进行线程同步

[笔记]Windows核心编程《九》同步设备I/O和异步设备I/O

[笔记]Windows核心编程《十一》Windows线程池

[笔记]Windows核心编程《十二》纤程

[笔记]Windows核心编程《十三》windows内存体系结构

[笔记]Windows核心编程《十四》探索虚拟内存

[笔记]Windows核心编程《十五》在应用程序中使用虚拟内存

[笔记]Windows核心编程《十六》线程栈

[笔记]Windows核心编程《十七》内存映射文件

[笔记]Windows核心编程《十八》堆栈

[笔记]Windows核心编程《十九》DLL基础

[笔记]Windows核心编程《二十》DLL的高级操作技术

[笔记]Windows核心编程《二十一》线程本地存储器TLS

[笔记]Windows核心编程《二十二》注入DLL和拦截API

[笔记]Windows核心编程《二十三》结构化异常处理


相关:

参考4

参考1

参考2

参考3


文章目录



   系列文章目录

   前言

   一、预定地址空间区域

       1.1 VirtualAlloc函数

   二、给区域调拨物理存储器

       案例:如何提交物理存储器

   三、同时预定和调拨物理存储器

       大页面支持

   四、何时调拨物理存储器

       案例:实现一个电子表格应用程序

           前置条件

           对比传统实现

           使用虚拟内存实现及步骤

           确定何时提交/调拨物理存储器

   五、撤销调拨物理存储器及释放区域

       VirtualFree函数

       正常回收并全部释放情况

       回收某些物理存储器情况

       回收原则:按页面粒度回收

       MEM_RELEASE和MEM_DECOMMIT

           MEM_RELEASE

           MEM_DECOMMIT

       15.5.1 何时撤销调拨物理存储器

       15.5.2 虚拟内存分配示例程序

   六、改变保护属性

   七、重置物理存储器的内容

       MemReset示例程序

   八、地址窗口扩展

       AWE示例程序

   总结

       预定空间和调拨空间的关系

       撤销调拨物理存储器和释放物理存储器的关系


前言


Microsoft Windows 提供三种机制来对内存进行操控:

  • 虚拟内存 最适合用来管理大型对象数组或大型结构数组
  • 内存映射文件 最适合用来管理大型数据流(通常是文件),以及同一机器上运行的多个进程之间的共享数据。
  • 堆 最用来适合管理大量的小型对象


一、预定地址空间区域


1.1 VirtualAlloc函数

我们可以调用VirtualAlloc函数来预定进程中的地址空间区域:

LPVOID WINAPI VirtualAlloc(
  __in_opt  LPVOID lpAddress,
  __in      SIZE_T dwSize,
  __in      DWORD flAllocationType,
  __in      DWORD flProtect
);

lpAddress : 想要预订地址空间的哪一块,如果要在进程地址空间第50MB的地方分配区域。我们要传52 428 800(5010241024)给pvAddress参数。系统始终按分配粒度的整数倍来分配的因此如果我们想要在19668992(30065536+8192)的地方预订区域,那么系统会向下取整到64整数倍,也就是19660800(30065536)然后取整后的地址预订区域。如果VirtualAlloc能满足我们的要求,那么它会预订一块区域并返回该区域的基地址。如果我们还给pvAddesss指定了参数,那么,它的返回值就等于我们传给pvASddress参数值向下取整到64KB的整数倍。如果该参数为NULL,系统决定该空间的分配。


dwSize: 指定我们想要分析区域的大小,以字节为单位。由于系统始终都根据cpu页面大小整数倍来预订区域,因此如果我们的页面大小为4KB,8KB或16KB的机器上预订62 KB的区域的话,那么最终得到的区域大小为64KB。


flAllocationType: 是预订区域还是调拨物理存储器,如果是预订,就要传 MEM_RESERVE 为参数如果打算预订一块区域,并且用很长时间,那我们可能会希望系统从尽可能高的内存地址来预订。这样可以防止进程地址空间的中间预订区域,从而避免可能会引起内存碎片。

如果希望尽可能从高位内存地址来预定区域,那么我们必须传NULL给pvAddress,MEM_TOP_DOWN标志和MEM_RESERVE标志连接起来传给fdwAllocationType参数传入。


fdwProtect: 给区域指定保护属性。如果应用程序在非统一内存访问的机器上运行,我们可以调用VirtualAllocExNuma函数来强制系统在某个节点的物理内存中分配一部分虚拟内存。


   当保留一个区域时,应该为该区域赋予一个已提交内存最常用的保护属性。

   例如,如果打算提交的物理存储器的保护属性是 PAGE_READWRITE(这是最常用的保护属性),那么应该用PAGE_READWRITE保护属性来保留该区域。当区域的保护属性与已提交内存的保护属性相匹配时,系统保存的内部记录的运行效率最高。


   可以使用下列保护属性中的任何一个: PAGE_NOACCESS、PAGE _READWRITE、PAGE_READONLY、PAGE_EXECUTE、PAGE_EXECUTE_READ或PAGE_EXECUTE_READWRITE。但是,既不能设定 PAGE_WRITECOPY属性,也不能设定PAGE_EXECUTE_WRITECOPY属性。如果设定了这些属性,VirtualAlloc函数将不保留该区域,并且返回NULL。另外,当保留地址空间区域时,不能使用保护属性标志 PAGE_GUARD,PAGE_NOCACHE或PAGE_WRITECOMBINE,这些标志只能用于已提交的内存。


   注意 Windows 98只支持PAGE_NOACCESS、PAGE_READONLY和PAGE_READWRITE保护属性。如果试图保留使用 PAGE_EXECUT E或PAGE_ EXECUTE_READ两个保护属性的区域,将会产生一个带有 PAGE_READONLY保护属性的区域。同样,如果保留一个使用 PAGE _EXECUTE_READWRITE保护属性的区域,就会产生一个带有PAGE _READWRITE保护属性的区域。


二、给区域调拨物理存储器


在预定区域之后,我们还需要给给区域调拨物理存储器,这样才能访问其中的内存地址。


   系统会从页交换文件中来调拨物理存储器给区域。在调拨物理存储器时,起始地址始终都是页面大小的整数倍,整个大小也是页面大小的整数倍


为了调拨物理存储器,我们必须再次调用VirtualAlloc,但我们会传MEM_COMMIT作为fdwAllocationType的值。


在已保留的区域中,你必须告诉Vi r t u a l A l l o c函数,你想将物理存储器提交到何处,以及要提交多少物理存储器。为了做到这一点,可以在 pvA d d r e s s参数中设定你需要的内存地址,并在d w S i z e参数中设定物理存储器的数量(以字节为计量单位)。


   注意,不必立即将物理存储器提交给整个区域。


案例:如何提交物理存储器

比如说,你的应用程序是在 x86 CPU上运行的,该应用程序保留了一个从地址 0x500000 开始的512 KB的区域。你想让应用程序将物理存储器提交给已保留区域的6 KB部分,从2 KB的地方开始,直到已保留区域的地址空间。为此,可以调用带有MEM_COMMIT标志的VirtualAlloc函数,如下所示:


VirtualAlloc(
  (pvOID)(5242880 + (2* 1024)),
  6*1024,
  MEM_COMMIT,
  PAGE_READWR1TE
);

在这个例子中,

系统必须提交8 KB的物理存储器,

地址范围从0x500000到0x500007 (0x500000 + 8 KB - 1字节)。

这两个提交的页面都拥有PAGE_READWRITE保护属性。


   注意:保护属性只以整个页面为单位来赋予。 同一个内存页面的不同部分不能使用不同的保护属性。然而,区域中的一个页面可以使用一种保护属性(比如PAGE_READWRITE),而同一个区域中的另一个页面可以使用不同的保护属性(比如PAGE_READONLY)。


三、同时预定和调拨物理存储器


有时你可能想要在保留区域的同时,将物理存储器提交给它。只需要一次调用 VirtualAlloc函数就能进行这样的操作,如下所示:

PVOID pvHem = VirtualAlloc(
        NULL,
        99* 1024,
        MEM_RESERVE |MEN_COMMIT,
        PAGE_READWRITE
        );

这个函数调用请求保留一个 99 KB的区域,并且将99 KB的物理存储器提交给它。当系统处理这个函数调用时,它首先要搜索你的进程的地址空间,找出未保留的地址空间中一个地址连续的区域,它必须足够大,能够存放100 KB(在4 KB页面的计算机上)或104 KB(在8 KB页面的计算机上)。


系统之所以要搜索地址空间,原因是已将 pvAddress参数设定为NULL。如果为pvAddress设定了内存地址,系统就要查看在该内存地址上是否存在足够大的未保留地址空间。如果系统找不到足够大的未保留地址空间,VirtualAlloc将返回NULL,


如果能够保留一个合适的区域,系统就将物理存储器提交给整个区域。无论是该区域还是提交的内存,都将被赋予PAGE_READWRITE保护属性。


最后需要说明的是,VirtualAlloc将返回保留区域和提交区域的虚拟地址,然后该虚拟地址被保存在pvMem变量中。如果系统无法找到足够大的地址空间,或者不能提交该物理存储器,VirtualAlloc将返回NULL。


大页面支持

Windows还提供大页面支持,可以在处理大内存块时提高性能。这时,系统不再使用GetSysInfo的dwPageSize 页面粒度,而是使用以下函数大页面分配粒度:

SIZE_T GetLargePageMinimum();

   注意:如果CPU不支持大页面分配,那么GetLargePageMinimum会返回0.只要需要分配的内存大于GetLargePageMinimum返回值,就可以使用Windows大页面的支持。只要分配内存调用VirtualAlloc并将MEM_LARGE_PAGE标志和fdwAllocationType参数按位或起来即可


还必须满足三个条件:


   要分配的内存块的大小(即dwSize参数的值),必须是 GetLargePageMinimum函数返回值的整数倍。

   在调用VirtualAlloc时,必须把MEM_RESERVE | MEM_COMMIT与MEM_LARGE_PAGE参数按位或起来。换句话说,我们必须同时预定和调拨内存,我们不能先预定一块区域然后再给其中的一部分调拨物理存储器。

   在用VirtualAlloc分配内存时必须传PAGE_READWRITE保护属性给fdwProtect参数。


Windows认为用MEM_LARGE_PAGE标志分配到的内存是不可换页的(unpagable):也就是说必须驻留在内存中。这也是为什么这种方式得到的内存能提供更好的性能。


   注意:MEM_LARGE_PAGE并不是所有用户都有这个权限,需要调用具有内存中锁定页面(Lock Page in Memory)的用户权限,而这必须管理员先分配。

   参考这个大佬的操作步骤


四、何时调拨物理存储器


案例:实现一个电子表格应用程序

前置条件

假设想实现一个电子表格应用程序,这个电子表格为 2 0 0行 x 256列。对于每一个单元格,都需要一个C E L L D ATA结构来描述单元格的内容。若要处理这种二维单元格矩阵,最容易的方法是在应用程序中声明下面的变量:

CELLDATA CellData[200][256];


如果C E L L D ATA结构的大小是1 2 8字节,那么这个二维矩阵将需要6 553 600(200 x 256 x1 2 8)个字节的物理存储器。对于电子表格来说,如果直接用页文件来分配物理存储器,那么这是个不小的数目了,尤其是考虑到大多数用户只是将信息放入少数的单元格中,而大部分单元格却空闲不用,因此显得有些浪费。内存的利用率非常低。


对比传统实现

传统上,电子表格一直是用其他数据结构技术来实现的,比如链接表等。使用链接表,只需要为电子表格中实际包含数据的单元格创建C E L L D ATA结构。由于电子表格中的大多数单元格都是不用的,因此这种方法可以节省大量的内存。但是这种方法使得你很难获得单元格的内容。如果想知道第5行第1 0列的单元格的内容,必须遍历链接表,才能找到需要的单元格,因此使用链接表方法比明确声明的矩阵方法速度要慢。


使用虚拟内存实现及步骤

虚拟内存为我们提供了一种兼顾预先声明二维矩阵和实现链接表的两全其美的方法。运用虚拟内存,既可以使用已声明的矩阵技术进行快速而方便的访问,又可以利用链接表技术大大节省内存的使用量。


如果想利用虚拟内存技术的优点,你的程序必须按照下列步骤来编写:


   保留一个足够大的地址空间区域,用来存放 C E L L D ATA结构的整个数组。保留一个根本不使用任何物理存储器的区域。

   当用户将数据输入一个单元格时,找出 C E L L D ATA结构应该进入的保留区域中的内存地址。当然,这时尚未有任何物理存储器被映射到该地址,因此,访问该地址的内存的任何企图都会引发访问违规。

   就C E L L D ATA结构来说,只将足够的物理存储器提交给第二步中找到的内存地址(你可以告诉系统将物理存储器提交给保留区域的特定部分,这个区域既可以包含映射到物理存储器的各个部分,也可以包含没有映射到物理存储器的各个部分)。

   设置新的C E L L D ATA结构的成员。


现在物理存储器已经映射到相应的位置,你的程序能够访问内存,而不会引发访问违规。


这个虚拟内存技术非常出色,因为只有在用户将数据输入电子表格的单元格时,才会提交物理存储器。由于电子表格中的大多数单元格是空的,因此大部分保留区域没有提交给它的物理存储器。


确定何时提交/调拨物理存储器

虚拟内存技术存在的一个问题是,必须确定物理存储器在何时提交。如果用户将数据输入一个单元格,然后只是编辑或修改该数据,那么就没有必要提交物理存储器,因为该单元格的C E L L D ATA结构的内存在数据初次输入时就已经提交了。


另外,系统总是按页面的分配粒度来提交物理存储器的。因此,当试图为单个 C E L L D ATA结构提交物理存储器时(像上面的第二步那样),系统实际上提交的是内存的一个完整的页面。这并不像它听起来那样十分浪费:为单个C E L L D ATA结构提交物理存储器的结果是,也要为附近的其他C E L L D ATA结构提交内存。如果这时用户将数据输入邻近的单元格(这是经常出现的情况),就不需要提交更多的物理存储器。


有4种方法可以用来确定是否要将物理存储器提交给区域的一个部分:


   始终设法进行物理存储器的提交。每次调用 Vi r t u a l A l l o c函数的时候,不要查看物理存储器是否已经映射到地址空间区域的一个部分,而是让你的程序设法进行内存的提交。系统首先查看内存是否已经被提交,如果已经提交,那么就不要提交更多的物理存储器。

   这种方法最容易操作,但是它的缺点是每次改变 C E L L D ATA结构时要多进行一次函数的调用,这会使程序运行得比较慢。


   (使用Vi r t u a l Q u e r y函数)确定物理存储器是否已经提交给包含C E L L D ATA结构的地址空间。如果已经提交了,那么就不要进行任何别的操作。如果尚未提交,则可以调用Vi r t u a l A l l o c函数以便提交内存。这种方法实际上比第一种方法差,它既会增加代码的长度,又会降低程序运行的速度(因为增加了对Vi r t u a l A l l o c函数的调用)。


   保留一个关于哪些页面已经提交和哪些页面尚未提交的记录。这样做可以使你的应用程序运行得更快,因为不必调用 Vi r t u a l A l l o c函数,你的代码能够比系统更快地确定内存是否已经被提交。它的缺点是,必须不断跟踪页面提交的信息,这可能非常简单,也可能非常困难,要根据你的情况而定。


   使用结构化异常处理( S E H)方法,这是最好的方法。 S E H是一个操作系统特性,它使系统能够在发生某种情况时将此情况通知你的应用程序。实际上可以创建一个带有异常处理程序的应用程序,然后,每当试图访问未提交的内存时,系统就将这个问题通知应用程序。然后你的应用程序便进行内存的提交,并告诉系统重新运行导致异常条件的指令。这时对内存的访问就能成功地进行了,程序将继续运行,仿佛从未发生过问题一样。这种方法是优点最多的方法,因为需要做的工作最少(也就是说要你编写的代码比较少),同时,你的程序可以全速运行。关于 S E H的全面介绍,请参见第2 3、2 4和2 5章。第2 5章中的电子表格示例应用程序说明了如何按照上面介绍的方法来使用虚拟内存。


五、撤销调拨物理存储器及释放区域


VirtualFree函数

若要回收映射到一个区域的物理存储器,或者释放这个地址空间区域,可调用 VirtualFree函数:

B0OL VirtualFree(
  LPVOID pvAddress,
  SIZE_T dwSize,
  DWORD fdwFreeType
);

首先让我们观察一下调用Vi r t u a l F r e e函数来释放一个已保留区域的简单例子。 当你的进程不再访问区域中的物理存储器时,就可以释放整个保留的区域和所有提交给该区域的物理存储器,方法是一次调用Vi r t u a l F r e e函数。


正常回收并全部释放情况

就这个函数的调用来说, pvAddress参数必须是该区域的基地址。 此地址与该区域被保留时VirtualAlloc函数返回的地址相同。系统知道在特定内存地址上的该区域的大小,因此可以为dwSize参数传递0。实际上,必须为dwSize参数传递0,否则对VirtualFree的调用就会失败。对于第三个参数fdwFreeType,必须传递 MEM_RELEASE ,以告诉系统将所有映射的物理存储器提交给该区域并释放该区域。当释放一个区域时,必须释放该区域保留的所有地址空间。例如不能保留一个128 KB的区域,然后决定只释放它的64 KB。必须释放所有的128 KB。


回收某些物理存储器情况

当想要从一个区域回收某些物理存储器,但是却不释放该区域时,也可以调用 VirtualFree函数 ,若要回收某些物理存储器,必须在VirtualFree函数的pvAddress参数中传递用于标识要回收的第一个页面的内存地址,还必须在 dwSize参数中设定要释放的字节数,并在 fdwFreeType参数中传递 MEM_DECOMMIT 标志。


回收原则:按页面粒度回收

与提交物理存储器的情况一样,回收时也必须按照页面的分配粒度来进行。 这就是说,设定页面中间的一个内存地址就可以回收整个页面。当然,如果 pvAddress + dwSize的值位于一个页面的中间,那么包含该地址的整个页面将被回收。因此位于 pvAddress 至pvAddress +dwSize范围内的所有页面均被回收。


如果dwSize是0,pvAddress是已分配区域的基地址,那么 VirtualFree将回收全部范围内的已分配页面。 当物理存储器的页面已经回收之后,已释放的物理存储器就可以供系统中的所有其他进程使用,如果试图访问未回收的内存,将会造成访问违规。


MEM_RELEASE和MEM_DECOMMIT

MEM_RELEASE

MEM_RELEASE 告诉系统将所有映射的物理存储器提交给该区域并释放该区域。当释放一个区域时,必须释放该区域保留的所有地址空间。例如不能保留一个128 KB的区域,然后决定只释放它的64 KB。必须释放所有的128 KB。


MEM_DECOMMIT

当想要从一个区域回收某些物理存储器,但是却不释放该区域时,也可以调用 VirtualFree函数 ,若要回收某些物理存储器,必须在VirtualFree函数的pvAddress参数中传递用于标识要回收的第一个页面的内存地址,还必须在 dwSize参数中设定要释放的字节数,并在 fdwFreeType参数中传递 MEM_DECOMMIT 标志。


15.5.1 何时撤销调拨物理存储器

在实践中,想知道何时能够安全地撤销调拨物理存储器是需要一些技巧的。

  • 最简单的方法是将结构设计成正好等于一个页面的大小。
  • 另一种更为实用的解决方案是记录哪些结构正在使用。
  • 最后一种解决方案是实现一个垃圾收集函数。


15.5.2 虚拟内存分配示例程序

Virtual Memory Allocator

也可以参考这大佬的源码

源码


六、改变保护属性


虽然实际应用中很少需要改变保护属性,但这仍然是可行的。

假设有一段代码来管理一个链表,并把链表中的节点保存在一个已预订的区域中。然后设计一个处理函数,在每个函数的开头把物理存储页的保护属性改成 PAGE_READWRITE,并在函数结束前把保护属性改成 PAGE_NOACCESS 这样可以保护链表的数据,使他免受其他缺陷程序的影响。

如果进程中的其他代码试图用一个错误指针来访问链表数据,将会引发访问违规。如果在应用程序中定位一个很难发现的缺陷,可以试试看保护属性,他可以发挥难以置信的作用。

WINBASEAPI
_Success_(return != FALSE)
BOOL
WINAPI
VirtualProtect(
    _In_ LPVOID lpAddress,
    _In_ SIZE_T dwSize,
    _In_ DWORD flNewProtect,
    _Out_ PDWORD lpflOldProtect
    );

lpAddress:是内存的基地址(用户分区)

dwSize:想要改变保护属性的大小,以字节为单位,

flNewProtect:

除了PAGE_WRITECOPY和PAGE_EXECUTE_WRITECOPY以外的任何PAGE_*保护属性。

lpfOldProtect: 返回原来的保护属性。必须传递一个有效的地址给lpfOldProtect,否则会调用失败。

保护属性是与整个物理存储页关联的,以下代码实际上是在给两个物理存储页指定保护属性 PAGE_NOACCESS

VirtualProtect(pvRgnBase + (3 * 1024), 2 * 1024,
    PAGE_NOACCESS, &flOldProtect);

若干连续的物理存储页跨越了不同的区域时, VirtualProtect是不能改变他们的保护属性的。如果有相邻的区域,有想改变跨区域的连续页面的保护属性,需要调用 VirtualProtect多次。


七、重置物理存储器的内容


当修改物理存储页的时候,系统尽量把改动保持在内存中。但是,当应用程序在运行时候,系统可能要从exe文件,dll文件或者页交换文件中载入新的页面到内存。为了满足最近的载入请求,系统会在内存中查找可用的页面,如果找到的页面已经被修改过,那么系统还必须将它们换出到页面文件中。

windows提供了一项特性(重置物理存储器)告知系统一个或几个物理存储页中的数据没有被修改过。如果系统正在查找一页闲置内存,并找到了一个修改过的页面,那么系统必须把该内存页写入到页交换文件中。这个操作比较慢,会影响性能。对于大多数应用程序来说,我们都希望系统把修改后的页面保存到页交换文件中。


但是,有些应用程序只在一小段时间使用存储器,之后也不需要保留存储器中的内容。为了提高性能,应用程序可以告知系统不要在交换文件中保存指定的存储页。这是一种告知系统一个页面未被修改过的一种方法。如果系统要将一个内存页挪做他用,他不会将页面的内容保存到页交换文件中,这样就提高的了性能。

为了重置存储器,应用程序调用 VirtualAlloc函数,并在第三个参数传入 MEM_RESET标志。


调用 VirtualAlloc时,如果被引用到的页面在页交换文件中,那么系统会直接抛弃这些页面。下次应用程序再访问存储器,会使用新的,全部清零的内存页。如果重置的页面在内存中,系统会将其标记为未修改过。这样系统就绝不会把他们写到页交换文件中。(注意,即使该内存页没有被清零,我们也不应该继续读取其中的内容,因为系统随时可能会把该页挪做他用。因此一旦做了页重置,就应当将其视为无效数据)


还有一些注意事项:


   调用 VirtualAlloc,基地址通常会被向下取整到页面大小的整数倍,而大小会被向下取整到页面大小的整数倍;在重置存储器时,这种方式对基地址进行取整是非常危险的;如果传入 MEM_RESET那么VirtualAlloc会从相反的方向进行取整操作。

   例如:

PINT pnData = (PINT)VirtualAlloc(NULL, 1024, 
    MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  pnData[0] = 100;
  pnData[1] = 200;
  VirtualAlloc((PVOID)pnData, sizeof(int), MEM_RESET, PAGE_READWRITE);

代码先调拨了一页存储器,然后告知系统最前4个字节(sizeof(4))不再使用,可以被重置。事实上重置会调用失败。VirtualAlloc返回NULL,GetLastError返回ERROR_INVALID_ADDRESS

因为在执行 MEM_RESET的时候为了防止误操作把其他重要的数据抛弃。会把大小向下取整到0,而重置0字节是没有意义的。把大小向下取整到页面的整数倍也是出于同样的原因:如果垃圾数据并未占满整个页,那么我们并不希望重置整个页,因为其中可能还包含有效数据。这样系统就确保只会重置整个页面都是垃圾数据的内存页。


   MEM_RESET只能单独使用,不能和其他标志进行按位或。

PVOID pvMem = VirtualAlloc(NULL, 1024,
    MEM_RESERVE | MEM_COMMIT | MEM_RESET, PAGE_READWRITE);

该代码会失败并返回NULL

  1. 在使用 MEM_RESET调用 VirtualAlloc时,需要传递一个有效的保护属性值,实际上函数并没有用到这个值。

原文链接

MemReset示例程序

也可以参考这大佬的源码

源码


八、地址窗口扩展


随着时间的推移,应用程序需要越来越多的内存。服务器程序尤其如此,将数据保存在内存中而减少磁盘和内存的页交换以提高性能。32位地址空间不大够用


Windows提供了一项特性,即地址窗口扩展(Address Windowing Extension,也称作AWE)在创建AWE以后,Microsoft有以下两个目标:

1)允许应用程序以一种特殊的方式分配内存,操作系统保证不会将以这种方式分配的内存换出到磁盘上。

2)允许应用程序访问比进程地址空间还要多的内存。


AWE可以让应用程序分配一块或多块内存。当一开始分配时,在进程的地址空间中是看不到这些内存块的。应用程序通过调用 VirtualAlloc预定地址空间区域,这就是地址窗口。然后程序调用一个函数,每调用一个函数,就把一块内存指定到该地址窗口。(把内存地址指定到地址窗口是毫秒级别的)


使用地址窗口,一次只能访问一块内存。因此需要在代码中显式调用函数来把不同的内存块指定到地址窗口中。(这增加了代码编写的难度)


原文链接


AWE示例程序

也可以参考这大佬的源码

源码


总结


预定空间和调拨空间的关系


预定:预先分配起始地址,但尚未分配物理存储器。不会消耗物理存储器。

调拨:实际上即将开始分配物理存储器,然后使用预定的地址进行读写操作。

撤销调拨物理存储器和释放物理存储器的关系


回收(撤销调拨):取消所调拨的物理存储器

释放物理存储器:释放物理存储器的内容


相关文章
|
5天前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
16 2
|
30天前
|
NoSQL 测试技术
内存程序崩溃
【10月更文挑战第13天】
138 62
|
14天前
|
C# Windows
【Azure App Service】在App Service for Windows上验证能占用的内存最大值
根据以上测验,当使用App Service内存没有达到预期的值,且应用异常日志出现OutOfMemory时,就需要检查Platform的设置是否位64bit。
38 11
|
17天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
1月前
|
监控 Ubuntu Linux
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
这篇文章介绍了如何在Ubuntu和Windows系统中通过设置相同的时区并使用ntp服务来解决时间同步问题。
65 4
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
|
1月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
112 21
|
2月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
1月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
2月前
|
网络协议 API Windows
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
|
2月前
|
Windows
[原创]用MASM32编程获取windows类型
[原创]用MASM32编程获取windows类型

相关课程

更多
下一篇
无影云桌面