win32汇编-内存管理 (五)

当设计一个可能需要申请大量内存的程序时,如何预先得知系统的配置情况呢?对此可以使用GlobalMemoryStatus函数:

    invoke  GlobalMemoryStatuslpBuffer

lpBuffer指向一个MEMORYSTATUS结构,结构的定义如下:

MEMORYSTATUS STRUCT

  dwLength            DWORD      ?     ;本结构的长度

  dwMemoryLoad      DWORD      ?     ;已用内存的百分比

  dwTotalPhys       DWORD      ?     ;物理内存总量

  dwAvailPhys       DWORD      ?     ;可用物理内存

  dwTotalPageFile   DWORD      ?     ;交换文件总的大小

  dwAvailPageFile   DWORD      ?     ;交换文件中空闲部分大小

  dwTotalVirtual    DWORD      ?     ;用户可用的地址空间

  dwAvailVirtual    DWORD      ?     ;当前空闲的地址空间

MEMORYSTATUS ENDS

在调用之前需要首先将dwLength字段设置为MEMORYSTATUS结构的长度,当调用GlobalMemoryStatus函数后,函数会在结构中返回对应的数值。注意:dwTotalPageFile字段返回的是交换文件的最大值,并不是当前实际建立的交换文件的大小,一般当前的交换文件大小会小于这个数值,但这个数值的大小也不是确定的,如果需要的话,系统会增加它的大小直到不再有空余的磁盘空间放置交换文件为止。

标准内存管理函数的功能是在进程的默认堆中申请和释放内存块,它由下面一些函数组成:

GlobalAlloc,GlobalFree和GlobalReAlloc分别用来申请、释放和修改内存大小;

GlobalLock和GlobalUnlock用来进行锁定操作;

在Win32中是完全相同的,读者可以自由使用名字以Global或Local为前缀的函数。

而GlobalDiscard,GlobalFlags,GlobalHandle和GlobalSize等用来丢弃内存或获取已分配内存的一些信息。

1. 固定的内存块

常规意义上的内存就是固定的内存块,因为申请到内存后,这块内存的线性地址是固定不变的。要申请一块固定的内存,可以使用函数:

invoke  GlobalAllocGMEM_FIXED or GMEM_ZEROINITdwBytes

    .if     eax

            mov lpMemoryeax

    .endif

第一个参数是标志,GMEM_FIXED表示申请的是固定的内存块,GMEM_ZEROINIT表示需要将内存块中的所有字节预先初始化为0,也可以简单地使用GPTR标志,它就相当于是GMEM_FIXED or GMEM_ZEROINIT;第2个参数dwBytes指出了需要申请的是以字节为单位的内存大小。如果内存申请失败,eax中返回NULL,否则返回值是一个指向内存块起始地址的指针,用户需要保存这个指针,在使用内存或者释放内存的时候还要用到它。

如果要释放一个先前申请的固定内存块,可以使用GlobalFree函数:

invoke  GlobalFreelpMemory

 

在实际使用中往往需要改变一个内存块的大小,这时候就要用到GlobalReAlloc函数,这个函数可以缩小或者扩大一块已经申请到的内存:

 

invoke GlobalReAlloclpMemorydwBytesuFlags

 

.if    e   ax

 

       mov lpNewMemoryeax

 

.endif

 

lpMemory是先前申请的内存块指针,dwBytes是新的大小,如果这个数值比原来申请的时候要小,也就是需要缩小内存块,那么uFlags标志参数可以是NULL,如果缩小内存块的操作不成功,那么函数的返回值为0,否则是新的缩小了的内存块指针,当然,这个指针和原来的指针肯定是一样的。

可以在GlobalReAlloc函数中通过指定不同的uFlags来规定是否允许Windows在必要的时候移动内存块。当uFlags中有GMEM_MOVEABLE选项的时候,如果需要移动内存块,Windows会在别的地方开辟一块新的内存,并把原来内存块中的内容自动复制到新的内存块中,这时函数的返回值是新的指针,原来的指针作废。

如果不指定GMEM_MOVEABLE选项,那么只有当内存块后面扩展所需的空间没有被使用时,函数才会执行成功,否则,函数失败并返回NULL,这时原来的指针继续有效。

为了保证内存块扩大成功,建议总是使用下面的语句来扩大和缩小内存:

invoke  GlobalReAlloclpMemorydwBytesGMEM_ZEROINIT or GMEM_MOVEABLE

.if     eax

            mov lpMemoryeax

    .endif

指定GMEM_ZEROINIT选项可以使内存块扩大的部分自动被初始化为0,然后程序判断返回值,如果改变大小成功的话,则用新的指针替换原来的指针,其他和原来指针有关的值也不要忘了同时更新。

2. 可移动的内存块

可移动的内存块在不使用的时候允许Windows改变它的线性地址

要申请一个可移动的内存块,使用的函数还是GlobalAlloc,但需要使用不同的参数:

invoke  GlobalAllocGMEM_MOVEABLE or GMEM_ZEROINITdwBytes

.if     eax

    mov hMemoryeax

.endif

GMEM_MOVEABLE标志指定了分配的内存是可移动的,GMEM_ZEROINIT同样表示将申请到的内存块的内容初始化为0(也可以用GHND标志,它就相当于GMEM _MOVEABLE or GMEM_ZEROINIT);如果内存申请失败,eax中返回NULL,成功的话返回值是一个句柄而不是内存指针,用户需要保存这个句柄,在锁定或释放内存的时候还要用到它。一个进程可以申请的可移动内存的块数最大不能超过65 536个,申请固定内存块时则没有数量限制。

要使用可移动内存之前,需要把它锁定,这相当于告诉Windows现在程序要使用这块内存了,不能将它移动,锁定内存使用GlobalLock函数:

invoke  GlobalLock,hMemory

.if     eax

    mov lpMemoryeax

.endif

函数的入口参数是GlobalAlloc返回的内存句柄,如果锁定成功,函数返回一个指针,程序可以用使用固定内存块同样的方法来使用它;如果锁定失败,则函数返回NULL。每次锁定返回的指针位置可能是不同的,但内存块中的数据不会变化。

当程序暂时不需要操作这块内存的时候,应该将它解锁,否则和使用固定的内存块就没有区别了,解锁使用GlobalUnlock函数:

        invoke  GlobalUnlock,hMemory

函数的参数同样是GlobalAlloc返回的句柄,解锁成功的话函数返回非0值。读者可能有个问题:在多线程的程序中,两个地方同时锁定内存,但当一个地方还在使用的情况下另一个地方却调用GlobalUnlock将内存解锁了怎么办?其实不用担心这个问题,Windows为每个可移动的内存句柄维护一个锁定计数,每次锁定内存的时候计数加1,解锁的时候计数减1,只有当计数为0的时候内存才真正被解锁,所以只要程序中的GlobalLock函数和GlobalUnlock函数是配对的,就不用担心这个问题。

要释放一个可移动的内存块,同样使用GlobalFree函数:

    invoke  GlobalFreehMemory

但使用的参数是GlobalAlloc返回的内存句柄,如果释放成功,函数返回NULL。不管内存当前是否处在锁定状态,都可以被成功释放。

调整可移动内存块的大小,同样使用GlobalReAlloc函数:

    invoke  GlobalReAllochMemorydwBytesGMEM_ZEROINIT or GMEM_MOVEABLE

如果调整成功,返回值就是输入的hMemory,失败的话返回值是NULL。即使内存块在锁定状态,函数仍然可以调用成功,但这时候内存块可能已经被移动了位置,原来用GlobalLock函数获取的指针可能已经失效了,所以调整可移动内存块的大小最好还是先将内存解锁,等调整完毕以后再锁定使用。

 

 

3. 可丢弃的内存块

 

分配可移动内存块的时候还可以配合GMEM_MOVEABLE标志使用GMEM_DI SCARDABLE标志,这样生成的内存块是可丢弃的内存块,表示当Windows急需内存使用的时候,可以将它从物理内存中丢弃,可丢弃的内存块首先必须是可移动的内存块。函数调用如下:

 

invoke  GlobalAllocGHND or GMEM_DISCARDABLEdwBytes

 

.if     eax

 

            mov hMemoryeax

 

.endif

 

当用GlobalLock锁定内存的时候如果返回NULL指针,表示内存已经被Windows丢弃了,当然其中的数据也丢失了,程序需要重新生成数据。当内存块被丢弃的时候,内存句柄还是有效的,如果程序还要使用这个句柄,那么可以对它使用GlobalReAlloc函数来重新分配内存。

 

当可丢弃内存块的锁定计数为0时,程序也可以使用GlobalDiscard函数主动将它丢弃,这和Windows将它丢弃的效果是一样的:

 

invoke  GlobalDiscardhMemory

 

4. 获取内存块的信息

 

标准内存管理函数中的其他函数GlobalFlags,GlobalHandle和GlobalSize用来获取已分配内存块的一些信息。

 

GlobalFlags函数主要用来获取可移动内存块当前的锁定计数,也可以用来检测可丢弃内存块是否已经被丢弃。对一个hMemory调用GlobalFlags函数如下所示:

 

    invoke  GlobalFlagshMemory

 

如果不是返回GMEM_INVALID_HANDLE,则表示调用成功,这时返回值的低8位是内存块的锁定计数,程序可以用GMEM_LOCKCOUNT对获取计数值进行and操作(在Windows.inc头文件中,GMEM_LOCKCOUNT定义为0ffh):

 

    invoke  GlobalFlagshMemory

 

    and     eaxGMEM_LOCKCOUNT

 

mov     dwLockCounteax

 

返回值的其他数据位可能包含下列标志:

 

   GMEM_DISCARDABLE          表示内存块是可丢弃内存块。

 

   GMEM_DISCARDED                     表示内存块已经被丢弃。

 

GlobalHandle可以从GlobalLock函数得到的lpMemory值获取其对应的hMemory,而GlobalSize函数可以获知一个内存块的尺寸。

 

 

原文地址:https://www.cnblogs.com/xuankuwa/p/3660414.html